From f3b6eb1c85b59022a3760f57a376ffd97c06edce Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 14 Apr 2021 14:00:16 -0700 Subject: [PATCH 001/694] grpc-js-xds: Update deps and generated code for xDS v3 --- packages/grpc-js-xds/deps/envoy-api | 2 +- packages/grpc-js-xds/deps/udpa | 2 +- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/generated/ads.ts | 63 + packages/grpc-js-xds/src/generated/cluster.ts | 89 +- .../grpc-js-xds/src/generated/endpoint.ts | 49 +- .../envoy/api/v2/UpstreamConnectionOptions.ts | 17 - .../src/generated/envoy/api/v2/Vhds.ts | 17 - .../v2/auth/CertificateValidationContext.ts | 315 ----- .../envoy/api/v2/auth/CommonTlsContext.ts | 140 --- .../envoy/api/v2/auth/DownstreamTlsContext.ts | 101 -- .../envoy/api/v2/auth/GenericSecret.ts | 17 - .../envoy/api/v2/auth/PrivateKeyProvider.ts | 42 - .../envoy/api/v2/auth/SdsSecretConfig.ts | 23 - .../src/generated/envoy/api/v2/auth/Secret.ts | 36 - .../envoy/api/v2/auth/TlsCertificate.ts | 78 -- .../envoy/api/v2/auth/TlsParameters.ts | 171 --- .../envoy/api/v2/auth/TlsSessionTicketKeys.ts | 61 - .../envoy/api/v2/auth/UpstreamTlsContext.ts | 68 -- .../envoy/api/v2/core/BuildVersion.ts | 4 +- .../envoy/api/v2/core/GrpcProtocolOptions.ts | 17 - .../envoy/api/v2/core/RuntimeDouble.ts | 2 +- .../envoy/api/v2/core/SelfConfigSource.ts | 20 - .../v2/endpoint/EndpointLoadMetricStats.ts | 2 +- .../generated/envoy/api/v2/listener/Filter.ts | 34 - .../envoy/api/v2/listener/FilterChain.ts | 118 -- .../envoy/api/v2/route/HedgePolicy.ts | 66 -- .../api/v2/route/QueryParameterMatcher.ts | 86 -- .../generated/envoy/api/v2/route/RateLimit.ts | 341 ------ .../envoy/api/v2/route/RedirectAction.ts | 139 --- .../envoy/api/v2/route/RetryPolicy.ts | 218 ---- .../v2 => accesslog/v3}/AccessLog.ts | 37 +- .../config/accesslog/v3/AccessLogFilter.ts | 124 ++ .../v2 => accesslog/v3}/AndFilter.ts | 8 +- .../config/accesslog/v3/ComparisonFilter.ts | 48 + .../config/accesslog/v3/DurationFilter.ts | 23 + .../v2 => accesslog/v3}/ExtensionFilter.ts | 11 +- .../v2 => accesslog/v3}/GrpcStatusFilter.ts | 28 +- .../envoy/config/accesslog/v3/HeaderFilter.ts | 25 + .../config/accesslog/v3/MetadataFilter.ts | 48 + .../v3}/NotHealthCheckFilter.ts | 2 +- .../accesslog/v2 => accesslog/v3}/OrFilter.ts | 8 +- .../v2 => accesslog/v3}/ResponseFlagFilter.ts | 20 +- .../config/accesslog/v3/RuntimeFilter.ts | 73 ++ .../config/accesslog/v3/StatusCodeFilter.ts | 23 + .../v2 => accesslog/v3}/TraceableFilter.ts | 2 +- .../cluster/v3}/CircuitBreakers.ts | 50 +- .../{api/v2 => config/cluster/v3}/Cluster.ts | 1043 +++++++++++------ .../config/cluster/v3/ClusterCollection.ts | 19 + .../cluster => config/cluster/v3}/Filter.ts | 2 +- .../cluster/v3}/LoadBalancingPolicy.ts | 23 +- .../cluster/v3}/OutlierDetection.ts | 48 +- .../config/cluster/v3/TrackClusterStats.ts | 36 + .../cluster/v3}/UpstreamBindConfig.ts | 8 +- .../cluster/v3/UpstreamConnectionOptions.ts | 17 + .../generated/envoy/config/core/v3/Address.ts | 35 + .../core/v3}/AggregatedConfigSource.ts | 6 +- .../core/v3}/ApiConfigSource.ts | 44 +- .../v2/core => config/core/v3}/ApiVersion.ts | 4 +- .../envoy/config/core/v3/AsyncDataSource.ts | 34 + .../envoy/config/core/v3/BackoffStrategy.ts | 43 + .../envoy/config/core/v3/BindConfig.ts | 49 + .../envoy/config/core/v3/BuildVersion.ts | 36 + .../envoy/config/core/v3/CidrRange.ts | 33 + .../core => config/core/v3}/ConfigSource.ts | 53 +- .../envoy/config/core/v3/ControlPlane.ts | 26 + .../envoy/config/core/v3/DataSource.ts | 40 + .../config/core/v3/EnvoyInternalAddress.ts | 28 + .../core/v3}/EventServiceConfig.ts | 8 +- .../envoy/config/core/v3/Extension.ts | 75 ++ .../config/core/v3/ExtensionConfigSource.ts | 72 ++ .../config/core/v3/GrpcProtocolOptions.ts | 17 + .../v2/core => config/core/v3}/GrpcService.ts | 213 ++-- .../envoy/config/core/v3/HeaderMap.ts | 17 + .../envoy/config/core/v3/HeaderValue.ts | 38 + .../envoy/config/core/v3/HeaderValueOption.ts | 34 + .../v2/core => config/core/v3}/HealthCheck.ts | 258 ++-- .../core => config/core/v3}/HealthStatus.ts | 2 +- .../core/v3}/Http1ProtocolOptions.ts | 62 +- .../core/v3}/Http2ProtocolOptions.ts | 128 +- .../config/core/v3/Http3ProtocolOptions.ts | 22 + .../core/v3}/HttpProtocolOptions.ts | 26 +- .../generated/envoy/config/core/v3/HttpUri.ts | 79 ++ .../envoy/config/core/v3/KeepaliveSettings.ts | 40 + .../envoy/config/core/v3/Locality.ts | 56 + .../envoy/config/core/v3/Metadata.ts | 67 ++ .../generated/envoy/config/core/v3/Node.ts | 159 +++ .../generated/envoy/config/core/v3/Pipe.ts | 30 + .../config/core/v3/ProxyProtocolConfig.ts | 29 + .../core/v3}/RateLimitSettings.ts | 2 +- .../envoy/config/core/v3/RemoteDataSource.ts | 40 + .../envoy/config/core/v3/RequestMethod.ts | 17 + .../envoy/config/core/v3/RetryPolicy.ts | 38 + .../envoy/config/core/v3/RoutingPriority.ts | 15 + .../envoy/config/core/v3/RuntimeDouble.ts | 30 + .../config/core/v3/RuntimeFeatureFlag.ts | 35 + .../core/v3/RuntimeFractionalPercent.ts | 49 + .../envoy/config/core/v3/RuntimePercent.ts | 31 + .../envoy/config/core/v3/RuntimeUInt32.ts | 30 + .../envoy/config/core/v3/SelfConfigSource.ts | 31 + .../envoy/config/core/v3/SocketAddress.ts | 97 ++ .../envoy/config/core/v3/SocketOption.ts | 90 ++ .../core/v3/SubstitutionFormatString.ts | 197 ++++ .../envoy/config/core/v3/TcpKeepalive.ts | 43 + .../core/v3}/TcpProtocolOptions.ts | 2 +- .../envoy/config/core/v3/TrafficDirection.ts | 19 + .../envoy/config/core/v3/TransportSocket.ts | 43 + .../config/core/v3/TypedExtensionConfig.ts | 43 + .../core/v3}/UpstreamHttpProtocolOptions.ts | 2 +- .../envoy/config/core/v3/WatchedDirectory.ts | 24 + .../endpoint/v3}/ClusterLoadAssignment.ts | 74 +- .../envoy/config/endpoint/v3/ClusterStats.ts | 115 ++ .../endpoint/v3}/Endpoint.ts | 32 +- .../endpoint/v3/EndpointLoadMetricStats.ts | 35 + .../endpoint/v3}/LbEndpoint.ts | 24 +- .../endpoint/v3}/LocalityLbEndpoints.ts | 14 +- .../endpoint/v3/UpstreamEndpointStats.ts | 104 ++ .../endpoint/v3/UpstreamLocalityStats.ts | 104 ++ .../filter/accesslog/v2/AccessLogFilter.ts | 115 -- .../filter/accesslog/v2/ComparisonFilter.ts | 48 - .../filter/accesslog/v2/DurationFilter.ts | 23 - .../filter/accesslog/v2/HeaderFilter.ts | 25 - .../filter/accesslog/v2/RuntimeFilter.ts | 63 - .../filter/accesslog/v2/StatusCodeFilter.ts | 23 - .../http_connection_manager/v2/HttpFilter.ts | 34 - .../http_connection_manager/v2/ScopedRds.ts | 17 - .../v2/ScopedRouteConfigurationsList.ts | 17 - .../v3}/ActiveRawUdpListenerConfig.ts | 2 +- .../config/listener/{v2 => v3}/ApiListener.ts | 2 +- .../envoy/config/listener/v3/Filter.ts | 52 + .../envoy/config/listener/v3/FilterChain.ts | 170 +++ .../listener/v3}/FilterChainMatch.ts | 46 +- .../v2 => config/listener/v3}/Listener.ts | 231 ++-- .../config/listener/v3/ListenerCollection.ts | 19 + .../listener/v3}/ListenerFilter.ts | 19 +- .../v3}/ListenerFilterChainMatchPredicate.ts | 30 +- .../listener/v3}/UdpListenerConfig.ts | 9 +- .../route => config/route/v3}/CorsPolicy.ts | 86 +- .../v2/route => config/route/v3}/Decorator.ts | 2 +- .../route/v3}/DirectResponseAction.ts | 16 +- .../route => config/route/v3}/FilterAction.ts | 2 +- .../envoy/config/route/v3/FilterConfig.ts | 47 + .../route/v3}/HeaderMatcher.ts | 84 +- .../envoy/config/route/v3/HedgePolicy.ts | 76 ++ .../config/route/v3/InternalRedirectPolicy.ts | 72 ++ .../config/route/v3/QueryParameterMatcher.ts | 47 + .../envoy/config/route/v3/RateLimit.ts | 578 +++++++++ .../envoy/config/route/v3/RedirectAction.ts | 222 ++++ .../envoy/config/route/v3/RetryPolicy.ts | 392 +++++++ .../v2/route => config/route/v3}/Route.ts | 107 +- .../route => config/route/v3}/RouteAction.ts | 479 +++++--- .../route/v3}/RouteConfiguration.ts | 89 +- .../route => config/route/v3}/RouteMatch.ts | 128 +- .../route/v3}/ScopedRouteConfiguration.ts | 58 +- .../v2/route => config/route/v3}/Tracing.ts | 26 +- .../generated/envoy/config/route/v3/Vhds.ts | 17 + .../route/v3}/VirtualCluster.ts | 57 +- .../route => config/route/v3}/VirtualHost.ts | 107 +- .../route/v3}/WeightedCluster.ts | 97 +- .../envoy/config/trace/{v2 => v3}/Tracing.ts | 53 +- .../v3}/HttpConnectionManager.ts | 395 ++++--- .../http_connection_manager/v3/HttpFilter.ts | 66 ++ .../v3/LocalReplyConfig.ts | 106 ++ .../http_connection_manager/v3}/Rds.ts | 8 +- .../v3}/RequestIDExtension.ts | 2 +- .../v3/ResponseMapper.ts | 67 ++ .../http_connection_manager/v3/ScopedRds.ts | 17 + .../v3/ScopedRouteConfigurationsList.ts | 17 + .../v3}/ScopedRoutes.ts | 88 +- .../envoy/service/discovery/v3/AdsDummy.ts | 16 + .../v3/AggregatedDiscoveryService.ts | 52 + .../discovery/v3/DeltaDiscoveryRequest.ts | 206 ++++ .../discovery/v3/DeltaDiscoveryResponse.ts | 74 ++ .../service/discovery/v3/DiscoveryRequest.ts | 110 ++ .../service/discovery/v3/DiscoveryResponse.ts | 106 ++ .../envoy/service/discovery/v3/Resource.ts | 118 ++ .../load_stats/v3/LoadReportingService.ts | 108 ++ .../service/load_stats/v3/LoadStatsRequest.ts | 32 + .../load_stats/v3/LoadStatsResponse.ts | 71 ++ .../src/generated/envoy/type/Percent.ts | 2 +- .../envoy/type/matcher/ListStringMatcher.ts | 17 - .../envoy/type/matcher/v3/DoubleMatcher.ts | 35 + .../envoy/type/matcher/v3/ListMatcher.ts | 25 + .../type/matcher/v3/ListStringMatcher.ts | 17 + .../envoy/type/matcher/v3/MetadataMatcher.ts | 65 + .../{ => v3}/RegexMatchAndSubstitute.ts | 8 +- .../type/matcher/{ => v3}/RegexMatcher.ts | 32 +- .../type/matcher/{ => v3}/StringMatcher.ts | 66 +- .../envoy/type/matcher/v3/ValueMatcher.ts | 101 ++ .../envoy/type/metadata/v2/MetadataKind.ts | 98 -- .../type/metadata/{v2 => v3}/MetadataKey.ts | 14 +- .../envoy/type/metadata/v3/MetadataKind.ts | 98 ++ .../type/tracing/{v2 => v3}/CustomTag.ts | 54 +- .../envoy/type/{ => v3}/CodecClientType.ts | 2 +- .../envoy/type/{ => v3}/DoubleRange.ts | 6 +- .../envoy/type/v3/FractionalPercent.ts | 68 ++ .../envoy/type/{ => v3}/Int32Range.ts | 2 +- .../envoy/type/{ => v3}/Int64Range.ts | 2 +- .../src/generated/envoy/type/v3/Percent.ts | 16 + .../envoy/type/v3/SemanticVersion.ts | 24 + .../generated/google/api/CustomHttpPattern.ts | 30 - .../src/generated/google/api/Http.ts | 49 - .../src/generated/google/api/HttpRule.ts | 680 ----------- .../src/generated/google/protobuf/Any.ts | 6 +- .../generated/google/protobuf/DoubleValue.ts | 2 +- .../generated/google/protobuf/FieldOptions.ts | 3 + .../generated/google/protobuf/FloatValue.ts | 2 +- .../google/protobuf/MessageOptions.ts | 3 + .../google/protobuf/UninterpretedOption.ts | 2 +- .../src/generated/google/protobuf/Value.ts | 2 +- .../src/generated/http_connection_manager.ts | 123 +- .../grpc-js-xds/src/generated/listener.ts | 132 ++- packages/grpc-js-xds/src/generated/lrs.ts | 59 + packages/grpc-js-xds/src/generated/route.ts | 58 +- .../annotations/FieldSecurityAnnotation.ts | 32 + .../udpa/annotations/VersioningAnnotation.ts | 20 + .../src/generated/validate/DoubleRules.ts | 14 +- .../src/generated/validate/FloatRules.ts | 14 +- .../src/generated/xds/core/v3/Authority.ts | 16 + .../generated/xds/core/v3/CollectionEntry.ts | 90 ++ .../generated/xds/core/v3/ContextParams.ts | 26 + .../generated/xds/core/v3/ResourceLocator.ts | 220 ++++ 222 files changed, 9580 insertions(+), 5510 deletions(-) delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/UpstreamConnectionOptions.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/Vhds.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CertificateValidationContext.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CommonTlsContext.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/DownstreamTlsContext.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/GenericSecret.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/PrivateKeyProvider.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/SdsSecretConfig.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/Secret.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsCertificate.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsParameters.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsSessionTicketKeys.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/auth/UpstreamTlsContext.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/GrpcProtocolOptions.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/SelfConfigSource.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/listener/Filter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/listener/FilterChain.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/route/HedgePolicy.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/route/QueryParameterMatcher.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/route/RateLimit.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/route/RedirectAction.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/route/RetryPolicy.ts rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/AccessLog.ts (54%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/AndFilter.ts (51%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/ExtensionFilter.ts (64%) rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/GrpcStatusFilter.ts (51%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/NotHealthCheckFilter.ts (81%) rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/OrFilter.ts (50%) rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/ResponseFlagFilter.ts (51%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts rename packages/grpc-js-xds/src/generated/envoy/config/{filter/accesslog/v2 => accesslog/v3}/TraceableFilter.ts (81%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/cluster => config/cluster/v3}/CircuitBreakers.ts (75%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2 => config/cluster/v3}/Cluster.ts (50%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/cluster => config/cluster/v3}/Filter.ts (92%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2 => config/cluster/v3}/LoadBalancingPolicy.ts (80%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/cluster => config/cluster/v3}/OutlierDetection.ts (85%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/TrackClusterStats.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2 => config/cluster/v3}/UpstreamBindConfig.ts (59%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/AggregatedConfigSource.ts (57%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/ApiConfigSource.ts (66%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/ApiVersion.ts (69%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/ConfigSource.ts (70%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/ControlPlane.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/EventServiceConfig.ts (58%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/GrpcService.ts (59%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderMap.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/HealthCheck.ts (67%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/HealthStatus.ts (91%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/Http1ProtocolOptions.ts (57%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/Http2ProtocolOptions.ts (65%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/HttpProtocolOptions.ts (75%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/Pipe.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/RateLimitSettings.ts (94%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeDouble.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeUInt32.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/TcpProtocolOptions.ts (70%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/core => config/core/v3}/UpstreamHttpProtocolOptions.ts (95%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/WatchedDirectory.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2 => config/endpoint/v3}/ClusterLoadAssignment.ts (67%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/endpoint => config/endpoint/v3}/Endpoint.ts (69%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/EndpointLoadMetricStats.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/endpoint => config/endpoint/v3}/LbEndpoint.ts (74%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/endpoint => config/endpoint/v3}/LocalityLbEndpoints.ts (87%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AccessLogFilter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ComparisonFilter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/DurationFilter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/HeaderFilter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/RuntimeFilter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/StatusCodeFilter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/HttpFilter.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRds.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRouteConfigurationsList.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/listener => config/listener/v3}/ActiveRawUdpListenerConfig.ts (56%) rename packages/grpc-js-xds/src/generated/envoy/config/listener/{v2 => v3}/ApiListener.ts (96%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/listener => config/listener/v3}/FilterChainMatch.ts (81%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2 => config/listener/v3}/Listener.ts (66%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/listener => config/listener/v3}/ListenerFilter.ts (58%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/listener => config/listener/v3}/ListenerFilterChainMatchPredicate.ts (66%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/listener => config/listener/v3}/UdpListenerConfig.ts (72%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/CorsPolicy.ts (50%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/Decorator.ts (94%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/DirectResponseAction.ts (52%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/FilterAction.ts (82%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/HeaderMatcher.ts (69%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/Route.ts (58%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/RouteAction.ts (62%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2 => config/route/v3}/RouteConfiguration.ts (63%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/RouteMatch.ts (67%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2 => config/route/v3}/ScopedRouteConfiguration.ts (59%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/Tracing.ts (75%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/VirtualCluster.ts (58%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/VirtualHost.ts (71%) rename packages/grpc-js-xds/src/generated/envoy/{api/v2/route => config/route/v3}/WeightedCluster.ts (64%) rename packages/grpc-js-xds/src/generated/envoy/config/trace/{v2 => v3}/Tracing.ts (54%) rename packages/grpc-js-xds/src/generated/envoy/{config/filter/network/http_connection_manager/v2 => extensions/filters/network/http_connection_manager/v3}/HttpConnectionManager.ts (64%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts rename packages/grpc-js-xds/src/generated/envoy/{config/filter/network/http_connection_manager/v2 => extensions/filters/network/http_connection_manager/v3}/Rds.ts (63%) rename packages/grpc-js-xds/src/generated/envoy/{config/filter/network/http_connection_manager/v2 => extensions/filters/network/http_connection_manager/v3}/RequestIDExtension.ts (78%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList.ts rename packages/grpc-js-xds/src/generated/envoy/{config/filter/network/http_connection_manager/v2 => extensions/filters/network/http_connection_manager/v3}/ScopedRoutes.ts (57%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AdsDummy.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/ListStringMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListStringMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts rename packages/grpc-js-xds/src/generated/envoy/type/matcher/{ => v3}/RegexMatchAndSubstitute.ts (89%) rename packages/grpc-js-xds/src/generated/envoy/type/matcher/{ => v3}/RegexMatcher.ts (57%) rename packages/grpc-js-xds/src/generated/envoy/type/matcher/{ => v3}/StringMatcher.ts (58%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/type/metadata/v2/MetadataKind.ts rename packages/grpc-js-xds/src/generated/envoy/type/metadata/{v2 => v3}/MetadataKey.ts (85%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts rename packages/grpc-js-xds/src/generated/envoy/type/tracing/{v2 => v3}/CustomTag.ts (66%) rename packages/grpc-js-xds/src/generated/envoy/type/{ => v3}/CodecClientType.ts (84%) rename packages/grpc-js-xds/src/generated/envoy/type/{ => v3}/DoubleRange.ts (82%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts rename packages/grpc-js-xds/src/generated/envoy/type/{ => v3}/Int32Range.ts (90%) rename packages/grpc-js-xds/src/generated/envoy/type/{ => v3}/Int64Range.ts (91%) create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/v3/Percent.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/v3/SemanticVersion.ts delete mode 100644 packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts delete mode 100644 packages/grpc-js-xds/src/generated/google/api/Http.ts delete mode 100644 packages/grpc-js-xds/src/generated/google/api/HttpRule.ts create mode 100644 packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts create mode 100644 packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts diff --git a/packages/grpc-js-xds/deps/envoy-api b/packages/grpc-js-xds/deps/envoy-api index 50cef8fca..18b54850c 160000 --- a/packages/grpc-js-xds/deps/envoy-api +++ b/packages/grpc-js-xds/deps/envoy-api @@ -1 +1 @@ -Subproject commit 50cef8fcab37ba59a61068934d08a3f4c28a681f +Subproject commit 18b54850c9b7ba29a4ab67cbd7ed7eab7b0bbdb2 diff --git a/packages/grpc-js-xds/deps/udpa b/packages/grpc-js-xds/deps/udpa index 3b31d022a..cc1b757b3 160000 --- a/packages/grpc-js-xds/deps/udpa +++ b/packages/grpc-js-xds/deps/udpa @@ -1 +1 @@ -Subproject commit 3b31d022a144b334eb2224838e4d6952ab5253aa +Subproject commit cc1b757b3eddccaaaf0743cbb107742bb7e3ee4f diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 3f90c7e97..f69c8bf49 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 --json --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js 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", + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", "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" }, "repository": { diff --git a/packages/grpc-js-xds/src/generated/ads.ts b/packages/grpc-js-xds/src/generated/ads.ts index 0eacd1e34..665374958 100644 --- a/packages/grpc-js-xds/src/generated/ads.ts +++ b/packages/grpc-js-xds/src/generated/ads.ts @@ -2,6 +2,7 @@ import type * as grpc from '@grpc/grpc-js'; import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v2_AggregatedDiscoveryServiceClient } from './envoy/service/discovery/v2/AggregatedDiscoveryService'; +import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v3_AggregatedDiscoveryServiceClient } from './envoy/service/discovery/v3/AggregatedDiscoveryService'; type SubtypeConstructor any, Subtype> = { new(...args: ConstructorParameters): Subtype; @@ -50,6 +51,45 @@ export interface ProtoGrpcType { } } } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + } service: { discovery: { v2: { @@ -64,12 +104,34 @@ export interface ProtoGrpcType { */ AggregatedDiscoveryService: SubtypeConstructor & { service: ServiceDefinition } } + v3: { + AdsDummy: MessageTypeDefinition + /** + * See https://github.com/lyft/envoy-api#apis for a description of the role of + * ADS and how it is intended to be used by a management server. ADS requests + * have the same structure as their singleton xDS counterparts, but can + * multiplex many resource types on a single stream. The type_url in the + * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover + * the multiplexed singleton APIs at the Envoy instance and management server. + */ + AggregatedDiscoveryService: SubtypeConstructor & { service: ServiceDefinition } + DeltaDiscoveryRequest: MessageTypeDefinition + DeltaDiscoveryResponse: MessageTypeDefinition + DiscoveryRequest: MessageTypeDefinition + DiscoveryResponse: MessageTypeDefinition + Resource: MessageTypeDefinition + } } } type: { FractionalPercent: MessageTypeDefinition Percent: MessageTypeDefinition SemanticVersion: MessageTypeDefinition + v3: { + FractionalPercent: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } } } google: { @@ -122,6 +184,7 @@ export interface ProtoGrpcType { MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition } } validate: { diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index b7005a3f0..5e4683580 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -10,32 +10,22 @@ export interface ProtoGrpcType { envoy: { annotations: { } - api: { - v2: { - Cluster: MessageTypeDefinition - ClusterLoadAssignment: MessageTypeDefinition - LoadBalancingPolicy: MessageTypeDefinition - UpstreamBindConfig: MessageTypeDefinition - UpstreamConnectionOptions: MessageTypeDefinition - auth: { - CertificateValidationContext: MessageTypeDefinition - CommonTlsContext: MessageTypeDefinition - DownstreamTlsContext: MessageTypeDefinition - GenericSecret: MessageTypeDefinition - PrivateKeyProvider: MessageTypeDefinition - SdsSecretConfig: MessageTypeDefinition - Secret: MessageTypeDefinition - TlsCertificate: MessageTypeDefinition - TlsParameters: MessageTypeDefinition - TlsSessionTicketKeys: MessageTypeDefinition - UpstreamTlsContext: MessageTypeDefinition - } - cluster: { + config: { + cluster: { + v3: { CircuitBreakers: MessageTypeDefinition + Cluster: MessageTypeDefinition + ClusterCollection: MessageTypeDefinition Filter: MessageTypeDefinition + LoadBalancingPolicy: MessageTypeDefinition OutlierDetection: MessageTypeDefinition + TrackClusterStats: MessageTypeDefinition + UpstreamBindConfig: MessageTypeDefinition + UpstreamConnectionOptions: MessageTypeDefinition } - core: { + } + core: { + v3: { Address: MessageTypeDefinition AggregatedConfigSource: MessageTypeDefinition ApiConfigSource: MessageTypeDefinition @@ -48,8 +38,10 @@ export interface ProtoGrpcType { ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition EventServiceConfig: MessageTypeDefinition Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition GrpcProtocolOptions: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition @@ -59,8 +51,10 @@ export interface ProtoGrpcType { HealthStatus: EnumTypeDefinition Http1ProtocolOptions: MessageTypeDefinition Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition HttpProtocolOptions: MessageTypeDefinition HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition @@ -73,6 +67,7 @@ export interface ProtoGrpcType { RuntimeDouble: MessageTypeDefinition RuntimeFeatureFlag: MessageTypeDefinition RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition @@ -81,9 +76,14 @@ export interface ProtoGrpcType { TcpProtocolOptions: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition } - endpoint: { + } + endpoint: { + v3: { + ClusterLoadAssignment: MessageTypeDefinition Endpoint: MessageTypeDefinition LbEndpoint: MessageTypeDefinition LocalityLbEndpoints: MessageTypeDefinition @@ -91,27 +91,26 @@ export interface ProtoGrpcType { } } type: { - CodecClientType: EnumTypeDefinition - DoubleRange: MessageTypeDefinition - FractionalPercent: MessageTypeDefinition - Int32Range: MessageTypeDefinition - Int64Range: MessageTypeDefinition - Percent: MessageTypeDefinition - SemanticVersion: MessageTypeDefinition matcher: { - ListStringMatcher: MessageTypeDefinition - RegexMatchAndSubstitute: MessageTypeDefinition - RegexMatcher: MessageTypeDefinition - StringMatcher: MessageTypeDefinition + v3: { + ListStringMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } + } + v3: { + CodecClientType: EnumTypeDefinition + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition } } } google: { - api: { - CustomHttpPattern: MessageTypeDefinition - Http: MessageTypeDefinition - HttpRule: MessageTypeDefinition - } protobuf: { Any: MessageTypeDefinition BoolValue: MessageTypeDefinition @@ -155,10 +154,12 @@ export interface ProtoGrpcType { udpa: { annotations: { FieldMigrateAnnotation: MessageTypeDefinition + FieldSecurityAnnotation: MessageTypeDefinition FileMigrateAnnotation: MessageTypeDefinition MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition } } validate: { @@ -187,5 +188,15 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + core: { + v3: { + Authority: MessageTypeDefinition + CollectionEntry: MessageTypeDefinition + ContextParams: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/endpoint.ts b/packages/grpc-js-xds/src/generated/endpoint.ts index 33d5872ef..dda6a78da 100644 --- a/packages/grpc-js-xds/src/generated/endpoint.ts +++ b/packages/grpc-js-xds/src/generated/endpoint.ts @@ -8,12 +8,9 @@ type SubtypeConstructor any, Subtype> export interface ProtoGrpcType { envoy: { - annotations: { - } - api: { - v2: { - ClusterLoadAssignment: MessageTypeDefinition - core: { + config: { + core: { + v3: { Address: MessageTypeDefinition AsyncDataSource: MessageTypeDefinition BackoffStrategy: MessageTypeDefinition @@ -22,6 +19,7 @@ export interface ProtoGrpcType { CidrRange: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition EventServiceConfig: MessageTypeDefinition Extension: MessageTypeDefinition GrpcService: MessageTypeDefinition @@ -42,14 +40,19 @@ export interface ProtoGrpcType { RuntimeDouble: MessageTypeDefinition RuntimeFeatureFlag: MessageTypeDefinition RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition } - endpoint: { + } + endpoint: { + v3: { + ClusterLoadAssignment: MessageTypeDefinition Endpoint: MessageTypeDefinition LbEndpoint: MessageTypeDefinition LocalityLbEndpoints: MessageTypeDefinition @@ -57,27 +60,26 @@ export interface ProtoGrpcType { } } type: { - CodecClientType: EnumTypeDefinition - DoubleRange: MessageTypeDefinition - FractionalPercent: MessageTypeDefinition - Int32Range: MessageTypeDefinition - Int64Range: MessageTypeDefinition - Percent: MessageTypeDefinition - SemanticVersion: MessageTypeDefinition matcher: { - ListStringMatcher: MessageTypeDefinition - RegexMatchAndSubstitute: MessageTypeDefinition - RegexMatcher: MessageTypeDefinition - StringMatcher: MessageTypeDefinition + v3: { + ListStringMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } + } + v3: { + CodecClientType: EnumTypeDefinition + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition } } } google: { - api: { - CustomHttpPattern: MessageTypeDefinition - Http: MessageTypeDefinition - HttpRule: MessageTypeDefinition - } protobuf: { Any: MessageTypeDefinition BoolValue: MessageTypeDefinition @@ -125,6 +127,7 @@ export interface ProtoGrpcType { MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition } } validate: { diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/UpstreamConnectionOptions.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/UpstreamConnectionOptions.ts deleted file mode 100644 index e46a2cd06..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/UpstreamConnectionOptions.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto - -import type { TcpKeepalive as _envoy_api_v2_core_TcpKeepalive, TcpKeepalive__Output as _envoy_api_v2_core_TcpKeepalive__Output } from '../../../envoy/api/v2/core/TcpKeepalive'; - -export interface UpstreamConnectionOptions { - /** - * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. - */ - 'tcp_keepalive'?: (_envoy_api_v2_core_TcpKeepalive); -} - -export interface UpstreamConnectionOptions__Output { - /** - * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. - */ - 'tcp_keepalive'?: (_envoy_api_v2_core_TcpKeepalive__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/Vhds.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/Vhds.ts deleted file mode 100644 index f2ec45d2d..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/Vhds.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/route.proto - -import type { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../envoy/api/v2/core/ConfigSource'; - -export interface Vhds { - /** - * Configuration source specifier for VHDS. - */ - 'config_source'?: (_envoy_api_v2_core_ConfigSource); -} - -export interface Vhds__Output { - /** - * Configuration source specifier for VHDS. - */ - 'config_source'?: (_envoy_api_v2_core_ConfigSource__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CertificateValidationContext.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CertificateValidationContext.ts deleted file mode 100644 index 0272f3b5c..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CertificateValidationContext.ts +++ /dev/null @@ -1,315 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/common.proto - -import type { DataSource as _envoy_api_v2_core_DataSource, DataSource__Output as _envoy_api_v2_core_DataSource__Output } from '../../../../envoy/api/v2/core/DataSource'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { StringMatcher as _envoy_type_matcher_StringMatcher, StringMatcher__Output as _envoy_type_matcher_StringMatcher__Output } from '../../../../envoy/type/matcher/StringMatcher'; - -// Original file: deps/envoy-api/envoy/api/v2/auth/common.proto - -/** - * Peer certificate verification mode. - */ -export enum _envoy_api_v2_auth_CertificateValidationContext_TrustChainVerification { - /** - * Perform default certificate verification (e.g., against CA / verification lists) - */ - VERIFY_TRUST_CHAIN = 0, - /** - * Connections where the certificate fails verification will be permitted. - * For HTTP connections, the result of certificate verification can be used in route matching. ( - * see :ref:`validated ` ). - */ - ACCEPT_UNTRUSTED = 1, -} - -/** - * [#next-free-field: 11] - */ -export interface CertificateValidationContext { - /** - * TLS certificate data containing certificate authority certificates to use in verifying - * a presented peer certificate (e.g. server certificate for clusters or client certificate - * for listeners). If not specified and a peer certificate is presented it will not be - * verified. By default, a client certificate is optional, unless one of the additional - * options (:ref:`require_client_certificate - * `, - * :ref:`verify_certificate_spki - * `, - * :ref:`verify_certificate_hash - * `, or - * :ref:`match_subject_alt_names - * `) is also - * specified. - * - * It can optionally contain certificate revocation lists, in which case Envoy will verify - * that the presented peer certificate has not been revoked by one of the included CRLs. - * - * See :ref:`the TLS overview ` for a list of common - * system CA locations. - */ - 'trusted_ca'?: (_envoy_api_v2_core_DataSource); - /** - * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that - * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. - * - * A hex-encoded SHA-256 of the certificate can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 - * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a - * - * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 - * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A - * - * Both of those formats are acceptable. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - */ - 'verify_certificate_hash'?: (string)[]; - /** - * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the - * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate - * matches one of the specified values. - * - * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -pubkey - * | openssl pkey -pubin -outform DER - * | openssl dgst -sha256 -binary - * | openssl enc -base64 - * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= - * - * This is the format used in HTTP Public Key Pinning. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - * - * .. attention:: - * - * This option is preferred over :ref:`verify_certificate_hash - * `, - * because SPKI is tied to a private key, so it doesn't change when the certificate - * is renewed using the same private key. - */ - 'verify_certificate_spki'?: (string)[]; - /** - * An optional list of Subject Alternative Names. If specified, Envoy will verify that the - * Subject Alternative Name of the presented certificate matches one of the specified values. - * - * .. attention:: - * - * Subject Alternative Names are easily spoofable and verifying only them is insecure, - * therefore this option must be used together with :ref:`trusted_ca - * `. - */ - 'verify_subject_alt_name'?: (string)[]; - /** - * [#not-implemented-hide:] Must present a signed time-stamped OCSP response. - */ - 'require_ocsp_staple'?: (_google_protobuf_BoolValue); - /** - * [#not-implemented-hide:] Must present signed certificate time-stamp. - */ - 'require_signed_certificate_timestamp'?: (_google_protobuf_BoolValue); - /** - * An optional `certificate revocation list - * `_ - * (in PEM format). If specified, Envoy will verify that the presented peer - * certificate has not been revoked by this CRL. If this DataSource contains - * multiple CRLs, all of them will be used. - */ - 'crl'?: (_envoy_api_v2_core_DataSource); - /** - * If specified, Envoy will not reject expired certificates. - */ - 'allow_expired_certificate'?: (boolean); - /** - * An optional list of Subject Alternative name matchers. Envoy will verify that the - * Subject Alternative Name of the presented certificate matches one of the specified matches. - * - * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - * configured with exact match type in the :ref:`string matcher `. - * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", - * it should be configured as shown below. - * - * .. code-block:: yaml - * - * match_subject_alt_names: - * exact: "api.example.com" - * - * .. attention:: - * - * Subject Alternative Names are easily spoofable and verifying only them is insecure, - * therefore this option must be used together with :ref:`trusted_ca - * `. - */ - 'match_subject_alt_names'?: (_envoy_type_matcher_StringMatcher)[]; - /** - * Certificate trust chain verification mode. - */ - 'trust_chain_verification'?: (_envoy_api_v2_auth_CertificateValidationContext_TrustChainVerification | keyof typeof _envoy_api_v2_auth_CertificateValidationContext_TrustChainVerification); -} - -/** - * [#next-free-field: 11] - */ -export interface CertificateValidationContext__Output { - /** - * TLS certificate data containing certificate authority certificates to use in verifying - * a presented peer certificate (e.g. server certificate for clusters or client certificate - * for listeners). If not specified and a peer certificate is presented it will not be - * verified. By default, a client certificate is optional, unless one of the additional - * options (:ref:`require_client_certificate - * `, - * :ref:`verify_certificate_spki - * `, - * :ref:`verify_certificate_hash - * `, or - * :ref:`match_subject_alt_names - * `) is also - * specified. - * - * It can optionally contain certificate revocation lists, in which case Envoy will verify - * that the presented peer certificate has not been revoked by one of the included CRLs. - * - * See :ref:`the TLS overview ` for a list of common - * system CA locations. - */ - 'trusted_ca'?: (_envoy_api_v2_core_DataSource__Output); - /** - * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that - * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. - * - * A hex-encoded SHA-256 of the certificate can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 - * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a - * - * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 - * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A - * - * Both of those formats are acceptable. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - */ - 'verify_certificate_hash': (string)[]; - /** - * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the - * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate - * matches one of the specified values. - * - * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -pubkey - * | openssl pkey -pubin -outform DER - * | openssl dgst -sha256 -binary - * | openssl enc -base64 - * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= - * - * This is the format used in HTTP Public Key Pinning. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - * - * .. attention:: - * - * This option is preferred over :ref:`verify_certificate_hash - * `, - * because SPKI is tied to a private key, so it doesn't change when the certificate - * is renewed using the same private key. - */ - 'verify_certificate_spki': (string)[]; - /** - * An optional list of Subject Alternative Names. If specified, Envoy will verify that the - * Subject Alternative Name of the presented certificate matches one of the specified values. - * - * .. attention:: - * - * Subject Alternative Names are easily spoofable and verifying only them is insecure, - * therefore this option must be used together with :ref:`trusted_ca - * `. - */ - 'verify_subject_alt_name': (string)[]; - /** - * [#not-implemented-hide:] Must present a signed time-stamped OCSP response. - */ - 'require_ocsp_staple'?: (_google_protobuf_BoolValue__Output); - /** - * [#not-implemented-hide:] Must present signed certificate time-stamp. - */ - 'require_signed_certificate_timestamp'?: (_google_protobuf_BoolValue__Output); - /** - * An optional `certificate revocation list - * `_ - * (in PEM format). If specified, Envoy will verify that the presented peer - * certificate has not been revoked by this CRL. If this DataSource contains - * multiple CRLs, all of them will be used. - */ - 'crl'?: (_envoy_api_v2_core_DataSource__Output); - /** - * If specified, Envoy will not reject expired certificates. - */ - 'allow_expired_certificate': (boolean); - /** - * An optional list of Subject Alternative name matchers. Envoy will verify that the - * Subject Alternative Name of the presented certificate matches one of the specified matches. - * - * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - * configured with exact match type in the :ref:`string matcher `. - * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", - * it should be configured as shown below. - * - * .. code-block:: yaml - * - * match_subject_alt_names: - * exact: "api.example.com" - * - * .. attention:: - * - * Subject Alternative Names are easily spoofable and verifying only them is insecure, - * therefore this option must be used together with :ref:`trusted_ca - * `. - */ - 'match_subject_alt_names': (_envoy_type_matcher_StringMatcher__Output)[]; - /** - * Certificate trust chain verification mode. - */ - 'trust_chain_verification': (keyof typeof _envoy_api_v2_auth_CertificateValidationContext_TrustChainVerification); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CommonTlsContext.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CommonTlsContext.ts deleted file mode 100644 index 784e3d2ce..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/CommonTlsContext.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/tls.proto - -import type { TlsParameters as _envoy_api_v2_auth_TlsParameters, TlsParameters__Output as _envoy_api_v2_auth_TlsParameters__Output } from '../../../../envoy/api/v2/auth/TlsParameters'; -import type { TlsCertificate as _envoy_api_v2_auth_TlsCertificate, TlsCertificate__Output as _envoy_api_v2_auth_TlsCertificate__Output } from '../../../../envoy/api/v2/auth/TlsCertificate'; -import type { CertificateValidationContext as _envoy_api_v2_auth_CertificateValidationContext, CertificateValidationContext__Output as _envoy_api_v2_auth_CertificateValidationContext__Output } from '../../../../envoy/api/v2/auth/CertificateValidationContext'; -import type { SdsSecretConfig as _envoy_api_v2_auth_SdsSecretConfig, SdsSecretConfig__Output as _envoy_api_v2_auth_SdsSecretConfig__Output } from '../../../../envoy/api/v2/auth/SdsSecretConfig'; - -export interface _envoy_api_v2_auth_CommonTlsContext_CombinedCertificateValidationContext { - /** - * How to validate peer certificates. - */ - 'default_validation_context'?: (_envoy_api_v2_auth_CertificateValidationContext); - /** - * Config for fetching validation context via SDS API. - */ - 'validation_context_sds_secret_config'?: (_envoy_api_v2_auth_SdsSecretConfig); -} - -export interface _envoy_api_v2_auth_CommonTlsContext_CombinedCertificateValidationContext__Output { - /** - * How to validate peer certificates. - */ - 'default_validation_context'?: (_envoy_api_v2_auth_CertificateValidationContext__Output); - /** - * Config for fetching validation context via SDS API. - */ - 'validation_context_sds_secret_config'?: (_envoy_api_v2_auth_SdsSecretConfig__Output); -} - -/** - * TLS context shared by both client and server TLS contexts. - * [#next-free-field: 9] - */ -export interface CommonTlsContext { - /** - * TLS protocol versions, cipher suites etc. - */ - 'tls_params'?: (_envoy_api_v2_auth_TlsParameters); - /** - * :ref:`Multiple TLS certificates ` can be associated with the - * same context to allow both RSA and ECDSA certificates. - * - * Only a single TLS certificate is supported in client contexts. In server contexts, the first - * RSA certificate is used for clients that only support RSA and the first ECDSA certificate is - * used for clients that support ECDSA. - */ - 'tls_certificates'?: (_envoy_api_v2_auth_TlsCertificate)[]; - /** - * How to validate peer certificates. - */ - 'validation_context'?: (_envoy_api_v2_auth_CertificateValidationContext); - /** - * Supplies the list of ALPN protocols that the listener should expose. In - * practice this is likely to be set to one of two values (see the - * :ref:`codec_type - * ` - * parameter in the HTTP connection manager for more information): - * - * * "h2,http/1.1" If the listener is going to support both HTTP/2 and HTTP/1.1. - * * "http/1.1" If the listener is only going to support HTTP/1.1. - * - * There is no default for this parameter. If empty, Envoy will not expose ALPN. - */ - 'alpn_protocols'?: (string)[]; - /** - * Configs for fetching TLS certificates via SDS API. - */ - 'tls_certificate_sds_secret_configs'?: (_envoy_api_v2_auth_SdsSecretConfig)[]; - /** - * Config for fetching validation context via SDS API. - */ - 'validation_context_sds_secret_config'?: (_envoy_api_v2_auth_SdsSecretConfig); - /** - * Combined certificate validation context holds a default CertificateValidationContext - * and SDS config. When SDS server returns dynamic CertificateValidationContext, both dynamic - * and default CertificateValidationContext are merged into a new CertificateValidationContext - * for validation. This merge is done by Message::MergeFrom(), so dynamic - * CertificateValidationContext overwrites singular fields in default - * CertificateValidationContext, and concatenates repeated fields to default - * CertificateValidationContext, and logical OR is applied to boolean fields. - */ - 'combined_validation_context'?: (_envoy_api_v2_auth_CommonTlsContext_CombinedCertificateValidationContext); - 'validation_context_type'?: "validation_context"|"validation_context_sds_secret_config"|"combined_validation_context"; -} - -/** - * TLS context shared by both client and server TLS contexts. - * [#next-free-field: 9] - */ -export interface CommonTlsContext__Output { - /** - * TLS protocol versions, cipher suites etc. - */ - 'tls_params'?: (_envoy_api_v2_auth_TlsParameters__Output); - /** - * :ref:`Multiple TLS certificates ` can be associated with the - * same context to allow both RSA and ECDSA certificates. - * - * Only a single TLS certificate is supported in client contexts. In server contexts, the first - * RSA certificate is used for clients that only support RSA and the first ECDSA certificate is - * used for clients that support ECDSA. - */ - 'tls_certificates': (_envoy_api_v2_auth_TlsCertificate__Output)[]; - /** - * How to validate peer certificates. - */ - 'validation_context'?: (_envoy_api_v2_auth_CertificateValidationContext__Output); - /** - * Supplies the list of ALPN protocols that the listener should expose. In - * practice this is likely to be set to one of two values (see the - * :ref:`codec_type - * ` - * parameter in the HTTP connection manager for more information): - * - * * "h2,http/1.1" If the listener is going to support both HTTP/2 and HTTP/1.1. - * * "http/1.1" If the listener is only going to support HTTP/1.1. - * - * There is no default for this parameter. If empty, Envoy will not expose ALPN. - */ - 'alpn_protocols': (string)[]; - /** - * Configs for fetching TLS certificates via SDS API. - */ - 'tls_certificate_sds_secret_configs': (_envoy_api_v2_auth_SdsSecretConfig__Output)[]; - /** - * Config for fetching validation context via SDS API. - */ - 'validation_context_sds_secret_config'?: (_envoy_api_v2_auth_SdsSecretConfig__Output); - /** - * Combined certificate validation context holds a default CertificateValidationContext - * and SDS config. When SDS server returns dynamic CertificateValidationContext, both dynamic - * and default CertificateValidationContext are merged into a new CertificateValidationContext - * for validation. This merge is done by Message::MergeFrom(), so dynamic - * CertificateValidationContext overwrites singular fields in default - * CertificateValidationContext, and concatenates repeated fields to default - * CertificateValidationContext, and logical OR is applied to boolean fields. - */ - 'combined_validation_context'?: (_envoy_api_v2_auth_CommonTlsContext_CombinedCertificateValidationContext__Output); - 'validation_context_type': "validation_context"|"validation_context_sds_secret_config"|"combined_validation_context"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/DownstreamTlsContext.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/DownstreamTlsContext.ts deleted file mode 100644 index ef9a6f9a4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/DownstreamTlsContext.ts +++ /dev/null @@ -1,101 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/tls.proto - -import type { CommonTlsContext as _envoy_api_v2_auth_CommonTlsContext, CommonTlsContext__Output as _envoy_api_v2_auth_CommonTlsContext__Output } from '../../../../envoy/api/v2/auth/CommonTlsContext'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { TlsSessionTicketKeys as _envoy_api_v2_auth_TlsSessionTicketKeys, TlsSessionTicketKeys__Output as _envoy_api_v2_auth_TlsSessionTicketKeys__Output } from '../../../../envoy/api/v2/auth/TlsSessionTicketKeys'; -import type { SdsSecretConfig as _envoy_api_v2_auth_SdsSecretConfig, SdsSecretConfig__Output as _envoy_api_v2_auth_SdsSecretConfig__Output } from '../../../../envoy/api/v2/auth/SdsSecretConfig'; -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; - -/** - * [#next-free-field: 8] - */ -export interface DownstreamTlsContext { - /** - * Common TLS context settings. - */ - 'common_tls_context'?: (_envoy_api_v2_auth_CommonTlsContext); - /** - * If specified, Envoy will reject connections without a valid client - * certificate. - */ - 'require_client_certificate'?: (_google_protobuf_BoolValue); - /** - * If specified, Envoy will reject connections without a valid and matching SNI. - * [#not-implemented-hide:] - */ - 'require_sni'?: (_google_protobuf_BoolValue); - /** - * TLS session ticket key settings. - */ - 'session_ticket_keys'?: (_envoy_api_v2_auth_TlsSessionTicketKeys); - /** - * Config for fetching TLS session ticket keys via SDS API. - */ - 'session_ticket_keys_sds_secret_config'?: (_envoy_api_v2_auth_SdsSecretConfig); - /** - * If specified, session_timeout will change maximum lifetime (in seconds) of TLS session - * Currently this value is used as a hint to `TLS session ticket lifetime (for TLSv1.2) - * ` - * only seconds could be specified (fractional seconds are going to be ignored). - */ - 'session_timeout'?: (_google_protobuf_Duration); - /** - * Config for controlling stateless TLS session resumption: setting this to true will cause the TLS - * server to not issue TLS session tickets for the purposes of stateless TLS session resumption. - * If set to false, the TLS server will issue TLS session tickets and encrypt/decrypt them using - * the keys specified through either :ref:`session_ticket_keys ` - * or :ref:`session_ticket_keys_sds_secret_config `. - * If this config is set to false and no keys are explicitly configured, the TLS server will issue - * TLS session tickets and encrypt/decrypt them using an internally-generated and managed key, with the - * implication that sessions cannot be resumed across hot restarts or on different hosts. - */ - 'disable_stateless_session_resumption'?: (boolean); - 'session_ticket_keys_type'?: "session_ticket_keys"|"session_ticket_keys_sds_secret_config"|"disable_stateless_session_resumption"; -} - -/** - * [#next-free-field: 8] - */ -export interface DownstreamTlsContext__Output { - /** - * Common TLS context settings. - */ - 'common_tls_context'?: (_envoy_api_v2_auth_CommonTlsContext__Output); - /** - * If specified, Envoy will reject connections without a valid client - * certificate. - */ - 'require_client_certificate'?: (_google_protobuf_BoolValue__Output); - /** - * If specified, Envoy will reject connections without a valid and matching SNI. - * [#not-implemented-hide:] - */ - 'require_sni'?: (_google_protobuf_BoolValue__Output); - /** - * TLS session ticket key settings. - */ - 'session_ticket_keys'?: (_envoy_api_v2_auth_TlsSessionTicketKeys__Output); - /** - * Config for fetching TLS session ticket keys via SDS API. - */ - 'session_ticket_keys_sds_secret_config'?: (_envoy_api_v2_auth_SdsSecretConfig__Output); - /** - * If specified, session_timeout will change maximum lifetime (in seconds) of TLS session - * Currently this value is used as a hint to `TLS session ticket lifetime (for TLSv1.2) - * ` - * only seconds could be specified (fractional seconds are going to be ignored). - */ - 'session_timeout'?: (_google_protobuf_Duration__Output); - /** - * Config for controlling stateless TLS session resumption: setting this to true will cause the TLS - * server to not issue TLS session tickets for the purposes of stateless TLS session resumption. - * If set to false, the TLS server will issue TLS session tickets and encrypt/decrypt them using - * the keys specified through either :ref:`session_ticket_keys ` - * or :ref:`session_ticket_keys_sds_secret_config `. - * If this config is set to false and no keys are explicitly configured, the TLS server will issue - * TLS session tickets and encrypt/decrypt them using an internally-generated and managed key, with the - * implication that sessions cannot be resumed across hot restarts or on different hosts. - */ - 'disable_stateless_session_resumption'?: (boolean); - 'session_ticket_keys_type': "session_ticket_keys"|"session_ticket_keys_sds_secret_config"|"disable_stateless_session_resumption"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/GenericSecret.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/GenericSecret.ts deleted file mode 100644 index d7b712525..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/GenericSecret.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/secret.proto - -import type { DataSource as _envoy_api_v2_core_DataSource, DataSource__Output as _envoy_api_v2_core_DataSource__Output } from '../../../../envoy/api/v2/core/DataSource'; - -export interface GenericSecret { - /** - * Secret of generic type and is available to filters. - */ - 'secret'?: (_envoy_api_v2_core_DataSource); -} - -export interface GenericSecret__Output { - /** - * Secret of generic type and is available to filters. - */ - 'secret'?: (_envoy_api_v2_core_DataSource__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/PrivateKeyProvider.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/PrivateKeyProvider.ts deleted file mode 100644 index 415448053..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/PrivateKeyProvider.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/common.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; - -/** - * BoringSSL private key method configuration. The private key methods are used for external - * (potentially asynchronous) signing and decryption operations. Some use cases for private key - * methods would be TPM support and TLS acceleration. - */ -export interface PrivateKeyProvider { - /** - * Private key method provider name. The name must match a - * supported private key method provider type. - */ - 'provider_name'?: (string); - 'config'?: (_google_protobuf_Struct); - 'typed_config'?: (_google_protobuf_Any); - /** - * Private key method provider specific configuration. - */ - 'config_type'?: "config"|"typed_config"; -} - -/** - * BoringSSL private key method configuration. The private key methods are used for external - * (potentially asynchronous) signing and decryption operations. Some use cases for private key - * methods would be TPM support and TLS acceleration. - */ -export interface PrivateKeyProvider__Output { - /** - * Private key method provider name. The name must match a - * supported private key method provider type. - */ - 'provider_name': (string); - 'config'?: (_google_protobuf_Struct__Output); - 'typed_config'?: (_google_protobuf_Any__Output); - /** - * Private key method provider specific configuration. - */ - 'config_type': "config"|"typed_config"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/SdsSecretConfig.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/SdsSecretConfig.ts deleted file mode 100644 index 888059332..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/SdsSecretConfig.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/secret.proto - -import type { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../../envoy/api/v2/core/ConfigSource'; - -export interface SdsSecretConfig { - /** - * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. - * When both name and config are specified, then secret can be fetched and/or reloaded via - * SDS. When only name is specified, then secret will be loaded from static resources. - */ - 'name'?: (string); - 'sds_config'?: (_envoy_api_v2_core_ConfigSource); -} - -export interface SdsSecretConfig__Output { - /** - * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. - * When both name and config are specified, then secret can be fetched and/or reloaded via - * SDS. When only name is specified, then secret will be loaded from static resources. - */ - 'name': (string); - 'sds_config'?: (_envoy_api_v2_core_ConfigSource__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/Secret.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/Secret.ts deleted file mode 100644 index 0768daed8..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/Secret.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/secret.proto - -import type { TlsCertificate as _envoy_api_v2_auth_TlsCertificate, TlsCertificate__Output as _envoy_api_v2_auth_TlsCertificate__Output } from '../../../../envoy/api/v2/auth/TlsCertificate'; -import type { TlsSessionTicketKeys as _envoy_api_v2_auth_TlsSessionTicketKeys, TlsSessionTicketKeys__Output as _envoy_api_v2_auth_TlsSessionTicketKeys__Output } from '../../../../envoy/api/v2/auth/TlsSessionTicketKeys'; -import type { CertificateValidationContext as _envoy_api_v2_auth_CertificateValidationContext, CertificateValidationContext__Output as _envoy_api_v2_auth_CertificateValidationContext__Output } from '../../../../envoy/api/v2/auth/CertificateValidationContext'; -import type { GenericSecret as _envoy_api_v2_auth_GenericSecret, GenericSecret__Output as _envoy_api_v2_auth_GenericSecret__Output } from '../../../../envoy/api/v2/auth/GenericSecret'; - -/** - * [#next-free-field: 6] - */ -export interface Secret { - /** - * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. - */ - 'name'?: (string); - 'tls_certificate'?: (_envoy_api_v2_auth_TlsCertificate); - 'session_ticket_keys'?: (_envoy_api_v2_auth_TlsSessionTicketKeys); - 'validation_context'?: (_envoy_api_v2_auth_CertificateValidationContext); - 'generic_secret'?: (_envoy_api_v2_auth_GenericSecret); - 'type'?: "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; -} - -/** - * [#next-free-field: 6] - */ -export interface Secret__Output { - /** - * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. - */ - 'name': (string); - 'tls_certificate'?: (_envoy_api_v2_auth_TlsCertificate__Output); - 'session_ticket_keys'?: (_envoy_api_v2_auth_TlsSessionTicketKeys__Output); - 'validation_context'?: (_envoy_api_v2_auth_CertificateValidationContext__Output); - 'generic_secret'?: (_envoy_api_v2_auth_GenericSecret__Output); - 'type': "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsCertificate.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsCertificate.ts deleted file mode 100644 index dd7efcf21..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsCertificate.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/common.proto - -import type { DataSource as _envoy_api_v2_core_DataSource, DataSource__Output as _envoy_api_v2_core_DataSource__Output } from '../../../../envoy/api/v2/core/DataSource'; -import type { PrivateKeyProvider as _envoy_api_v2_auth_PrivateKeyProvider, PrivateKeyProvider__Output as _envoy_api_v2_auth_PrivateKeyProvider__Output } from '../../../../envoy/api/v2/auth/PrivateKeyProvider'; - -/** - * [#next-free-field: 7] - */ -export interface TlsCertificate { - /** - * The TLS certificate chain. - */ - 'certificate_chain'?: (_envoy_api_v2_core_DataSource); - /** - * The TLS private key. - */ - 'private_key'?: (_envoy_api_v2_core_DataSource); - /** - * The password to decrypt the TLS private key. If this field is not set, it is assumed that the - * TLS private key is not password encrypted. - */ - 'password'?: (_envoy_api_v2_core_DataSource); - /** - * [#not-implemented-hide:] - */ - 'ocsp_staple'?: (_envoy_api_v2_core_DataSource); - /** - * [#not-implemented-hide:] - */ - 'signed_certificate_timestamp'?: (_envoy_api_v2_core_DataSource)[]; - /** - * BoringSSL private key method provider. This is an alternative to :ref:`private_key - * ` field. This can't be - * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - * ` and - * :ref:`private_key_provider - * ` fields will result in an - * error. - */ - 'private_key_provider'?: (_envoy_api_v2_auth_PrivateKeyProvider); -} - -/** - * [#next-free-field: 7] - */ -export interface TlsCertificate__Output { - /** - * The TLS certificate chain. - */ - 'certificate_chain'?: (_envoy_api_v2_core_DataSource__Output); - /** - * The TLS private key. - */ - 'private_key'?: (_envoy_api_v2_core_DataSource__Output); - /** - * The password to decrypt the TLS private key. If this field is not set, it is assumed that the - * TLS private key is not password encrypted. - */ - 'password'?: (_envoy_api_v2_core_DataSource__Output); - /** - * [#not-implemented-hide:] - */ - 'ocsp_staple'?: (_envoy_api_v2_core_DataSource__Output); - /** - * [#not-implemented-hide:] - */ - 'signed_certificate_timestamp': (_envoy_api_v2_core_DataSource__Output)[]; - /** - * BoringSSL private key method provider. This is an alternative to :ref:`private_key - * ` field. This can't be - * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - * ` and - * :ref:`private_key_provider - * ` fields will result in an - * error. - */ - 'private_key_provider'?: (_envoy_api_v2_auth_PrivateKeyProvider__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsParameters.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsParameters.ts deleted file mode 100644 index 29fe8f4c8..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsParameters.ts +++ /dev/null @@ -1,171 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/common.proto - - -// Original file: deps/envoy-api/envoy/api/v2/auth/common.proto - -export enum _envoy_api_v2_auth_TlsParameters_TlsProtocol { - /** - * Envoy will choose the optimal TLS version. - */ - TLS_AUTO = 0, - /** - * TLS 1.0 - */ - TLSv1_0 = 1, - /** - * TLS 1.1 - */ - TLSv1_1 = 2, - /** - * TLS 1.2 - */ - TLSv1_2 = 3, - /** - * TLS 1.3 - */ - TLSv1_3 = 4, -} - -export interface TlsParameters { - /** - * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for - * servers. - */ - 'tls_minimum_protocol_version'?: (_envoy_api_v2_auth_TlsParameters_TlsProtocol | keyof typeof _envoy_api_v2_auth_TlsParameters_TlsProtocol); - /** - * Maximum TLS protocol version. By default, it's ``TLSv1_3`` for servers in non-FIPS builds, and - * ``TLSv1_2`` for clients and for servers using :ref:`BoringSSL FIPS `. - */ - 'tls_maximum_protocol_version'?: (_envoy_api_v2_auth_TlsParameters_TlsProtocol | keyof typeof _envoy_api_v2_auth_TlsParameters_TlsProtocol); - /** - * If specified, the TLS listener will only support the specified `cipher list - * `_ - * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). If not - * specified, the default list will be used. - * - * In non-FIPS builds, the default cipher list is: - * - * .. code-block:: none - * - * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] - * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - * - * In builds using :ref:`BoringSSL FIPS `, the default cipher list is: - * - * .. code-block:: none - * - * ECDHE-ECDSA-AES128-GCM-SHA256 - * ECDHE-RSA-AES128-GCM-SHA256 - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - */ - 'cipher_suites'?: (string)[]; - /** - * If specified, the TLS connection will only support the specified ECDH - * curves. If not specified, the default curves will be used. - * - * In non-FIPS builds, the default curves are: - * - * .. code-block:: none - * - * X25519 - * P-256 - * - * In builds using :ref:`BoringSSL FIPS `, the default curve is: - * - * .. code-block:: none - * - * P-256 - */ - 'ecdh_curves'?: (string)[]; -} - -export interface TlsParameters__Output { - /** - * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for - * servers. - */ - 'tls_minimum_protocol_version': (keyof typeof _envoy_api_v2_auth_TlsParameters_TlsProtocol); - /** - * Maximum TLS protocol version. By default, it's ``TLSv1_3`` for servers in non-FIPS builds, and - * ``TLSv1_2`` for clients and for servers using :ref:`BoringSSL FIPS `. - */ - 'tls_maximum_protocol_version': (keyof typeof _envoy_api_v2_auth_TlsParameters_TlsProtocol); - /** - * If specified, the TLS listener will only support the specified `cipher list - * `_ - * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). If not - * specified, the default list will be used. - * - * In non-FIPS builds, the default cipher list is: - * - * .. code-block:: none - * - * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] - * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - * - * In builds using :ref:`BoringSSL FIPS `, the default cipher list is: - * - * .. code-block:: none - * - * ECDHE-ECDSA-AES128-GCM-SHA256 - * ECDHE-RSA-AES128-GCM-SHA256 - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - */ - 'cipher_suites': (string)[]; - /** - * If specified, the TLS connection will only support the specified ECDH - * curves. If not specified, the default curves will be used. - * - * In non-FIPS builds, the default curves are: - * - * .. code-block:: none - * - * X25519 - * P-256 - * - * In builds using :ref:`BoringSSL FIPS `, the default curve is: - * - * .. code-block:: none - * - * P-256 - */ - 'ecdh_curves': (string)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsSessionTicketKeys.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsSessionTicketKeys.ts deleted file mode 100644 index d4bedd682..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/TlsSessionTicketKeys.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/common.proto - -import type { DataSource as _envoy_api_v2_core_DataSource, DataSource__Output as _envoy_api_v2_core_DataSource__Output } from '../../../../envoy/api/v2/core/DataSource'; - -export interface TlsSessionTicketKeys { - /** - * Keys for encrypting and decrypting TLS session tickets. The - * first key in the array contains the key to encrypt all new sessions created by this context. - * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys - * by, for example, putting the new key first, and the previous key second. - * - * If :ref:`session_ticket_keys ` - * is not specified, the TLS library will still support resuming sessions via tickets, but it will - * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts - * or on different hosts. - * - * Each key must contain exactly 80 bytes of cryptographically-secure random data. For - * example, the output of ``openssl rand 80``. - * - * .. attention:: - * - * Using this feature has serious security considerations and risks. Improper handling of keys - * may result in loss of secrecy in connections, even if ciphers supporting perfect forward - * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some - * discussion. To minimize the risk, you must: - * - * * Keep the session ticket keys at least as secure as your TLS certificate private keys - * * Rotate session ticket keys at least daily, and preferably hourly - * * Always generate keys using a cryptographically-secure random data source - */ - 'keys'?: (_envoy_api_v2_core_DataSource)[]; -} - -export interface TlsSessionTicketKeys__Output { - /** - * Keys for encrypting and decrypting TLS session tickets. The - * first key in the array contains the key to encrypt all new sessions created by this context. - * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys - * by, for example, putting the new key first, and the previous key second. - * - * If :ref:`session_ticket_keys ` - * is not specified, the TLS library will still support resuming sessions via tickets, but it will - * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts - * or on different hosts. - * - * Each key must contain exactly 80 bytes of cryptographically-secure random data. For - * example, the output of ``openssl rand 80``. - * - * .. attention:: - * - * Using this feature has serious security considerations and risks. Improper handling of keys - * may result in loss of secrecy in connections, even if ciphers supporting perfect forward - * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some - * discussion. To minimize the risk, you must: - * - * * Keep the session ticket keys at least as secure as your TLS certificate private keys - * * Rotate session ticket keys at least daily, and preferably hourly - * * Always generate keys using a cryptographically-secure random data source - */ - 'keys': (_envoy_api_v2_core_DataSource__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/UpstreamTlsContext.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/UpstreamTlsContext.ts deleted file mode 100644 index b9dc4414f..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/auth/UpstreamTlsContext.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/auth/tls.proto - -import type { CommonTlsContext as _envoy_api_v2_auth_CommonTlsContext, CommonTlsContext__Output as _envoy_api_v2_auth_CommonTlsContext__Output } from '../../../../envoy/api/v2/auth/CommonTlsContext'; -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; - -export interface UpstreamTlsContext { - /** - * Common TLS context settings. - * - * .. attention:: - * - * Server certificate verification is not enabled by default. Configure - * :ref:`trusted_ca` to enable - * verification. - */ - 'common_tls_context'?: (_envoy_api_v2_auth_CommonTlsContext); - /** - * SNI string to use when creating TLS backend connections. - */ - 'sni'?: (string); - /** - * If true, server-initiated TLS renegotiation will be allowed. - * - * .. attention:: - * - * TLS renegotiation is considered insecure and shouldn't be used unless absolutely necessary. - */ - 'allow_renegotiation'?: (boolean); - /** - * Maximum number of session keys (Pre-Shared Keys for TLSv1.3+, Session IDs and Session Tickets - * for TLSv1.2 and older) to store for the purpose of session resumption. - * - * Defaults to 1, setting this to 0 disables session resumption. - */ - 'max_session_keys'?: (_google_protobuf_UInt32Value); -} - -export interface UpstreamTlsContext__Output { - /** - * Common TLS context settings. - * - * .. attention:: - * - * Server certificate verification is not enabled by default. Configure - * :ref:`trusted_ca` to enable - * verification. - */ - 'common_tls_context'?: (_envoy_api_v2_auth_CommonTlsContext__Output); - /** - * SNI string to use when creating TLS backend connections. - */ - 'sni': (string); - /** - * If true, server-initiated TLS renegotiation will be allowed. - * - * .. attention:: - * - * TLS renegotiation is considered insecure and shouldn't be used unless absolutely necessary. - */ - 'allow_renegotiation': (boolean); - /** - * Maximum number of session keys (Pre-Shared Keys for TLSv1.3+, Session IDs and Session Tickets - * for TLSv1.2 and older) to store for the purpose of session resumption. - * - * Defaults to 1, setting this to 0 disables session resumption. - */ - 'max_session_keys'?: (_google_protobuf_UInt32Value__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts index 009506fa2..50a44a3b2 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts @@ -14,7 +14,7 @@ export interface BuildVersion { 'version'?: (_envoy_type_SemanticVersion); /** * Free-form build information. - * Envoy defines several well known keys in the source/common/common/version.h file + * Envoy defines several well known keys in the source/common/version/version.h file */ 'metadata'?: (_google_protobuf_Struct); } @@ -30,7 +30,7 @@ export interface BuildVersion__Output { 'version'?: (_envoy_type_SemanticVersion__Output); /** * Free-form build information. - * Envoy defines several well known keys in the source/common/common/version.h file + * Envoy defines several well known keys in the source/common/version/version.h file */ 'metadata'?: (_google_protobuf_Struct__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/GrpcProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/GrpcProtocolOptions.ts deleted file mode 100644 index 66723801e..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/GrpcProtocolOptions.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/protocol.proto - -import type { Http2ProtocolOptions as _envoy_api_v2_core_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_api_v2_core_Http2ProtocolOptions__Output } from '../../../../envoy/api/v2/core/Http2ProtocolOptions'; - -/** - * [#not-implemented-hide:] - */ -export interface GrpcProtocolOptions { - 'http2_protocol_options'?: (_envoy_api_v2_core_Http2ProtocolOptions); -} - -/** - * [#not-implemented-hide:] - */ -export interface GrpcProtocolOptions__Output { - 'http2_protocol_options'?: (_envoy_api_v2_core_Http2ProtocolOptions__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts index 8d9aba3e0..1ea2b8146 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts @@ -22,7 +22,7 @@ export interface RuntimeDouble__Output { /** * Default value if runtime value is not available. */ - 'default_value': (number | string); + 'default_value': (number); /** * Runtime key to get value for comparison. This value is used if defined. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SelfConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SelfConfigSource.ts deleted file mode 100644 index 144cfdf5a..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SelfConfigSource.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/config_source.proto - - -/** - * [#not-implemented-hide:] - * Self-referencing config source options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to - * specify that other data can be obtained from the same server. - */ -export interface SelfConfigSource { -} - -/** - * [#not-implemented-hide:] - * Self-referencing config source options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to - * specify that other data can be obtained from the same server. - */ -export interface SelfConfigSource__Output { -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts index 9ed8016b7..335b759b6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts @@ -37,5 +37,5 @@ export interface EndpointLoadMetricStats__Output { * Sum of metric values across all calls that finished with this metric for * load_reporting_interval. */ - 'total_metric_value': (number | string); + 'total_metric_value': (number); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/Filter.ts deleted file mode 100644 index 2c6e0d087..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/Filter.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener/listener_components.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; - -export interface Filter { - /** - * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. - */ - '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 Filter__Output { - /** - * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. - */ - '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"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/FilterChain.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/FilterChain.ts deleted file mode 100644 index 1e98abe50..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/FilterChain.ts +++ /dev/null @@ -1,118 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener/listener_components.proto - -import type { FilterChainMatch as _envoy_api_v2_listener_FilterChainMatch, FilterChainMatch__Output as _envoy_api_v2_listener_FilterChainMatch__Output } from '../../../../envoy/api/v2/listener/FilterChainMatch'; -import type { DownstreamTlsContext as _envoy_api_v2_auth_DownstreamTlsContext, DownstreamTlsContext__Output as _envoy_api_v2_auth_DownstreamTlsContext__Output } from '../../../../envoy/api/v2/auth/DownstreamTlsContext'; -import type { Filter as _envoy_api_v2_listener_Filter, Filter__Output as _envoy_api_v2_listener_Filter__Output } from '../../../../envoy/api/v2/listener/Filter'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { Metadata as _envoy_api_v2_core_Metadata, Metadata__Output as _envoy_api_v2_core_Metadata__Output } from '../../../../envoy/api/v2/core/Metadata'; -import type { TransportSocket as _envoy_api_v2_core_TransportSocket, TransportSocket__Output as _envoy_api_v2_core_TransportSocket__Output } from '../../../../envoy/api/v2/core/TransportSocket'; - -/** - * A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and - * various other parameters. - * [#next-free-field: 8] - */ -export interface FilterChain { - /** - * The criteria to use when matching a connection to this filter chain. - */ - 'filter_chain_match'?: (_envoy_api_v2_listener_FilterChainMatch); - /** - * The TLS context for this filter chain. - * - * .. attention:: - * - * **This field is deprecated**. Use `transport_socket` with name `tls` instead. If both are - * set, `transport_socket` takes priority. - */ - 'tls_context'?: (_envoy_api_v2_auth_DownstreamTlsContext); - /** - * A list of individual network filters that make up the filter chain for - * connections established with the listener. Order matters as the filters are - * processed sequentially as connection events happen. Note: If the filter - * list is empty, the connection will close by default. - */ - 'filters'?: (_envoy_api_v2_listener_Filter)[]; - /** - * Whether the listener should expect a PROXY protocol V1 header on new - * connections. If this option is enabled, the listener will assume that that - * remote address of the connection is the one specified in the header. Some - * load balancers including the AWS ELB support this option. If the option is - * absent or set to false, Envoy will use the physical peer address of the - * connection as the remote address. - */ - 'use_proxy_proto'?: (_google_protobuf_BoolValue); - /** - * [#not-implemented-hide:] filter chain metadata. - */ - 'metadata'?: (_envoy_api_v2_core_Metadata); - /** - * Optional custom transport socket implementation to use for downstream connections. - * To setup TLS, set a transport socket with name `tls` and - * :ref:`DownstreamTlsContext ` in the `typed_config`. - * If no transport socket configuration is specified, new connections - * will be set up with plaintext. - */ - 'transport_socket'?: (_envoy_api_v2_core_TransportSocket); - /** - * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no - * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter - * chain is to be dynamically updated or removed via FCDS a unique name must be provided. - */ - 'name'?: (string); -} - -/** - * A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and - * various other parameters. - * [#next-free-field: 8] - */ -export interface FilterChain__Output { - /** - * The criteria to use when matching a connection to this filter chain. - */ - 'filter_chain_match'?: (_envoy_api_v2_listener_FilterChainMatch__Output); - /** - * The TLS context for this filter chain. - * - * .. attention:: - * - * **This field is deprecated**. Use `transport_socket` with name `tls` instead. If both are - * set, `transport_socket` takes priority. - */ - 'tls_context'?: (_envoy_api_v2_auth_DownstreamTlsContext__Output); - /** - * A list of individual network filters that make up the filter chain for - * connections established with the listener. Order matters as the filters are - * processed sequentially as connection events happen. Note: If the filter - * list is empty, the connection will close by default. - */ - 'filters': (_envoy_api_v2_listener_Filter__Output)[]; - /** - * Whether the listener should expect a PROXY protocol V1 header on new - * connections. If this option is enabled, the listener will assume that that - * remote address of the connection is the one specified in the header. Some - * load balancers including the AWS ELB support this option. If the option is - * absent or set to false, Envoy will use the physical peer address of the - * connection as the remote address. - */ - 'use_proxy_proto'?: (_google_protobuf_BoolValue__Output); - /** - * [#not-implemented-hide:] filter chain metadata. - */ - 'metadata'?: (_envoy_api_v2_core_Metadata__Output); - /** - * Optional custom transport socket implementation to use for downstream connections. - * To setup TLS, set a transport socket with name `tls` and - * :ref:`DownstreamTlsContext ` in the `typed_config`. - * If no transport socket configuration is specified, new connections - * will be set up with plaintext. - */ - 'transport_socket'?: (_envoy_api_v2_core_TransportSocket__Output); - /** - * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no - * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter - * chain is to be dynamically updated or removed via FCDS a unique name must be provided. - */ - 'name': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/HedgePolicy.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/route/HedgePolicy.ts deleted file mode 100644 index 8134fc359..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/HedgePolicy.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto - -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -import type { FractionalPercent as _envoy_type_FractionalPercent, FractionalPercent__Output as _envoy_type_FractionalPercent__Output } from '../../../../envoy/type/FractionalPercent'; - -/** - * HTTP request hedging :ref:`architecture overview `. - */ -export interface HedgePolicy { - /** - * Specifies the number of initial requests that should be sent upstream. - * Must be at least 1. - * Defaults to 1. - * [#not-implemented-hide:] - */ - 'initial_requests'?: (_google_protobuf_UInt32Value); - /** - * Specifies a probability that an additional upstream request should be sent - * on top of what is specified by initial_requests. - * Defaults to 0. - * [#not-implemented-hide:] - */ - 'additional_request_chance'?: (_envoy_type_FractionalPercent); - /** - * Indicates that a hedged request should be sent when the per-try timeout - * is hit. This will only occur if the retry policy also indicates that a - * timed out request should be retried. - * Once a timed out request is retried due to per try timeout, the router - * filter will ensure that it is not retried again even if the returned - * response headers would otherwise be retried according the specified - * :ref:`RetryPolicy `. - * Defaults to false. - */ - 'hedge_on_per_try_timeout'?: (boolean); -} - -/** - * HTTP request hedging :ref:`architecture overview `. - */ -export interface HedgePolicy__Output { - /** - * Specifies the number of initial requests that should be sent upstream. - * Must be at least 1. - * Defaults to 1. - * [#not-implemented-hide:] - */ - 'initial_requests'?: (_google_protobuf_UInt32Value__Output); - /** - * Specifies a probability that an additional upstream request should be sent - * on top of what is specified by initial_requests. - * Defaults to 0. - * [#not-implemented-hide:] - */ - 'additional_request_chance'?: (_envoy_type_FractionalPercent__Output); - /** - * Indicates that a hedged request should be sent when the per-try timeout - * is hit. This will only occur if the retry policy also indicates that a - * timed out request should be retried. - * Once a timed out request is retried due to per try timeout, the router - * filter will ensure that it is not retried again even if the returned - * response headers would otherwise be retried according the specified - * :ref:`RetryPolicy `. - * Defaults to false. - */ - 'hedge_on_per_try_timeout': (boolean); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/QueryParameterMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/route/QueryParameterMatcher.ts deleted file mode 100644 index 68f4fbcaa..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/QueryParameterMatcher.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto - -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { StringMatcher as _envoy_type_matcher_StringMatcher, StringMatcher__Output as _envoy_type_matcher_StringMatcher__Output } from '../../../../envoy/type/matcher/StringMatcher'; - -/** - * Query parameter matching treats the query string of a request's :path header - * as an ampersand-separated list of keys and/or key=value elements. - * [#next-free-field: 7] - */ -export interface QueryParameterMatcher { - /** - * Specifies the name of a key that must be present in the requested - * *path*'s query string. - */ - 'name'?: (string); - /** - * Specifies the value of the key. If the value is absent, a request - * that contains the key in its query string will match, whether the - * key appears with a value (e.g., "?debug=true") or not (e.g., "?debug") - * - * ..attention:: - * This field is deprecated. Use an `exact` match inside the `string_match` field. - */ - 'value'?: (string); - /** - * Specifies whether the query parameter value is a regular expression. - * Defaults to false. The entire query parameter value (i.e., the part to - * the right of the equals sign in "key=value") must match the regex. - * E.g., the regex ``\d+$`` will match *123* but not *a123* or *123a*. - * - * ..attention:: - * This field is deprecated. Use a `safe_regex` match inside the `string_match` field. - */ - 'regex'?: (_google_protobuf_BoolValue); - /** - * Specifies whether a query parameter value should match against a string. - */ - 'string_match'?: (_envoy_type_matcher_StringMatcher); - /** - * Specifies whether a query parameter should be present. - */ - 'present_match'?: (boolean); - 'query_parameter_match_specifier'?: "string_match"|"present_match"; -} - -/** - * Query parameter matching treats the query string of a request's :path header - * as an ampersand-separated list of keys and/or key=value elements. - * [#next-free-field: 7] - */ -export interface QueryParameterMatcher__Output { - /** - * Specifies the name of a key that must be present in the requested - * *path*'s query string. - */ - 'name': (string); - /** - * Specifies the value of the key. If the value is absent, a request - * that contains the key in its query string will match, whether the - * key appears with a value (e.g., "?debug=true") or not (e.g., "?debug") - * - * ..attention:: - * This field is deprecated. Use an `exact` match inside the `string_match` field. - */ - 'value': (string); - /** - * Specifies whether the query parameter value is a regular expression. - * Defaults to false. The entire query parameter value (i.e., the part to - * the right of the equals sign in "key=value") must match the regex. - * E.g., the regex ``\d+$`` will match *123* but not *a123* or *123a*. - * - * ..attention:: - * This field is deprecated. Use a `safe_regex` match inside the `string_match` field. - */ - 'regex'?: (_google_protobuf_BoolValue__Output); - /** - * Specifies whether a query parameter value should match against a string. - */ - 'string_match'?: (_envoy_type_matcher_StringMatcher__Output); - /** - * Specifies whether a query parameter should be present. - */ - 'present_match'?: (boolean); - 'query_parameter_match_specifier': "string_match"|"present_match"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RateLimit.ts deleted file mode 100644 index 998d94e53..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RateLimit.ts +++ /dev/null @@ -1,341 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto - -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { HeaderMatcher as _envoy_api_v2_route_HeaderMatcher, HeaderMatcher__Output as _envoy_api_v2_route_HeaderMatcher__Output } from '../../../../envoy/api/v2/route/HeaderMatcher'; - -/** - * [#next-free-field: 7] - */ -export interface _envoy_api_v2_route_RateLimit_Action { - /** - * Rate limit on source cluster. - */ - 'source_cluster'?: (_envoy_api_v2_route_RateLimit_Action_SourceCluster); - /** - * Rate limit on destination cluster. - */ - 'destination_cluster'?: (_envoy_api_v2_route_RateLimit_Action_DestinationCluster); - /** - * Rate limit on request headers. - */ - 'request_headers'?: (_envoy_api_v2_route_RateLimit_Action_RequestHeaders); - /** - * Rate limit on remote address. - */ - 'remote_address'?: (_envoy_api_v2_route_RateLimit_Action_RemoteAddress); - /** - * Rate limit on a generic key. - */ - 'generic_key'?: (_envoy_api_v2_route_RateLimit_Action_GenericKey); - /** - * Rate limit on the existence of request headers. - */ - 'header_value_match'?: (_envoy_api_v2_route_RateLimit_Action_HeaderValueMatch); - 'action_specifier'?: "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"; -} - -/** - * [#next-free-field: 7] - */ -export interface _envoy_api_v2_route_RateLimit_Action__Output { - /** - * Rate limit on source cluster. - */ - 'source_cluster'?: (_envoy_api_v2_route_RateLimit_Action_SourceCluster__Output); - /** - * Rate limit on destination cluster. - */ - 'destination_cluster'?: (_envoy_api_v2_route_RateLimit_Action_DestinationCluster__Output); - /** - * Rate limit on request headers. - */ - 'request_headers'?: (_envoy_api_v2_route_RateLimit_Action_RequestHeaders__Output); - /** - * Rate limit on remote address. - */ - 'remote_address'?: (_envoy_api_v2_route_RateLimit_Action_RemoteAddress__Output); - /** - * Rate limit on a generic key. - */ - 'generic_key'?: (_envoy_api_v2_route_RateLimit_Action_GenericKey__Output); - /** - * Rate limit on the existence of request headers. - */ - 'header_value_match'?: (_envoy_api_v2_route_RateLimit_Action_HeaderValueMatch__Output); - 'action_specifier': "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"; -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("destination_cluster", "") - * - * Once a request matches against a route table rule, a routed cluster is determined by one of - * the following :ref:`route table configuration ` - * settings: - * - * * :ref:`cluster ` indicates the upstream cluster - * to route to. - * * :ref:`weighted_clusters ` - * chooses a cluster randomly from a set of clusters with attributed weight. - * * :ref:`cluster_header ` indicates which - * header in the request contains the target cluster. - */ -export interface _envoy_api_v2_route_RateLimit_Action_DestinationCluster { -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("destination_cluster", "") - * - * Once a request matches against a route table rule, a routed cluster is determined by one of - * the following :ref:`route table configuration ` - * settings: - * - * * :ref:`cluster ` indicates the upstream cluster - * to route to. - * * :ref:`weighted_clusters ` - * chooses a cluster randomly from a set of clusters with attributed weight. - * * :ref:`cluster_header ` indicates which - * header in the request contains the target cluster. - */ -export interface _envoy_api_v2_route_RateLimit_Action_DestinationCluster__Output { -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("generic_key", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_GenericKey { - /** - * The value to use in the descriptor entry. - */ - 'descriptor_value'?: (string); -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("generic_key", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_GenericKey__Output { - /** - * The value to use in the descriptor entry. - */ - 'descriptor_value': (string); -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("header_match", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_HeaderValueMatch { - /** - * The value to use in the descriptor entry. - */ - 'descriptor_value'?: (string); - /** - * If set to true, the action will append a descriptor entry when the - * request matches the headers. If set to false, the action will append a - * descriptor entry when the request does not match the headers. The - * default value is true. - */ - 'expect_match'?: (_google_protobuf_BoolValue); - /** - * Specifies a set of headers that the rate limit action should match - * on. The action will check the request’s headers against all the - * specified headers in the config. A match will happen if all the - * headers in the config are present in the request with the same values - * (or based on presence if the value field is not in the config). - */ - 'headers'?: (_envoy_api_v2_route_HeaderMatcher)[]; -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("header_match", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_HeaderValueMatch__Output { - /** - * The value to use in the descriptor entry. - */ - 'descriptor_value': (string); - /** - * If set to true, the action will append a descriptor entry when the - * request matches the headers. If set to false, the action will append a - * descriptor entry when the request does not match the headers. The - * default value is true. - */ - 'expect_match'?: (_google_protobuf_BoolValue__Output); - /** - * Specifies a set of headers that the rate limit action should match - * on. The action will check the request’s headers against all the - * specified headers in the config. A match will happen if all the - * headers in the config are present in the request with the same values - * (or based on presence if the value field is not in the config). - */ - 'headers': (_envoy_api_v2_route_HeaderMatcher__Output)[]; -} - -/** - * The following descriptor entry is appended to the descriptor and is populated using the - * trusted address from :ref:`x-forwarded-for `: - * - * .. code-block:: cpp - * - * ("remote_address", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_RemoteAddress { -} - -/** - * The following descriptor entry is appended to the descriptor and is populated using the - * trusted address from :ref:`x-forwarded-for `: - * - * .. code-block:: cpp - * - * ("remote_address", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_RemoteAddress__Output { -} - -/** - * The following descriptor entry is appended when a header contains a key that matches the - * *header_name*: - * - * .. code-block:: cpp - * - * ("", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_RequestHeaders { - /** - * The header name to be queried from the request headers. The header’s - * value is used to populate the value of the descriptor entry for the - * descriptor_key. - */ - 'header_name'?: (string); - /** - * The key to use in the descriptor entry. - */ - 'descriptor_key'?: (string); -} - -/** - * The following descriptor entry is appended when a header contains a key that matches the - * *header_name*: - * - * .. code-block:: cpp - * - * ("", "") - */ -export interface _envoy_api_v2_route_RateLimit_Action_RequestHeaders__Output { - /** - * The header name to be queried from the request headers. The header’s - * value is used to populate the value of the descriptor entry for the - * descriptor_key. - */ - 'header_name': (string); - /** - * The key to use in the descriptor entry. - */ - 'descriptor_key': (string); -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("source_cluster", "") - * - * is derived from the :option:`--service-cluster` option. - */ -export interface _envoy_api_v2_route_RateLimit_Action_SourceCluster { -} - -/** - * The following descriptor entry is appended to the descriptor: - * - * .. code-block:: cpp - * - * ("source_cluster", "") - * - * is derived from the :option:`--service-cluster` option. - */ -export interface _envoy_api_v2_route_RateLimit_Action_SourceCluster__Output { -} - -/** - * Global rate limiting :ref:`architecture overview `. - */ -export interface RateLimit { - /** - * Refers to the stage set in the filter. The rate limit configuration only - * applies to filters with the same stage number. The default stage number is - * 0. - * - * .. note:: - * - * The filter supports a range of 0 - 10 inclusively for stage numbers. - */ - 'stage'?: (_google_protobuf_UInt32Value); - /** - * The key to be set in runtime to disable this rate limit configuration. - */ - 'disable_key'?: (string); - /** - * A list of actions that are to be applied for this rate limit configuration. - * Order matters as the actions are processed sequentially and the descriptor - * is composed by appending descriptor entries in that sequence. If an action - * cannot append a descriptor entry, no descriptor is generated for the - * configuration. See :ref:`composing actions - * ` for additional documentation. - */ - 'actions'?: (_envoy_api_v2_route_RateLimit_Action)[]; -} - -/** - * Global rate limiting :ref:`architecture overview `. - */ -export interface RateLimit__Output { - /** - * Refers to the stage set in the filter. The rate limit configuration only - * applies to filters with the same stage number. The default stage number is - * 0. - * - * .. note:: - * - * The filter supports a range of 0 - 10 inclusively for stage numbers. - */ - 'stage'?: (_google_protobuf_UInt32Value__Output); - /** - * The key to be set in runtime to disable this rate limit configuration. - */ - 'disable_key': (string); - /** - * A list of actions that are to be applied for this rate limit configuration. - * Order matters as the actions are processed sequentially and the descriptor - * is composed by appending descriptor entries in that sequence. If an action - * cannot append a descriptor entry, no descriptor is generated for the - * configuration. See :ref:`composing actions - * ` for additional documentation. - */ - 'actions': (_envoy_api_v2_route_RateLimit_Action__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RedirectAction.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RedirectAction.ts deleted file mode 100644 index de7105a54..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RedirectAction.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto - - -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto - -export enum _envoy_api_v2_route_RedirectAction_RedirectResponseCode { - /** - * Moved Permanently HTTP Status Code - 301. - */ - MOVED_PERMANENTLY = 0, - /** - * Found HTTP Status Code - 302. - */ - FOUND = 1, - /** - * See Other HTTP Status Code - 303. - */ - SEE_OTHER = 2, - /** - * Temporary Redirect HTTP Status Code - 307. - */ - TEMPORARY_REDIRECT = 3, - /** - * Permanent Redirect HTTP Status Code - 308. - */ - PERMANENT_REDIRECT = 4, -} - -/** - * [#next-free-field: 9] - */ -export interface RedirectAction { - /** - * The host portion of the URL will be swapped with this value. - */ - 'host_redirect'?: (string); - /** - * The path portion of the URL will be swapped with this value. - */ - 'path_redirect'?: (string); - /** - * The HTTP status code to use in the redirect response. The default response - * code is MOVED_PERMANENTLY (301). - */ - 'response_code'?: (_envoy_api_v2_route_RedirectAction_RedirectResponseCode | keyof typeof _envoy_api_v2_route_RedirectAction_RedirectResponseCode); - /** - * The scheme portion of the URL will be swapped with "https". - */ - 'https_redirect'?: (boolean); - /** - * Indicates that during redirection, the matched prefix (or path) - * should be swapped with this value. This option allows redirect URLs be dynamically created - * based on the request. - * - * .. attention:: - * - * Pay attention to the use of trailing slashes as mentioned in - * :ref:`RouteAction's prefix_rewrite `. - */ - 'prefix_rewrite'?: (string); - /** - * Indicates that during redirection, the query portion of the URL will - * be removed. Default value is false. - */ - 'strip_query'?: (boolean); - /** - * The scheme portion of the URL will be swapped with this value. - */ - 'scheme_redirect'?: (string); - /** - * The port value of the URL will be swapped with this value. - */ - 'port_redirect'?: (number); - /** - * When the scheme redirection take place, the following rules apply: - * 1. If the source URI scheme is `http` and the port is explicitly - * set to `:80`, the port will be removed after the redirection - * 2. If the source URI scheme is `https` and the port is explicitly - * set to `:443`, the port will be removed after the redirection - */ - 'scheme_rewrite_specifier'?: "https_redirect"|"scheme_redirect"; - 'path_rewrite_specifier'?: "path_redirect"|"prefix_rewrite"; -} - -/** - * [#next-free-field: 9] - */ -export interface RedirectAction__Output { - /** - * The host portion of the URL will be swapped with this value. - */ - 'host_redirect': (string); - /** - * The path portion of the URL will be swapped with this value. - */ - 'path_redirect'?: (string); - /** - * The HTTP status code to use in the redirect response. The default response - * code is MOVED_PERMANENTLY (301). - */ - 'response_code': (keyof typeof _envoy_api_v2_route_RedirectAction_RedirectResponseCode); - /** - * The scheme portion of the URL will be swapped with "https". - */ - 'https_redirect'?: (boolean); - /** - * Indicates that during redirection, the matched prefix (or path) - * should be swapped with this value. This option allows redirect URLs be dynamically created - * based on the request. - * - * .. attention:: - * - * Pay attention to the use of trailing slashes as mentioned in - * :ref:`RouteAction's prefix_rewrite `. - */ - 'prefix_rewrite'?: (string); - /** - * Indicates that during redirection, the query portion of the URL will - * be removed. Default value is false. - */ - 'strip_query': (boolean); - /** - * The scheme portion of the URL will be swapped with this value. - */ - 'scheme_redirect'?: (string); - /** - * The port value of the URL will be swapped with this value. - */ - 'port_redirect': (number); - /** - * When the scheme redirection take place, the following rules apply: - * 1. If the source URI scheme is `http` and the port is explicitly - * set to `:80`, the port will be removed after the redirection - * 2. If the source URI scheme is `https` and the port is explicitly - * set to `:443`, the port will be removed after the redirection - */ - 'scheme_rewrite_specifier': "https_redirect"|"scheme_redirect"; - 'path_rewrite_specifier': "path_redirect"|"prefix_rewrite"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RetryPolicy.ts deleted file mode 100644 index 63b7deb5a..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RetryPolicy.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto - -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { HeaderMatcher as _envoy_api_v2_route_HeaderMatcher, HeaderMatcher__Output as _envoy_api_v2_route_HeaderMatcher__Output } from '../../../../envoy/api/v2/route/HeaderMatcher'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -import type { Long } from '@grpc/proto-loader'; - -export interface _envoy_api_v2_route_RetryPolicy_RetryBackOff { - /** - * Specifies the base interval between retries. This parameter is required and must be greater - * than zero. Values less than 1 ms are rounded up to 1 ms. - * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's - * back-off algorithm. - */ - 'base_interval'?: (_google_protobuf_Duration); - /** - * Specifies the maximum interval between retries. This parameter is optional, but must be - * greater than or equal to the `base_interval` if set. The default is 10 times the - * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion - * of Envoy's back-off algorithm. - */ - 'max_interval'?: (_google_protobuf_Duration); -} - -export interface _envoy_api_v2_route_RetryPolicy_RetryBackOff__Output { - /** - * Specifies the base interval between retries. This parameter is required and must be greater - * than zero. Values less than 1 ms are rounded up to 1 ms. - * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's - * back-off algorithm. - */ - 'base_interval'?: (_google_protobuf_Duration__Output); - /** - * Specifies the maximum interval between retries. This parameter is optional, but must be - * greater than or equal to the `base_interval` if set. The default is 10 times the - * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion - * of Envoy's back-off algorithm. - */ - 'max_interval'?: (_google_protobuf_Duration__Output); -} - -export interface _envoy_api_v2_route_RetryPolicy_RetryHostPredicate { - 'name'?: (string); - 'config'?: (_google_protobuf_Struct); - 'typed_config'?: (_google_protobuf_Any); - 'config_type'?: "config"|"typed_config"; -} - -export interface _envoy_api_v2_route_RetryPolicy_RetryHostPredicate__Output { - 'name': (string); - 'config'?: (_google_protobuf_Struct__Output); - 'typed_config'?: (_google_protobuf_Any__Output); - 'config_type': "config"|"typed_config"; -} - -export interface _envoy_api_v2_route_RetryPolicy_RetryPriority { - 'name'?: (string); - 'config'?: (_google_protobuf_Struct); - 'typed_config'?: (_google_protobuf_Any); - 'config_type'?: "config"|"typed_config"; -} - -export interface _envoy_api_v2_route_RetryPolicy_RetryPriority__Output { - 'name': (string); - 'config'?: (_google_protobuf_Struct__Output); - 'typed_config'?: (_google_protobuf_Any__Output); - 'config_type': "config"|"typed_config"; -} - -/** - * HTTP retry :ref:`architecture overview `. - * [#next-free-field: 11] - */ -export interface RetryPolicy { - /** - * Specifies the conditions under which retry takes place. These are the same - * conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and - * :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. - */ - 'retry_on'?: (string); - /** - * Specifies the allowed number of retries. This parameter is optional and - * defaults to 1. These are the same conditions documented for - * :ref:`config_http_filters_router_x-envoy-max-retries`. - */ - 'num_retries'?: (_google_protobuf_UInt32Value); - /** - * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The - * same conditions documented for - * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. - * - * .. note:: - * - * If left unspecified, Envoy will use the global - * :ref:`route timeout ` for the request. - * Consequently, when using a :ref:`5xx ` based - * retry policy, a request that times out will not be retried as the total timeout budget - * would have been exhausted. - */ - 'per_try_timeout'?: (_google_protobuf_Duration); - /** - * Specifies an implementation of a RetryPriority which is used to determine the - * distribution of load across priorities used for retries. Refer to - * :ref:`retry plugin configuration ` for more details. - */ - 'retry_priority'?: (_envoy_api_v2_route_RetryPolicy_RetryPriority); - /** - * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host - * for retries. If any of the predicates reject the host, host selection will be reattempted. - * Refer to :ref:`retry plugin configuration ` for more - * details. - */ - 'retry_host_predicate'?: (_envoy_api_v2_route_RetryPolicy_RetryHostPredicate)[]; - /** - * The maximum number of times host selection will be reattempted before giving up, at which - * point the host that was last selected will be routed to. If unspecified, this will default to - * retrying once. - */ - 'host_selection_retry_max_attempts'?: (number | string | Long); - /** - * HTTP status codes that should trigger a retry in addition to those specified by retry_on. - */ - 'retriable_status_codes'?: (number)[]; - /** - * Specifies parameters that control retry back off. This parameter is optional, in which case the - * default base interval is 25 milliseconds or, if set, the current value of the - * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times - * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` - * describes Envoy's back-off algorithm. - */ - 'retry_back_off'?: (_envoy_api_v2_route_RetryPolicy_RetryBackOff); - /** - * HTTP response headers that trigger a retry if present in the response. A retry will be - * triggered if any of the header matches match the upstream response headers. - * The field is only consulted if 'retriable-headers' retry policy is active. - */ - 'retriable_headers'?: (_envoy_api_v2_route_HeaderMatcher)[]; - /** - * HTTP headers which must be present in the request for retries to be attempted. - */ - 'retriable_request_headers'?: (_envoy_api_v2_route_HeaderMatcher)[]; -} - -/** - * HTTP retry :ref:`architecture overview `. - * [#next-free-field: 11] - */ -export interface RetryPolicy__Output { - /** - * Specifies the conditions under which retry takes place. These are the same - * conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and - * :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. - */ - 'retry_on': (string); - /** - * Specifies the allowed number of retries. This parameter is optional and - * defaults to 1. These are the same conditions documented for - * :ref:`config_http_filters_router_x-envoy-max-retries`. - */ - 'num_retries'?: (_google_protobuf_UInt32Value__Output); - /** - * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The - * same conditions documented for - * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. - * - * .. note:: - * - * If left unspecified, Envoy will use the global - * :ref:`route timeout ` for the request. - * Consequently, when using a :ref:`5xx ` based - * retry policy, a request that times out will not be retried as the total timeout budget - * would have been exhausted. - */ - 'per_try_timeout'?: (_google_protobuf_Duration__Output); - /** - * Specifies an implementation of a RetryPriority which is used to determine the - * distribution of load across priorities used for retries. Refer to - * :ref:`retry plugin configuration ` for more details. - */ - 'retry_priority'?: (_envoy_api_v2_route_RetryPolicy_RetryPriority__Output); - /** - * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host - * for retries. If any of the predicates reject the host, host selection will be reattempted. - * Refer to :ref:`retry plugin configuration ` for more - * details. - */ - 'retry_host_predicate': (_envoy_api_v2_route_RetryPolicy_RetryHostPredicate__Output)[]; - /** - * The maximum number of times host selection will be reattempted before giving up, at which - * point the host that was last selected will be routed to. If unspecified, this will default to - * retrying once. - */ - 'host_selection_retry_max_attempts': (string); - /** - * HTTP status codes that should trigger a retry in addition to those specified by retry_on. - */ - 'retriable_status_codes': (number)[]; - /** - * Specifies parameters that control retry back off. This parameter is optional, in which case the - * default base interval is 25 milliseconds or, if set, the current value of the - * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times - * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` - * describes Envoy's back-off algorithm. - */ - 'retry_back_off'?: (_envoy_api_v2_route_RetryPolicy_RetryBackOff__Output); - /** - * HTTP response headers that trigger a retry if present in the response. A retry will be - * triggered if any of the header matches match the upstream response headers. - * The field is only consulted if 'retriable-headers' retry policy is active. - */ - 'retriable_headers': (_envoy_api_v2_route_HeaderMatcher__Output)[]; - /** - * HTTP headers which must be present in the request for retries to be attempted. - */ - 'retriable_request_headers': (_envoy_api_v2_route_HeaderMatcher__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AccessLog.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts similarity index 54% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AccessLog.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts index 82e056a4b..413f569ce 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AccessLog.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts @@ -1,8 +1,7 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -import type { AccessLogFilter as _envoy_config_filter_accesslog_v2_AccessLogFilter, AccessLogFilter__Output as _envoy_config_filter_accesslog_v2_AccessLogFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/AccessLogFilter'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../google/protobuf/Any'; +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../envoy/config/accesslog/v3/AccessLogFilter'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; export interface AccessLog { /** @@ -17,21 +16,20 @@ export interface AccessLog { /** * Filter which is used to determine if the access log needs to be written. */ - 'filter'?: (_envoy_config_filter_accesslog_v2_AccessLogFilter); - 'config'?: (_google_protobuf_Struct); + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter); 'typed_config'?: (_google_protobuf_Any); /** - * Custom configuration that depends on the access log being instantiated. Built-in - * configurations include: + * Custom configuration that depends on the access log being instantiated. + * Built-in configurations include: * * #. "envoy.access_loggers.file": :ref:`FileAccessLog - * ` + * ` * #. "envoy.access_loggers.http_grpc": :ref:`HttpGrpcAccessLogConfig - * ` + * ` * #. "envoy.access_loggers.tcp_grpc": :ref:`TcpGrpcAccessLogConfig - * ` + * ` */ - 'config_type'?: "config"|"typed_config"; + 'config_type'?: "typed_config"; } export interface AccessLog__Output { @@ -47,19 +45,18 @@ export interface AccessLog__Output { /** * Filter which is used to determine if the access log needs to be written. */ - 'filter'?: (_envoy_config_filter_accesslog_v2_AccessLogFilter__Output); - 'config'?: (_google_protobuf_Struct__Output); + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter__Output); 'typed_config'?: (_google_protobuf_Any__Output); /** - * Custom configuration that depends on the access log being instantiated. Built-in - * configurations include: + * Custom configuration that depends on the access log being instantiated. + * Built-in configurations include: * * #. "envoy.access_loggers.file": :ref:`FileAccessLog - * ` + * ` * #. "envoy.access_loggers.http_grpc": :ref:`HttpGrpcAccessLogConfig - * ` + * ` * #. "envoy.access_loggers.tcp_grpc": :ref:`TcpGrpcAccessLogConfig - * ` + * ` */ - 'config_type': "config"|"typed_config"; + 'config_type': "typed_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts new file mode 100644 index 000000000..9470b6eaa --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts @@ -0,0 +1,124 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { StatusCodeFilter as _envoy_config_accesslog_v3_StatusCodeFilter, StatusCodeFilter__Output as _envoy_config_accesslog_v3_StatusCodeFilter__Output } from '../../../../envoy/config/accesslog/v3/StatusCodeFilter'; +import type { DurationFilter as _envoy_config_accesslog_v3_DurationFilter, DurationFilter__Output as _envoy_config_accesslog_v3_DurationFilter__Output } from '../../../../envoy/config/accesslog/v3/DurationFilter'; +import type { NotHealthCheckFilter as _envoy_config_accesslog_v3_NotHealthCheckFilter, NotHealthCheckFilter__Output as _envoy_config_accesslog_v3_NotHealthCheckFilter__Output } from '../../../../envoy/config/accesslog/v3/NotHealthCheckFilter'; +import type { TraceableFilter as _envoy_config_accesslog_v3_TraceableFilter, TraceableFilter__Output as _envoy_config_accesslog_v3_TraceableFilter__Output } from '../../../../envoy/config/accesslog/v3/TraceableFilter'; +import type { RuntimeFilter as _envoy_config_accesslog_v3_RuntimeFilter, RuntimeFilter__Output as _envoy_config_accesslog_v3_RuntimeFilter__Output } from '../../../../envoy/config/accesslog/v3/RuntimeFilter'; +import type { AndFilter as _envoy_config_accesslog_v3_AndFilter, AndFilter__Output as _envoy_config_accesslog_v3_AndFilter__Output } from '../../../../envoy/config/accesslog/v3/AndFilter'; +import type { OrFilter as _envoy_config_accesslog_v3_OrFilter, OrFilter__Output as _envoy_config_accesslog_v3_OrFilter__Output } from '../../../../envoy/config/accesslog/v3/OrFilter'; +import type { HeaderFilter as _envoy_config_accesslog_v3_HeaderFilter, HeaderFilter__Output as _envoy_config_accesslog_v3_HeaderFilter__Output } from '../../../../envoy/config/accesslog/v3/HeaderFilter'; +import type { ResponseFlagFilter as _envoy_config_accesslog_v3_ResponseFlagFilter, ResponseFlagFilter__Output as _envoy_config_accesslog_v3_ResponseFlagFilter__Output } from '../../../../envoy/config/accesslog/v3/ResponseFlagFilter'; +import type { GrpcStatusFilter as _envoy_config_accesslog_v3_GrpcStatusFilter, GrpcStatusFilter__Output as _envoy_config_accesslog_v3_GrpcStatusFilter__Output } from '../../../../envoy/config/accesslog/v3/GrpcStatusFilter'; +import type { ExtensionFilter as _envoy_config_accesslog_v3_ExtensionFilter, ExtensionFilter__Output as _envoy_config_accesslog_v3_ExtensionFilter__Output } from '../../../../envoy/config/accesslog/v3/ExtensionFilter'; +import type { MetadataFilter as _envoy_config_accesslog_v3_MetadataFilter, MetadataFilter__Output as _envoy_config_accesslog_v3_MetadataFilter__Output } from '../../../../envoy/config/accesslog/v3/MetadataFilter'; + +/** + * [#next-free-field: 13] + */ +export interface AccessLogFilter { + /** + * Status code filter. + */ + 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter); + /** + * Duration filter. + */ + 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter); + /** + * Not health check filter. + */ + 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter); + /** + * Traceable filter. + */ + 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter); + /** + * Runtime filter. + */ + 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter); + /** + * And filter. + */ + 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter); + /** + * Or filter. + */ + 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter); + /** + * Header filter. + */ + 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter); + /** + * Response flag filter. + */ + 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter); + /** + * gRPC status filter. + */ + 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter); + /** + * Extension filter. + */ + 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter); + /** + * Metadata Filter + */ + 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter); + 'filter_specifier'?: "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; +} + +/** + * [#next-free-field: 13] + */ +export interface AccessLogFilter__Output { + /** + * Status code filter. + */ + 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter__Output); + /** + * Duration filter. + */ + 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter__Output); + /** + * Not health check filter. + */ + 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter__Output); + /** + * Traceable filter. + */ + 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter__Output); + /** + * Runtime filter. + */ + 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter__Output); + /** + * And filter. + */ + 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter__Output); + /** + * Or filter. + */ + 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter__Output); + /** + * Header filter. + */ + 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter__Output); + /** + * Response flag filter. + */ + 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter__Output); + /** + * gRPC status filter. + */ + 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter__Output); + /** + * Extension filter. + */ + 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter__Output); + /** + * Metadata Filter + */ + 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter__Output); + 'filter_specifier': "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AndFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AndFilter.ts similarity index 51% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AndFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AndFilter.ts index 7cf1cb98c..c3c2ac8b4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AndFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AndFilter.ts @@ -1,6 +1,6 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -import type { AccessLogFilter as _envoy_config_filter_accesslog_v2_AccessLogFilter, AccessLogFilter__Output as _envoy_config_filter_accesslog_v2_AccessLogFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/AccessLogFilter'; +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../envoy/config/accesslog/v3/AccessLogFilter'; /** * Performs a logical “and” operation on the result of each filter in filters. @@ -8,7 +8,7 @@ import type { AccessLogFilter as _envoy_config_filter_accesslog_v2_AccessLogFilt * filter returns false immediately. */ export interface AndFilter { - 'filters'?: (_envoy_config_filter_accesslog_v2_AccessLogFilter)[]; + 'filters'?: (_envoy_config_accesslog_v3_AccessLogFilter)[]; } /** @@ -17,5 +17,5 @@ export interface AndFilter { * filter returns false immediately. */ export interface AndFilter__Output { - 'filters': (_envoy_config_filter_accesslog_v2_AccessLogFilter__Output)[]; + 'filters': (_envoy_config_accesslog_v3_AccessLogFilter__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts new file mode 100644 index 000000000..118a47a75 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts @@ -0,0 +1,48 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { RuntimeUInt32 as _envoy_config_core_v3_RuntimeUInt32, RuntimeUInt32__Output as _envoy_config_core_v3_RuntimeUInt32__Output } from '../../../../envoy/config/core/v3/RuntimeUInt32'; + +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +export enum _envoy_config_accesslog_v3_ComparisonFilter_Op { + /** + * = + */ + EQ = 0, + /** + * >= + */ + GE = 1, + /** + * <= + */ + LE = 2, +} + +/** + * Filter on an integer comparison. + */ +export interface ComparisonFilter { + /** + * Comparison operator. + */ + 'op'?: (_envoy_config_accesslog_v3_ComparisonFilter_Op | keyof typeof _envoy_config_accesslog_v3_ComparisonFilter_Op); + /** + * Value to compare against. + */ + 'value'?: (_envoy_config_core_v3_RuntimeUInt32); +} + +/** + * Filter on an integer comparison. + */ +export interface ComparisonFilter__Output { + /** + * Comparison operator. + */ + 'op': (keyof typeof _envoy_config_accesslog_v3_ComparisonFilter_Op); + /** + * Value to compare against. + */ + 'value'?: (_envoy_config_core_v3_RuntimeUInt32__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts new file mode 100644 index 000000000..03b0500e8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { ComparisonFilter as _envoy_config_accesslog_v3_ComparisonFilter, ComparisonFilter__Output as _envoy_config_accesslog_v3_ComparisonFilter__Output } from '../../../../envoy/config/accesslog/v3/ComparisonFilter'; + +/** + * Filters on total request duration in milliseconds. + */ +export interface DurationFilter { + /** + * Comparison. + */ + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter); +} + +/** + * Filters on total request duration in milliseconds. + */ +export interface DurationFilter__Output { + /** + * Comparison. + */ + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ExtensionFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts similarity index 64% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ExtensionFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts index 184c76eb6..127618eef 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ExtensionFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts @@ -1,7 +1,6 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../google/protobuf/Any'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; /** * Extension filter is statically registered at runtime. @@ -12,12 +11,11 @@ export interface ExtensionFilter { * match a statically registered filter. */ 'name'?: (string); - 'config'?: (_google_protobuf_Struct); 'typed_config'?: (_google_protobuf_Any); /** * Custom configuration that depends on the filter being instantiated. */ - 'config_type'?: "config"|"typed_config"; + 'config_type'?: "typed_config"; } /** @@ -29,10 +27,9 @@ export interface ExtensionFilter__Output { * match a statically registered filter. */ 'name': (string); - 'config'?: (_google_protobuf_Struct__Output); 'typed_config'?: (_google_protobuf_Any__Output); /** * Custom configuration that depends on the filter being instantiated. */ - 'config_type': "config"|"typed_config"; + 'config_type': "typed_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/GrpcStatusFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts similarity index 51% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/GrpcStatusFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts index 8e8b0981e..f7ccc8052 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/GrpcStatusFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts @@ -1,9 +1,9 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -export enum _envoy_config_filter_accesslog_v2_GrpcStatusFilter_Status { +export enum _envoy_config_accesslog_v3_GrpcStatusFilter_Status { OK = 0, CANCELED = 1, UNKNOWN = 2, @@ -24,33 +24,35 @@ export enum _envoy_config_filter_accesslog_v2_GrpcStatusFilter_Status { } /** - * Filters gRPC requests based on their response status. If a gRPC status is not provided, the - * filter will infer the status from the HTTP status code. + * Filters gRPC requests based on their response status. If a gRPC status is not + * provided, the filter will infer the status from the HTTP status code. */ export interface GrpcStatusFilter { /** * Logs only responses that have any one of the gRPC statuses in this field. */ - 'statuses'?: (_envoy_config_filter_accesslog_v2_GrpcStatusFilter_Status | keyof typeof _envoy_config_filter_accesslog_v2_GrpcStatusFilter_Status)[]; + 'statuses'?: (_envoy_config_accesslog_v3_GrpcStatusFilter_Status | keyof typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status)[]; /** - * If included and set to true, the filter will instead block all responses with a gRPC status or - * inferred gRPC status enumerated in statuses, and allow all other responses. + * If included and set to true, the filter will instead block all responses + * with a gRPC status or inferred gRPC status enumerated in statuses, and + * allow all other responses. */ 'exclude'?: (boolean); } /** - * Filters gRPC requests based on their response status. If a gRPC status is not provided, the - * filter will infer the status from the HTTP status code. + * Filters gRPC requests based on their response status. If a gRPC status is not + * provided, the filter will infer the status from the HTTP status code. */ export interface GrpcStatusFilter__Output { /** * Logs only responses that have any one of the gRPC statuses in this field. */ - 'statuses': (keyof typeof _envoy_config_filter_accesslog_v2_GrpcStatusFilter_Status)[]; + 'statuses': (keyof typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status)[]; /** - * If included and set to true, the filter will instead block all responses with a gRPC status or - * inferred gRPC status enumerated in statuses, and allow all other responses. + * If included and set to true, the filter will instead block all responses + * with a gRPC status or inferred gRPC status enumerated in statuses, and + * allow all other responses. */ 'exclude': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts new file mode 100644 index 000000000..9cda884b3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; + +/** + * Filters requests based on the presence or value of a request header. + */ +export interface HeaderFilter { + /** + * Only requests with a header which matches the specified HeaderMatcher will + * pass the filter check. + */ + 'header'?: (_envoy_config_route_v3_HeaderMatcher); +} + +/** + * Filters requests based on the presence or value of a request header. + */ +export interface HeaderFilter__Output { + /** + * Only requests with a header which matches the specified HeaderMatcher will + * pass the filter check. + */ + 'header'?: (_envoy_config_route_v3_HeaderMatcher__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts new file mode 100644 index 000000000..7fdd1d447 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts @@ -0,0 +1,48 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { MetadataMatcher as _envoy_type_matcher_v3_MetadataMatcher, MetadataMatcher__Output as _envoy_type_matcher_v3_MetadataMatcher__Output } from '../../../../envoy/type/matcher/v3/MetadataMatcher'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +/** + * Filters based on matching dynamic metadata. + * If the matcher path and key correspond to an existing key in dynamic + * metadata, the request is logged only if the matcher value is equal to the + * metadata value. If the matcher path and key *do not* correspond to an + * existing key in dynamic metadata, the request is logged only if + * match_if_key_not_found is "true" or unset. + */ +export interface MetadataFilter { + /** + * Matcher to check metadata for specified value. For example, to match on the + * access_log_hint metadata, set the filter to "envoy.common" and the path to + * "access_log_hint", and the value to "true". + */ + 'matcher'?: (_envoy_type_matcher_v3_MetadataMatcher); + /** + * Default result if the key does not exist in dynamic metadata: if unset or + * true, then log; if false, then don't log. + */ + 'match_if_key_not_found'?: (_google_protobuf_BoolValue); +} + +/** + * Filters based on matching dynamic metadata. + * If the matcher path and key correspond to an existing key in dynamic + * metadata, the request is logged only if the matcher value is equal to the + * metadata value. If the matcher path and key *do not* correspond to an + * existing key in dynamic metadata, the request is logged only if + * match_if_key_not_found is "true" or unset. + */ +export interface MetadataFilter__Output { + /** + * Matcher to check metadata for specified value. For example, to match on the + * access_log_hint metadata, set the filter to "envoy.common" and the path to + * "access_log_hint", and the value to "true". + */ + 'matcher'?: (_envoy_type_matcher_v3_MetadataMatcher__Output); + /** + * Default result if the key does not exist in dynamic metadata: if unset or + * true, then log; if false, then don't log. + */ + 'match_if_key_not_found'?: (_google_protobuf_BoolValue__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/NotHealthCheckFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/NotHealthCheckFilter.ts similarity index 81% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/NotHealthCheckFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/NotHealthCheckFilter.ts index 3de68f081..40728fc34 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/NotHealthCheckFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/NotHealthCheckFilter.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/OrFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/OrFilter.ts similarity index 50% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/OrFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/OrFilter.ts index 859c1218c..1756d3ad5 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/OrFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/OrFilter.ts @@ -1,6 +1,6 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -import type { AccessLogFilter as _envoy_config_filter_accesslog_v2_AccessLogFilter, AccessLogFilter__Output as _envoy_config_filter_accesslog_v2_AccessLogFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/AccessLogFilter'; +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../envoy/config/accesslog/v3/AccessLogFilter'; /** * Performs a logical “or” operation on the result of each individual filter. @@ -8,7 +8,7 @@ import type { AccessLogFilter as _envoy_config_filter_accesslog_v2_AccessLogFilt * filter returns true immediately. */ export interface OrFilter { - 'filters'?: (_envoy_config_filter_accesslog_v2_AccessLogFilter)[]; + 'filters'?: (_envoy_config_accesslog_v3_AccessLogFilter)[]; } /** @@ -17,5 +17,5 @@ export interface OrFilter { * filter returns true immediately. */ export interface OrFilter__Output { - 'filters': (_envoy_config_filter_accesslog_v2_AccessLogFilter__Output)[]; + 'filters': (_envoy_config_accesslog_v3_AccessLogFilter__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ResponseFlagFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ResponseFlagFilter.ts similarity index 51% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ResponseFlagFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ResponseFlagFilter.ts index 280fbb26a..c45a63054 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ResponseFlagFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ResponseFlagFilter.ts @@ -1,16 +1,17 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto /** * Filters requests that received responses with an Envoy response flag set. * A list of the response flags can be found - * in the access log formatter :ref:`documentation`. + * in the access log formatter + * :ref:`documentation`. */ export interface ResponseFlagFilter { /** - * Only responses with the any of the flags listed in this field will be logged. - * This field is optional. If it is not specified, then any response flag will pass - * the filter check. + * Only responses with the any of the flags listed in this field will be + * logged. This field is optional. If it is not specified, then any response + * flag will pass the filter check. */ 'flags'?: (string)[]; } @@ -18,13 +19,14 @@ export interface ResponseFlagFilter { /** * Filters requests that received responses with an Envoy response flag set. * A list of the response flags can be found - * in the access log formatter :ref:`documentation`. + * in the access log formatter + * :ref:`documentation`. */ export interface ResponseFlagFilter__Output { /** - * Only responses with the any of the flags listed in this field will be logged. - * This field is optional. If it is not specified, then any response flag will pass - * the filter check. + * Only responses with the any of the flags listed in this field will be + * logged. This field is optional. If it is not specified, then any response + * flag will pass the filter check. */ 'flags': (string)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts new file mode 100644 index 000000000..9388f49ec --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts @@ -0,0 +1,73 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; + +/** + * Filters for random sampling of requests. + */ +export interface RuntimeFilter { + /** + * Runtime key to get an optional overridden numerator for use in the + * *percent_sampled* field. If found in runtime, this value will replace the + * default numerator. + */ + 'runtime_key'?: (string); + /** + * The default sampling percentage. If not specified, defaults to 0% with + * denominator of 100. + */ + 'percent_sampled'?: (_envoy_type_v3_FractionalPercent); + /** + * By default, sampling pivots on the header + * :ref:`x-request-id` being + * present. If :ref:`x-request-id` + * is present, the filter will consistently sample across multiple hosts based + * on the runtime key value and the value extracted from + * :ref:`x-request-id`. If it is + * missing, or *use_independent_randomness* is set to true, the filter will + * randomly sample based on the runtime key value alone. + * *use_independent_randomness* can be used for logging kill switches within + * complex nested :ref:`AndFilter + * ` and :ref:`OrFilter + * ` blocks that are easier to + * reason about from a probability perspective (i.e., setting to true will + * cause the filter to behave like an independent random variable when + * composed within logical operator filters). + */ + 'use_independent_randomness'?: (boolean); +} + +/** + * Filters for random sampling of requests. + */ +export interface RuntimeFilter__Output { + /** + * Runtime key to get an optional overridden numerator for use in the + * *percent_sampled* field. If found in runtime, this value will replace the + * default numerator. + */ + 'runtime_key': (string); + /** + * The default sampling percentage. If not specified, defaults to 0% with + * denominator of 100. + */ + 'percent_sampled'?: (_envoy_type_v3_FractionalPercent__Output); + /** + * By default, sampling pivots on the header + * :ref:`x-request-id` being + * present. If :ref:`x-request-id` + * is present, the filter will consistently sample across multiple hosts based + * on the runtime key value and the value extracted from + * :ref:`x-request-id`. If it is + * missing, or *use_independent_randomness* is set to true, the filter will + * randomly sample based on the runtime key value alone. + * *use_independent_randomness* can be used for logging kill switches within + * complex nested :ref:`AndFilter + * ` and :ref:`OrFilter + * ` blocks that are easier to + * reason about from a probability perspective (i.e., setting to true will + * cause the filter to behave like an independent random variable when + * composed within logical operator filters). + */ + 'use_independent_randomness': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts new file mode 100644 index 000000000..313ed86ca --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { ComparisonFilter as _envoy_config_accesslog_v3_ComparisonFilter, ComparisonFilter__Output as _envoy_config_accesslog_v3_ComparisonFilter__Output } from '../../../../envoy/config/accesslog/v3/ComparisonFilter'; + +/** + * Filters on HTTP response/status code. + */ +export interface StatusCodeFilter { + /** + * Comparison. + */ + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter); +} + +/** + * Filters on HTTP response/status code. + */ +export interface StatusCodeFilter__Output { + /** + * Comparison. + */ + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/TraceableFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/TraceableFilter.ts similarity index 81% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/TraceableFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/TraceableFilter.ts index 8de24656a..c2b3a646b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/TraceableFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/TraceableFilter.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto /** diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/CircuitBreakers.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts similarity index 75% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/CircuitBreakers.ts rename to packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts index edf946590..c4f4add69 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/CircuitBreakers.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts @@ -1,10 +1,10 @@ -// Original file: deps/envoy-api/envoy/api/v2/cluster/circuit_breaker.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/circuit_breaker.proto -import type { RoutingPriority as _envoy_api_v2_core_RoutingPriority } from '../../../../envoy/api/v2/core/RoutingPriority'; +import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority } from '../../../../envoy/config/core/v3/RoutingPriority'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -import type { Percent as _envoy_type_Percent, Percent__Output as _envoy_type_Percent__Output } from '../../../../envoy/type/Percent'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; -export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget { +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget { /** * Specifies the limit on concurrent retries as a percentage of the sum of active requests and * active pending requests. For example, if there are 100 active requests and the @@ -12,7 +12,7 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget { * * This parameter is optional. Defaults to 20%. */ - 'budget_percent'?: (_envoy_type_Percent); + 'budget_percent'?: (_envoy_type_v3_Percent); /** * Specifies the minimum retry concurrency allowed for the retry budget. The limit on the * number of active retries may never go below this number. @@ -22,7 +22,7 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget { 'min_retry_concurrency'?: (_google_protobuf_UInt32Value); } -export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget__Output { +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget__Output { /** * Specifies the limit on concurrent retries as a percentage of the sum of active requests and * active pending requests. For example, if there are 100 active requests and the @@ -30,7 +30,7 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget__O * * This parameter is optional. Defaults to 20%. */ - 'budget_percent'?: (_envoy_type_Percent__Output); + 'budget_percent'?: (_envoy_type_v3_Percent__Output); /** * Specifies the minimum retry concurrency allowed for the retry budget. The limit on the * number of active retries may never go below this number. @@ -42,15 +42,15 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget__O /** * A Thresholds defines CircuitBreaker settings for a - * :ref:`RoutingPriority`. + * :ref:`RoutingPriority`. * [#next-free-field: 9] */ -export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds { +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { /** - * The :ref:`RoutingPriority` + * The :ref:`RoutingPriority` * the specified CircuitBreaker settings apply to. */ - 'priority'?: (_envoy_api_v2_core_RoutingPriority | keyof typeof _envoy_api_v2_core_RoutingPriority); + 'priority'?: (_envoy_config_core_v3_RoutingPriority | keyof typeof _envoy_config_core_v3_RoutingPriority); /** * The maximum number of connections that Envoy will make to the upstream * cluster. If not specified, the default is 1024. @@ -80,7 +80,7 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds { * If this field is set, the retry budget will override any configured retry circuit * breaker. */ - 'retry_budget'?: (_envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget); + 'retry_budget'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget); /** * If track_remaining is true, then stats will be published that expose * the number of resources remaining until the circuit breakers open. If @@ -104,15 +104,15 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds { /** * A Thresholds defines CircuitBreaker settings for a - * :ref:`RoutingPriority`. + * :ref:`RoutingPriority`. * [#next-free-field: 9] */ -export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds__Output { +export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { /** - * The :ref:`RoutingPriority` + * The :ref:`RoutingPriority` * the specified CircuitBreaker settings apply to. */ - 'priority': (keyof typeof _envoy_api_v2_core_RoutingPriority); + 'priority': (keyof typeof _envoy_config_core_v3_RoutingPriority); /** * The maximum number of connections that Envoy will make to the upstream * cluster. If not specified, the default is 1024. @@ -142,7 +142,7 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds__Output { * If this field is set, the retry budget will override any configured retry circuit * breaker. */ - 'retry_budget'?: (_envoy_api_v2_cluster_CircuitBreakers_Thresholds_RetryBudget__Output); + 'retry_budget'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget__Output); /** * If track_remaining is true, then stats will be published that expose * the number of resources remaining until the circuit breakers open. If @@ -170,13 +170,13 @@ export interface _envoy_api_v2_cluster_CircuitBreakers_Thresholds__Output { */ export interface CircuitBreakers { /** - * If multiple :ref:`Thresholds` - * are defined with the same :ref:`RoutingPriority`, + * If multiple :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, * the first one in the list is used. If no Thresholds is defined for a given - * :ref:`RoutingPriority`, the default values + * :ref:`RoutingPriority`, the default values * are used. */ - 'thresholds'?: (_envoy_api_v2_cluster_CircuitBreakers_Thresholds)[]; + 'thresholds'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds)[]; } /** @@ -185,11 +185,11 @@ export interface CircuitBreakers { */ export interface CircuitBreakers__Output { /** - * If multiple :ref:`Thresholds` - * are defined with the same :ref:`RoutingPriority`, + * If multiple :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, * the first one in the list is used. If no Thresholds is defined for a given - * :ref:`RoutingPriority`, the default values + * :ref:`RoutingPriority`, the default values * are used. */ - 'thresholds': (_envoy_api_v2_cluster_CircuitBreakers_Thresholds__Output)[]; + 'thresholds': (_envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/Cluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts similarity index 50% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/Cluster.ts rename to packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts index f077d2edc..fa76fba89 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/Cluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts @@ -1,36 +1,39 @@ -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../google/protobuf/UInt32Value'; -import type { Address as _envoy_api_v2_core_Address, Address__Output as _envoy_api_v2_core_Address__Output } from '../../../envoy/api/v2/core/Address'; -import type { HealthCheck as _envoy_api_v2_core_HealthCheck, HealthCheck__Output as _envoy_api_v2_core_HealthCheck__Output } from '../../../envoy/api/v2/core/HealthCheck'; -import type { CircuitBreakers as _envoy_api_v2_cluster_CircuitBreakers, CircuitBreakers__Output as _envoy_api_v2_cluster_CircuitBreakers__Output } from '../../../envoy/api/v2/cluster/CircuitBreakers'; -import type { UpstreamTlsContext as _envoy_api_v2_auth_UpstreamTlsContext, UpstreamTlsContext__Output as _envoy_api_v2_auth_UpstreamTlsContext__Output } from '../../../envoy/api/v2/auth/UpstreamTlsContext'; -import type { Http1ProtocolOptions as _envoy_api_v2_core_Http1ProtocolOptions, Http1ProtocolOptions__Output as _envoy_api_v2_core_Http1ProtocolOptions__Output } from '../../../envoy/api/v2/core/Http1ProtocolOptions'; -import type { Http2ProtocolOptions as _envoy_api_v2_core_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_api_v2_core_Http2ProtocolOptions__Output } from '../../../envoy/api/v2/core/Http2ProtocolOptions'; -import type { OutlierDetection as _envoy_api_v2_cluster_OutlierDetection, OutlierDetection__Output as _envoy_api_v2_cluster_OutlierDetection__Output } from '../../../envoy/api/v2/cluster/OutlierDetection'; -import type { BindConfig as _envoy_api_v2_core_BindConfig, BindConfig__Output as _envoy_api_v2_core_BindConfig__Output } from '../../../envoy/api/v2/core/BindConfig'; -import type { TransportSocket as _envoy_api_v2_core_TransportSocket, TransportSocket__Output as _envoy_api_v2_core_TransportSocket__Output } from '../../../envoy/api/v2/core/TransportSocket'; -import type { Metadata as _envoy_api_v2_core_Metadata, Metadata__Output as _envoy_api_v2_core_Metadata__Output } from '../../../envoy/api/v2/core/Metadata'; -import type { HttpProtocolOptions as _envoy_api_v2_core_HttpProtocolOptions, HttpProtocolOptions__Output as _envoy_api_v2_core_HttpProtocolOptions__Output } from '../../../envoy/api/v2/core/HttpProtocolOptions'; -import type { UpstreamConnectionOptions as _envoy_api_v2_UpstreamConnectionOptions, UpstreamConnectionOptions__Output as _envoy_api_v2_UpstreamConnectionOptions__Output } from '../../../envoy/api/v2/UpstreamConnectionOptions'; -import type { ClusterLoadAssignment as _envoy_api_v2_ClusterLoadAssignment, ClusterLoadAssignment__Output as _envoy_api_v2_ClusterLoadAssignment__Output } from '../../../envoy/api/v2/ClusterLoadAssignment'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; -import type { Filter as _envoy_api_v2_cluster_Filter, Filter__Output as _envoy_api_v2_cluster_Filter__Output } from '../../../envoy/api/v2/cluster/Filter'; -import type { LoadBalancingPolicy as _envoy_api_v2_LoadBalancingPolicy, LoadBalancingPolicy__Output as _envoy_api_v2_LoadBalancingPolicy__Output } from '../../../envoy/api/v2/LoadBalancingPolicy'; -import type { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../envoy/api/v2/core/ConfigSource'; -import type { UpstreamHttpProtocolOptions as _envoy_api_v2_core_UpstreamHttpProtocolOptions, UpstreamHttpProtocolOptions__Output as _envoy_api_v2_core_UpstreamHttpProtocolOptions__Output } from '../../../envoy/api/v2/core/UpstreamHttpProtocolOptions'; -import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../google/protobuf/UInt64Value'; -import type { Percent as _envoy_type_Percent, Percent__Output as _envoy_type_Percent__Output } from '../../../envoy/type/Percent'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { HealthCheck as _envoy_config_core_v3_HealthCheck, HealthCheck__Output as _envoy_config_core_v3_HealthCheck__Output } from '../../../../envoy/config/core/v3/HealthCheck'; +import type { CircuitBreakers as _envoy_config_cluster_v3_CircuitBreakers, CircuitBreakers__Output as _envoy_config_cluster_v3_CircuitBreakers__Output } from '../../../../envoy/config/cluster/v3/CircuitBreakers'; +import type { Http1ProtocolOptions as _envoy_config_core_v3_Http1ProtocolOptions, Http1ProtocolOptions__Output as _envoy_config_core_v3_Http1ProtocolOptions__Output } from '../../../../envoy/config/core/v3/Http1ProtocolOptions'; +import type { Http2ProtocolOptions as _envoy_config_core_v3_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_config_core_v3_Http2ProtocolOptions__Output } from '../../../../envoy/config/core/v3/Http2ProtocolOptions'; +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { OutlierDetection as _envoy_config_cluster_v3_OutlierDetection, OutlierDetection__Output as _envoy_config_cluster_v3_OutlierDetection__Output } from '../../../../envoy/config/cluster/v3/OutlierDetection'; +import type { BindConfig as _envoy_config_core_v3_BindConfig, BindConfig__Output as _envoy_config_core_v3_BindConfig__Output } from '../../../../envoy/config/core/v3/BindConfig'; +import type { TransportSocket as _envoy_config_core_v3_TransportSocket, TransportSocket__Output as _envoy_config_core_v3_TransportSocket__Output } from '../../../../envoy/config/core/v3/TransportSocket'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { HttpProtocolOptions as _envoy_config_core_v3_HttpProtocolOptions, HttpProtocolOptions__Output as _envoy_config_core_v3_HttpProtocolOptions__Output } from '../../../../envoy/config/core/v3/HttpProtocolOptions'; +import type { UpstreamConnectionOptions as _envoy_config_cluster_v3_UpstreamConnectionOptions, UpstreamConnectionOptions__Output as _envoy_config_cluster_v3_UpstreamConnectionOptions__Output } from '../../../../envoy/config/cluster/v3/UpstreamConnectionOptions'; +import type { ClusterLoadAssignment as _envoy_config_endpoint_v3_ClusterLoadAssignment, ClusterLoadAssignment__Output as _envoy_config_endpoint_v3_ClusterLoadAssignment__Output } from '../../../../envoy/config/endpoint/v3/ClusterLoadAssignment'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Filter as _envoy_config_cluster_v3_Filter, Filter__Output as _envoy_config_cluster_v3_Filter__Output } from '../../../../envoy/config/cluster/v3/Filter'; +import type { LoadBalancingPolicy as _envoy_config_cluster_v3_LoadBalancingPolicy, LoadBalancingPolicy__Output as _envoy_config_cluster_v3_LoadBalancingPolicy__Output } from '../../../../envoy/config/cluster/v3/LoadBalancingPolicy'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; +import type { UpstreamHttpProtocolOptions as _envoy_config_core_v3_UpstreamHttpProtocolOptions, UpstreamHttpProtocolOptions__Output as _envoy_config_core_v3_UpstreamHttpProtocolOptions__Output } from '../../../../envoy/config/core/v3/UpstreamHttpProtocolOptions'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { TrackClusterStats as _envoy_config_cluster_v3_TrackClusterStats, TrackClusterStats__Output as _envoy_config_cluster_v3_TrackClusterStats__Output } from '../../../../envoy/config/cluster/v3/TrackClusterStats'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { RuntimeDouble as _envoy_config_core_v3_RuntimeDouble, RuntimeDouble__Output as _envoy_config_core_v3_RuntimeDouble__Output } from '../../../../envoy/config/core/v3/RuntimeDouble'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; +import type { DoubleValue as _google_protobuf_DoubleValue, DoubleValue__Output as _google_protobuf_DoubleValue__Output } from '../../../../google/protobuf/DoubleValue'; import type { Long } from '@grpc/proto-loader'; -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto -export enum _envoy_api_v2_Cluster_ClusterProtocolSelection { +export enum _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection { /** * Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). - * If :ref:`http2_protocol_options ` are + * If :ref:`http2_protocol_options ` are * present, HTTP2 will be used, otherwise HTTP1.1 will be used. */ USE_CONFIGURED_PROTOCOL = 0, @@ -44,7 +47,7 @@ export enum _envoy_api_v2_Cluster_ClusterProtocolSelection { * Common configuration for all load balancer implementations. * [#next-free-field: 8] */ -export interface _envoy_api_v2_Cluster_CommonLbConfig { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { /** * Configures the :ref:`healthy panic threshold `. * If not specified, the default is 50%. @@ -53,9 +56,9 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig { * .. note:: * The specified percent will be truncated to the nearest 1%. */ - 'healthy_panic_threshold'?: (_envoy_type_Percent); - 'zone_aware_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig_ZoneAwareLbConfig); - 'locality_weighted_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig_LocalityWeightedLbConfig); + 'healthy_panic_threshold'?: (_envoy_type_v3_Percent); + 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig); + 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig); /** * If set, all health check/weight/metadata updates that happen within this duration will be * merged and delivered in one shot when the duration expires. The start of the duration is when @@ -74,25 +77,9 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig { */ 'update_merge_window'?: (_google_protobuf_Duration); /** - * If set to true, Envoy will not consider new hosts when computing load balancing weights until - * they have been health checked for the first time. This will have no effect unless - * active health checking is also configured. - * - * Ignoring a host means that for any load balancing calculations that adjust weights based - * on the ratio of eligible hosts and total hosts (priority spillover, locality weighting and - * panic mode) Envoy will exclude these hosts in the denominator. - * - * For example, with hosts in two priorities P0 and P1, where P0 looks like - * {healthy, unhealthy (new), unhealthy (new)} - * and where P1 looks like - * {healthy, healthy} - * all traffic will still hit P0, as 1 / (3 - 2) = 1. - * - * Enabling this will allow scaling up the number of hosts for a given cluster without entering - * panic mode or triggering priority spillover, assuming the hosts pass the first health check. - * - * If panic mode is triggered, new hosts are still eligible for traffic; they simply do not - * contribute to the calculation when deciding whether panic mode is enabled or not. + * If set to true, Envoy will :ref:`exclude ` new hosts + * when computing load balancing weights until they have been health checked for the first time. + * This will have no effect unless active health checking is also configured. */ 'ignore_new_hosts_until_first_hc'?: (boolean); /** @@ -103,7 +90,7 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig { /** * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ - 'consistent_hashing_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig_ConsistentHashingLbConfig); + 'consistent_hashing_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig); 'locality_config_specifier'?: "zone_aware_lb_config"|"locality_weighted_lb_config"; } @@ -111,7 +98,7 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig { * Common configuration for all load balancer implementations. * [#next-free-field: 8] */ -export interface _envoy_api_v2_Cluster_CommonLbConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { /** * Configures the :ref:`healthy panic threshold `. * If not specified, the default is 50%. @@ -120,9 +107,9 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig__Output { * .. note:: * The specified percent will be truncated to the nearest 1%. */ - 'healthy_panic_threshold'?: (_envoy_type_Percent__Output); - 'zone_aware_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output); - 'locality_weighted_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output); + 'healthy_panic_threshold'?: (_envoy_type_v3_Percent__Output); + 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output); + 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output); /** * If set, all health check/weight/metadata updates that happen within this duration will be * merged and delivered in one shot when the duration expires. The start of the duration is when @@ -141,25 +128,9 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig__Output { */ 'update_merge_window'?: (_google_protobuf_Duration__Output); /** - * If set to true, Envoy will not consider new hosts when computing load balancing weights until - * they have been health checked for the first time. This will have no effect unless - * active health checking is also configured. - * - * Ignoring a host means that for any load balancing calculations that adjust weights based - * on the ratio of eligible hosts and total hosts (priority spillover, locality weighting and - * panic mode) Envoy will exclude these hosts in the denominator. - * - * For example, with hosts in two priorities P0 and P1, where P0 looks like - * {healthy, unhealthy (new), unhealthy (new)} - * and where P1 looks like - * {healthy, healthy} - * all traffic will still hit P0, as 1 / (3 - 2) = 1. - * - * Enabling this will allow scaling up the number of hosts for a given cluster without entering - * panic mode or triggering priority spillover, assuming the hosts pass the first health check. - * - * If panic mode is triggered, new hosts are still eligible for traffic; they simply do not - * contribute to the calculation when deciding whether panic mode is enabled or not. + * If set to true, Envoy will :ref:`exclude ` new hosts + * when computing load balancing weights until they have been health checked for the first time. + * This will have no effect unless active health checking is also configured. */ 'ignore_new_hosts_until_first_hc': (boolean); /** @@ -170,36 +141,78 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig__Output { /** * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ - 'consistent_hashing_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output); + 'consistent_hashing_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output); 'locality_config_specifier': "zone_aware_lb_config"|"locality_weighted_lb_config"; } /** * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ -export interface _envoy_api_v2_Cluster_CommonLbConfig_ConsistentHashingLbConfig { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig { /** * If set to `true`, the cluster will use hostname instead of the resolved * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. */ 'use_hostname_for_hashing'?: (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * Applies to both Ring Hash and Maglev load balancers. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * being probed, so use a higher value if you require better performance. + */ + 'hash_balance_factor'?: (_google_protobuf_UInt32Value); } /** * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ -export interface _envoy_api_v2_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output { /** * If set to `true`, the cluster will use hostname instead of the resolved * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. */ 'use_hostname_for_hashing': (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * Applies to both Ring Hash and Maglev load balancers. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * being probed, so use a higher value if you require better performance. + */ + 'hash_balance_factor'?: (_google_protobuf_UInt32Value__Output); } /** * Extended cluster type. */ -export interface _envoy_api_v2_Cluster_CustomClusterType { +export interface _envoy_config_cluster_v3_Cluster_CustomClusterType { /** * The type of the cluster to instantiate. The name must match a supported cluster type. */ @@ -214,7 +227,7 @@ export interface _envoy_api_v2_Cluster_CustomClusterType { /** * Extended cluster type. */ -export interface _envoy_api_v2_Cluster_CustomClusterType__Output { +export interface _envoy_config_cluster_v3_Cluster_CustomClusterType__Output { /** * The type of the cluster to instantiate. The name must match a supported cluster type. */ @@ -226,13 +239,13 @@ export interface _envoy_api_v2_Cluster_CustomClusterType__Output { 'typed_config'?: (_google_protobuf_Any__Output); } -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto /** * Refer to :ref:`service discovery type ` * for an explanation on each type. */ -export enum _envoy_api_v2_Cluster_DiscoveryType { +export enum _envoy_config_cluster_v3_Cluster_DiscoveryType { /** * Refer to the :ref:`static discovery type` * for an explanation. @@ -263,7 +276,7 @@ export enum _envoy_api_v2_Cluster_DiscoveryType { ORIGINAL_DST = 4, } -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto /** * When V4_ONLY is selected, the DNS resolver will only perform a lookup for @@ -272,12 +285,12 @@ export enum _envoy_api_v2_Cluster_DiscoveryType { * specified, the DNS resolver will first perform a lookup for addresses in * the IPv6 family and fallback to a lookup for addresses in the IPv4 family. * For cluster types other than - * :ref:`STRICT_DNS` and - * :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS`, * this setting is * ignored. */ -export enum _envoy_api_v2_Cluster_DnsLookupFamily { +export enum _envoy_config_cluster_v3_Cluster_DnsLookupFamily { AUTO = 0, V4_ONLY = 1, V6_ONLY = 2, @@ -286,15 +299,15 @@ export enum _envoy_api_v2_Cluster_DnsLookupFamily { /** * Only valid when discovery type is EDS. */ -export interface _envoy_api_v2_Cluster_EdsClusterConfig { +export interface _envoy_config_cluster_v3_Cluster_EdsClusterConfig { /** * Configuration for the source of EDS updates for this Cluster. */ - 'eds_config'?: (_envoy_api_v2_core_ConfigSource); + 'eds_config'?: (_envoy_config_core_v3_ConfigSource); /** * Optional alternative to cluster name to present to EDS. This does not * have the same restrictions as cluster name, i.e. it may be arbitrary - * length. + * length. This may be a xdstp:// URL. */ 'service_name'?: (string); } @@ -302,25 +315,25 @@ export interface _envoy_api_v2_Cluster_EdsClusterConfig { /** * Only valid when discovery type is EDS. */ -export interface _envoy_api_v2_Cluster_EdsClusterConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output { /** * Configuration for the source of EDS updates for this Cluster. */ - 'eds_config'?: (_envoy_api_v2_core_ConfigSource__Output); + 'eds_config'?: (_envoy_config_core_v3_ConfigSource__Output); /** * Optional alternative to cluster name to present to EDS. This does not * have the same restrictions as cluster name, i.e. it may be arbitrary - * length. + * length. This may be a xdstp:// URL. */ 'service_name': (string); } -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto /** * The hash function used to hash hosts onto the ketama ring. */ -export enum _envoy_api_v2_Cluster_RingHashLbConfig_HashFunction { +export enum _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction { /** * Use `xxHash `_, this is the default hash function. */ @@ -333,13 +346,13 @@ export enum _envoy_api_v2_Cluster_RingHashLbConfig_HashFunction { MURMUR_HASH_2 = 1, } -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto /** * Refer to :ref:`load balancer type ` architecture * overview section for information on each type. */ -export enum _envoy_api_v2_Cluster_LbPolicy { +export enum _envoy_config_cluster_v3_Cluster_LbPolicy { /** * Refer to the :ref:`round robin load balancing * policy` @@ -364,16 +377,6 @@ export enum _envoy_api_v2_Cluster_LbPolicy { * for an explanation. */ RANDOM = 3, - /** - * Refer to the :ref:`original destination load balancing - * policy` - * for an explanation. - * - * .. attention:: - * - * **This load balancing policy is deprecated**. Use CLUSTER_PROVIDED instead. - */ - ORIGINAL_DST_LB = 4, /** * Refer to the :ref:`Maglev load balancing policy` * for an explanation. @@ -387,7 +390,7 @@ export enum _envoy_api_v2_Cluster_LbPolicy { CLUSTER_PROVIDED = 6, /** * [#not-implemented-hide:] Use the new :ref:`load_balancing_policy - * ` field to determine the LB policy. + * ` field to determine the LB policy. * [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field * and instead using the new load_balancing_policy field as the one and only mechanism for * configuring this.] @@ -400,22 +403,22 @@ export enum _envoy_api_v2_Cluster_LbPolicy { * endpoint metadata and selected by route and weighted cluster metadata. * [#next-free-field: 8] */ -export interface _envoy_api_v2_Cluster_LbSubsetConfig { +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { /** * The behavior used when no endpoint subset matches the selected route's * metadata. The value defaults to - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ - 'fallback_policy'?: (_envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy | keyof typeof _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); + 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); /** * Specifies the default subset of endpoints used during fallback if * fallback_policy is - * :ref:`DEFAULT_SUBSET`. + * :ref:`DEFAULT_SUBSET`. * Each field in default_subset is * compared to the matching LbEndpoint.Metadata under the *envoy.lb* * namespace. It is valid for no hosts to match, in which case the behavior * is the same as a fallback_policy of - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ 'default_subset'?: (_google_protobuf_Struct); /** @@ -434,7 +437,7 @@ export interface _envoy_api_v2_Cluster_LbSubsetConfig { * weighted cluster contains the same keys and values as the subset's * metadata. The same host may appear in multiple subsets. */ - 'subset_selectors'?: (_envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector)[]; + 'subset_selectors'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector)[]; /** * If true, routing to subsets will take into account the localities and locality weights of the * endpoints when making the routing decision. @@ -477,22 +480,22 @@ export interface _envoy_api_v2_Cluster_LbSubsetConfig { * endpoint metadata and selected by route and weighted cluster metadata. * [#next-free-field: 8] */ -export interface _envoy_api_v2_Cluster_LbSubsetConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { /** * The behavior used when no endpoint subset matches the selected route's * metadata. The value defaults to - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ - 'fallback_policy': (keyof typeof _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); + 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); /** * Specifies the default subset of endpoints used during fallback if * fallback_policy is - * :ref:`DEFAULT_SUBSET`. + * :ref:`DEFAULT_SUBSET`. * Each field in default_subset is * compared to the matching LbEndpoint.Metadata under the *envoy.lb* * namespace. It is valid for no hosts to match, in which case the behavior * is the same as a fallback_policy of - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ 'default_subset'?: (_google_protobuf_Struct__Output); /** @@ -511,7 +514,7 @@ export interface _envoy_api_v2_Cluster_LbSubsetConfig__Output { * weighted cluster contains the same keys and values as the subset's * metadata. The same host may appear in multiple subsets. */ - 'subset_selectors': (_envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector__Output)[]; + 'subset_selectors': (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector__Output)[]; /** * If true, routing to subsets will take into account the localities and locality weights of the * endpoints when making the routing decision. @@ -549,7 +552,7 @@ export interface _envoy_api_v2_Cluster_LbSubsetConfig__Output { 'list_as_any': (boolean); } -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto /** * If NO_FALLBACK is selected, a result @@ -558,7 +561,7 @@ export interface _envoy_api_v2_Cluster_LbSubsetConfig__Output { * etc). If DEFAULT_SUBSET is selected, load balancing is performed over the * endpoints matching the values from the default_subset field. */ -export enum _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy { +export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy { NO_FALLBACK = 0, ANY_ENDPOINT = 1, DEFAULT_SUBSET = 2, @@ -567,25 +570,40 @@ export enum _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy { /** * Specifications for subsets. */ -export interface _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector { +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector { /** * List of keys to match with the weighted cluster metadata. */ 'keys'?: (string)[]; + /** + * Selects a mode of operation in which each subset has only one host. This mode uses the same rules for + * choosing a host, but updating hosts is faster, especially for large numbers of hosts. + * + * If a match is found to a host, that host will be used regardless of priority levels, unless the host is unhealthy. + * + * Currently, this mode is only supported if `subset_selectors` has only one entry, and `keys` contains + * only one entry. + * + * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in `keys` + * will use only one of the hosts with the given key; no requests will be routed to the others. The cluster gauge + * :ref:`lb_subsets_single_host_per_subset_duplicate` indicates how many duplicates are + * present in the current configuration. + */ + 'single_host_per_subset'?: (boolean); /** * The behavior used when no endpoint subset matches the selected route's * metadata. */ - 'fallback_policy'?: (_envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy | keyof typeof _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); + 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); /** * Subset of - * :ref:`keys` used by - * :ref:`KEYS_SUBSET` + * :ref:`keys` used by + * :ref:`KEYS_SUBSET` * fallback policy. * It has to be a non empty list if KEYS_SUBSET fallback policy is selected. * For any other fallback policy the parameter is not used and should not be set. * Only values also present in - * :ref:`keys` are allowed, but + * :ref:`keys` are allowed, but * `fallback_keys_subset` cannot be equal to `keys`. */ 'fallback_keys_subset'?: (string)[]; @@ -594,36 +612,51 @@ export interface _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector { /** * Specifications for subsets. */ -export interface _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector__Output { +export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector__Output { /** * List of keys to match with the weighted cluster metadata. */ 'keys': (string)[]; + /** + * Selects a mode of operation in which each subset has only one host. This mode uses the same rules for + * choosing a host, but updating hosts is faster, especially for large numbers of hosts. + * + * If a match is found to a host, that host will be used regardless of priority levels, unless the host is unhealthy. + * + * Currently, this mode is only supported if `subset_selectors` has only one entry, and `keys` contains + * only one entry. + * + * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in `keys` + * will use only one of the hosts with the given key; no requests will be routed to the others. The cluster gauge + * :ref:`lb_subsets_single_host_per_subset_duplicate` indicates how many duplicates are + * present in the current configuration. + */ + 'single_host_per_subset': (boolean); /** * The behavior used when no endpoint subset matches the selected route's * metadata. */ - 'fallback_policy': (keyof typeof _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); + 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); /** * Subset of - * :ref:`keys` used by - * :ref:`KEYS_SUBSET` + * :ref:`keys` used by + * :ref:`KEYS_SUBSET` * fallback policy. * It has to be a non empty list if KEYS_SUBSET fallback policy is selected. * For any other fallback policy the parameter is not used and should not be set. * Only values also present in - * :ref:`keys` are allowed, but + * :ref:`keys` are allowed, but * `fallback_keys_subset` cannot be equal to `keys`. */ 'fallback_keys_subset': (string)[]; } -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto /** * Allows to override top level fallback policy per selector. */ -export enum _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy { +export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy { /** * If NOT_DEFINED top level config fallback policy is used instead. */ @@ -645,7 +678,7 @@ export enum _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelect /** * If KEYS_SUBSET is selected, subset selector matching is performed again with metadata * keys reduced to - * :ref:`fallback_keys_subset`. + * :ref:`fallback_keys_subset`. * It allows for a fallback to a different, less specific selector if some of the keys of * the selector are considered optional. */ @@ -655,37 +688,117 @@ export enum _envoy_api_v2_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelect /** * Specific configuration for the LeastRequest load balancing policy. */ -export interface _envoy_api_v2_Cluster_LeastRequestLbConfig { +export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig { /** * The number of random healthy hosts from which the host with the fewest active requests will * be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. */ 'choice_count'?: (_google_protobuf_UInt32Value); + /** + * The following formula is used to calculate the dynamic weights when hosts have different load + * balancing weights: + * + * `weight = load_balancing_weight / (active_requests + 1)^active_request_bias` + * + * The larger the active request bias is, the more aggressively active requests will lower the + * effective weight when all host weights are not equal. + * + * `active_request_bias` must be greater than or equal to 0.0. + * + * When `active_request_bias == 0.0` the Least Request Load Balancer doesn't consider the number + * of active requests at the time it picks a host and behaves like the Round Robin Load + * Balancer. + * + * When `active_request_bias > 0.0` the Least Request Load Balancer scales the load balancing + * weight by the number of active requests at the time it does a pick. + * + * The value is cached for performance reasons and refreshed whenever one of the Load Balancer's + * host sets changes, e.g., whenever there is a host membership update or a host load balancing + * weight change. + * + * .. note:: + * This setting only takes effect if all host weights are not equal. + */ + 'active_request_bias'?: (_envoy_config_core_v3_RuntimeDouble); } /** * Specific configuration for the LeastRequest load balancing policy. */ -export interface _envoy_api_v2_Cluster_LeastRequestLbConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output { /** * The number of random healthy hosts from which the host with the fewest active requests will * be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. */ 'choice_count'?: (_google_protobuf_UInt32Value__Output); + /** + * The following formula is used to calculate the dynamic weights when hosts have different load + * balancing weights: + * + * `weight = load_balancing_weight / (active_requests + 1)^active_request_bias` + * + * The larger the active request bias is, the more aggressively active requests will lower the + * effective weight when all host weights are not equal. + * + * `active_request_bias` must be greater than or equal to 0.0. + * + * When `active_request_bias == 0.0` the Least Request Load Balancer doesn't consider the number + * of active requests at the time it picks a host and behaves like the Round Robin Load + * Balancer. + * + * When `active_request_bias > 0.0` the Least Request Load Balancer scales the load balancing + * weight by the number of active requests at the time it does a pick. + * + * The value is cached for performance reasons and refreshed whenever one of the Load Balancer's + * host sets changes, e.g., whenever there is a host membership update or a host load balancing + * weight change. + * + * .. note:: + * This setting only takes effect if all host weights are not equal. + */ + 'active_request_bias'?: (_envoy_config_core_v3_RuntimeDouble__Output); } /** * Configuration for :ref:`locality weighted load balancing * ` */ -export interface _envoy_api_v2_Cluster_CommonLbConfig_LocalityWeightedLbConfig { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig { } /** * Configuration for :ref:`locality weighted load balancing * ` */ -export interface _envoy_api_v2_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output { +} + +/** + * Specific configuration for the :ref:`Maglev` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig { + /** + * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. + * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same + * upstream as it was before. Increasing the table size reduces the amount of disruption. + * The table size must be prime number. If it is not specified, the default is 65537. + */ + 'table_size'?: (_google_protobuf_UInt64Value); +} + +/** + * Specific configuration for the :ref:`Maglev` + * load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output { + /** + * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. + * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same + * upstream as it was before. Increasing the table size reduces the amount of disruption. + * The table size must be prime number. If it is not specified, the default is 65537. + */ + 'table_size'?: (_google_protobuf_UInt64Value__Output); } /** @@ -693,7 +806,7 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig_LocalityWeightedLbConfig__ * :ref:`Original Destination ` * load balancing policy. */ -export interface _envoy_api_v2_Cluster_OriginalDstLbConfig { +export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig { /** * When true, :ref:`x-envoy-original-dst-host * ` can be used to override destination @@ -704,6 +817,10 @@ export interface _envoy_api_v2_Cluster_OriginalDstLbConfig { * This header isn't sanitized by default, so enabling this feature allows HTTP clients to * route traffic to arbitrary hosts and/or ports, which may have serious security * consequences. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ 'use_http_header'?: (boolean); } @@ -713,7 +830,7 @@ export interface _envoy_api_v2_Cluster_OriginalDstLbConfig { * :ref:`Original Destination ` * load balancing policy. */ -export interface _envoy_api_v2_Cluster_OriginalDstLbConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output { /** * When true, :ref:`x-envoy-original-dst-host * ` can be used to override destination @@ -724,38 +841,152 @@ export interface _envoy_api_v2_Cluster_OriginalDstLbConfig__Output { * This header isn't sanitized by default, so enabling this feature allows HTTP clients to * route traffic to arbitrary hosts and/or ports, which may have serious security * consequences. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ 'use_http_header': (boolean); } -export interface _envoy_api_v2_Cluster_RefreshRate { +export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy { + /** + * Indicates how many streams (rounded up) can be anticipated per-upstream for each + * incoming stream. This is useful for high-QPS or latency-sensitive services. Preconnecting + * will only be done if the upstream is healthy and the cluster has traffic. + * + * For example if this is 2, for an incoming HTTP/1.1 stream, 2 connections will be + * established, one for the new incoming stream, and one for a presumed follow-up stream. For + * HTTP/2, only one connection would be established by default as one connection can + * serve both the original and presumed follow-up stream. + * + * In steady state for non-multiplexed connections a value of 1.5 would mean if there were 100 + * active streams, there would be 100 connections in use, and 50 connections preconnected. + * This might be a useful value for something like short lived single-use connections, + * for example proxying HTTP/1.1 if keep-alive were false and each stream resulted in connection + * termination. It would likely be overkill for long lived connections, such as TCP proxying SMTP + * or regular HTTP/1.1 with keep-alive. For long lived traffic, a value of 1.05 would be more + * reasonable, where for every 100 connections, 5 preconnected connections would be in the queue + * in case of unexpected disconnects where the connection could not be reused. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight. This means in steady state if a connection is torn down, + * a subsequent streams will pay an upstream-rtt latency penalty waiting for a new connection. + * + * This is limited somewhat arbitrarily to 3 because preconnecting too aggressively can + * harm latency more than the preconnecting helps. + */ + 'per_upstream_preconnect_ratio'?: (_google_protobuf_DoubleValue); + /** + * Indicates how many many streams (rounded up) can be anticipated across a cluster for each + * stream, useful for low QPS services. This is currently supported for a subset of + * deterministic non-hash-based load-balancing algorithms (weighted round robin, random). + * Unlike *per_upstream_preconnect_ratio* this preconnects across the upstream instances in a + * cluster, doing best effort predictions of what upstream would be picked next and + * pre-establishing a connection. + * + * Preconnecting will be limited to one preconnect per configured upstream in the cluster and will + * only be done if there are healthy upstreams and the cluster has traffic. + * + * For example if preconnecting is set to 2 for a round robin HTTP/2 cluster, on the first + * incoming stream, 2 connections will be preconnected - one to the first upstream for this + * cluster, one to the second on the assumption there will be a follow-up stream. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight, so during warm up and in steady state if a connection + * is closed (and per_upstream_preconnect_ratio is not set), there will be a latency hit for + * connection establishment. + * + * If both this and preconnect_ratio are set, Envoy will make sure both predicted needs are met, + * basically preconnecting max(predictive-preconnect, per-upstream-preconnect), for each + * upstream. + */ + 'predictive_preconnect_ratio'?: (_google_protobuf_DoubleValue); +} + +export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output { + /** + * Indicates how many streams (rounded up) can be anticipated per-upstream for each + * incoming stream. This is useful for high-QPS or latency-sensitive services. Preconnecting + * will only be done if the upstream is healthy and the cluster has traffic. + * + * For example if this is 2, for an incoming HTTP/1.1 stream, 2 connections will be + * established, one for the new incoming stream, and one for a presumed follow-up stream. For + * HTTP/2, only one connection would be established by default as one connection can + * serve both the original and presumed follow-up stream. + * + * In steady state for non-multiplexed connections a value of 1.5 would mean if there were 100 + * active streams, there would be 100 connections in use, and 50 connections preconnected. + * This might be a useful value for something like short lived single-use connections, + * for example proxying HTTP/1.1 if keep-alive were false and each stream resulted in connection + * termination. It would likely be overkill for long lived connections, such as TCP proxying SMTP + * or regular HTTP/1.1 with keep-alive. For long lived traffic, a value of 1.05 would be more + * reasonable, where for every 100 connections, 5 preconnected connections would be in the queue + * in case of unexpected disconnects where the connection could not be reused. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight. This means in steady state if a connection is torn down, + * a subsequent streams will pay an upstream-rtt latency penalty waiting for a new connection. + * + * This is limited somewhat arbitrarily to 3 because preconnecting too aggressively can + * harm latency more than the preconnecting helps. + */ + 'per_upstream_preconnect_ratio'?: (_google_protobuf_DoubleValue__Output); + /** + * Indicates how many many streams (rounded up) can be anticipated across a cluster for each + * stream, useful for low QPS services. This is currently supported for a subset of + * deterministic non-hash-based load-balancing algorithms (weighted round robin, random). + * Unlike *per_upstream_preconnect_ratio* this preconnects across the upstream instances in a + * cluster, doing best effort predictions of what upstream would be picked next and + * pre-establishing a connection. + * + * Preconnecting will be limited to one preconnect per configured upstream in the cluster and will + * only be done if there are healthy upstreams and the cluster has traffic. + * + * For example if preconnecting is set to 2 for a round robin HTTP/2 cluster, on the first + * incoming stream, 2 connections will be preconnected - one to the first upstream for this + * cluster, one to the second on the assumption there will be a follow-up stream. + * + * If this value is not set, or set explicitly to one, Envoy will fetch as many connections + * as needed to serve streams in flight, so during warm up and in steady state if a connection + * is closed (and per_upstream_preconnect_ratio is not set), there will be a latency hit for + * connection establishment. + * + * If both this and preconnect_ratio are set, Envoy will make sure both predicted needs are met, + * basically preconnecting max(predictive-preconnect, per-upstream-preconnect), for each + * upstream. + */ + 'predictive_preconnect_ratio'?: (_google_protobuf_DoubleValue__Output); +} + +export interface _envoy_config_cluster_v3_Cluster_RefreshRate { /** * Specifies the base interval between refreshes. This parameter is required and must be greater * than zero and less than - * :ref:`max_interval `. + * :ref:`max_interval `. */ 'base_interval'?: (_google_protobuf_Duration); /** * Specifies the maximum interval between refreshes. This parameter is optional, but must be * greater than or equal to the - * :ref:`base_interval ` if set. The default - * is 10 times the :ref:`base_interval `. + * :ref:`base_interval ` if set. The default + * is 10 times the :ref:`base_interval `. */ 'max_interval'?: (_google_protobuf_Duration); } -export interface _envoy_api_v2_Cluster_RefreshRate__Output { +export interface _envoy_config_cluster_v3_Cluster_RefreshRate__Output { /** * Specifies the base interval between refreshes. This parameter is required and must be greater * than zero and less than - * :ref:`max_interval `. + * :ref:`max_interval `. */ 'base_interval'?: (_google_protobuf_Duration__Output); /** * Specifies the maximum interval between refreshes. This parameter is optional, but must be * greater than or equal to the - * :ref:`base_interval ` if set. The default - * is 10 times the :ref:`base_interval `. + * :ref:`base_interval ` if set. The default + * is 10 times the :ref:`base_interval `. */ 'max_interval'?: (_google_protobuf_Duration__Output); } @@ -764,23 +995,23 @@ export interface _envoy_api_v2_Cluster_RefreshRate__Output { * Specific configuration for the :ref:`RingHash` * load balancing policy. */ -export interface _envoy_api_v2_Cluster_RingHashLbConfig { +export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig { /** * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each * provided host) the better the request distribution will reflect the desired weights. Defaults * to 1024 entries, and limited to 8M entries. See also - * :ref:`maximum_ring_size`. + * :ref:`maximum_ring_size`. */ 'minimum_ring_size'?: (_google_protobuf_UInt64Value); /** * The hash function used to hash hosts onto the ketama ring. The value defaults to - * :ref:`XX_HASH`. + * :ref:`XX_HASH`. */ - 'hash_function'?: (_envoy_api_v2_Cluster_RingHashLbConfig_HashFunction | keyof typeof _envoy_api_v2_Cluster_RingHashLbConfig_HashFunction); + 'hash_function'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction | keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); /** * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered * to further constrain resource use. See also - * :ref:`minimum_ring_size`. + * :ref:`minimum_ring_size`. */ 'maximum_ring_size'?: (_google_protobuf_UInt64Value); } @@ -789,23 +1020,23 @@ export interface _envoy_api_v2_Cluster_RingHashLbConfig { * Specific configuration for the :ref:`RingHash` * load balancing policy. */ -export interface _envoy_api_v2_Cluster_RingHashLbConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output { /** * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each * provided host) the better the request distribution will reflect the desired weights. Defaults * to 1024 entries, and limited to 8M entries. See also - * :ref:`maximum_ring_size`. + * :ref:`maximum_ring_size`. */ 'minimum_ring_size'?: (_google_protobuf_UInt64Value__Output); /** * The hash function used to hash hosts onto the ketama ring. The value defaults to - * :ref:`XX_HASH`. + * :ref:`XX_HASH`. */ - 'hash_function': (keyof typeof _envoy_api_v2_Cluster_RingHashLbConfig_HashFunction); + 'hash_function': (keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); /** * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered * to further constrain resource use. See also - * :ref:`minimum_ring_size`. + * :ref:`minimum_ring_size`. */ 'maximum_ring_size'?: (_google_protobuf_UInt64Value__Output); } @@ -814,7 +1045,7 @@ export interface _envoy_api_v2_Cluster_RingHashLbConfig__Output { * TransportSocketMatch specifies what transport socket config will be used * when the match conditions are satisfied. */ -export interface _envoy_api_v2_Cluster_TransportSocketMatch { +export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch { /** * The name of the match, used in stats generation. */ @@ -830,14 +1061,14 @@ export interface _envoy_api_v2_Cluster_TransportSocketMatch { /** * The configuration of the transport socket. */ - 'transport_socket'?: (_envoy_api_v2_core_TransportSocket); + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket); } /** * TransportSocketMatch specifies what transport socket config will be used * when the match conditions are satisfied. */ -export interface _envoy_api_v2_Cluster_TransportSocketMatch__Output { +export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output { /** * The name of the match, used in stats generation. */ @@ -853,21 +1084,21 @@ export interface _envoy_api_v2_Cluster_TransportSocketMatch__Output { /** * The configuration of the transport socket. */ - 'transport_socket'?: (_envoy_api_v2_core_TransportSocket__Output); + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket__Output); } /** * Configuration for :ref:`zone aware routing * `. */ -export interface _envoy_api_v2_Cluster_CommonLbConfig_ZoneAwareLbConfig { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig { /** * Configures percentage of requests that will be considered for zone aware routing * if zone aware routing is configured. If not specified, the default is 100%. * * :ref:`runtime values `. * * :ref:`Zone aware routing support `. */ - 'routing_enabled'?: (_envoy_type_Percent); + 'routing_enabled'?: (_envoy_type_v3_Percent); /** * Configures minimum upstream cluster size required for zone aware routing * If upstream cluster size is less than specified, zone aware routing is not performed @@ -889,14 +1120,14 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig_ZoneAwareLbConfig { * Configuration for :ref:`zone aware routing * `. */ -export interface _envoy_api_v2_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output { +export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output { /** * Configures percentage of requests that will be considered for zone aware routing * if zone aware routing is configured. If not specified, the default is 100%. * * :ref:`runtime values `. * * :ref:`Zone aware routing support `. */ - 'routing_enabled'?: (_envoy_type_Percent__Output); + 'routing_enabled'?: (_envoy_type_v3_Percent__Output); /** * Configures minimum upstream cluster size required for zone aware routing * If upstream cluster size is less than specified, zone aware routing is not performed @@ -916,14 +1147,14 @@ export interface _envoy_api_v2_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output /** * Configuration for a single upstream cluster. - * [#next-free-field: 48] + * [#next-free-field: 53] */ export interface Cluster { /** * Supplies the name of the cluster which must be unique across all clusters. * The cluster name is used when emitting * :ref:`statistics ` if :ref:`alt_stat_name - * ` is not provided. + * ` is not provided. * Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. */ 'name'?: (string); @@ -931,11 +1162,11 @@ export interface Cluster { * The :ref:`service discovery type ` * to use for resolving the cluster. */ - 'type'?: (_envoy_api_v2_Cluster_DiscoveryType | keyof typeof _envoy_api_v2_Cluster_DiscoveryType); + 'type'?: (_envoy_config_cluster_v3_Cluster_DiscoveryType | keyof typeof _envoy_config_cluster_v3_Cluster_DiscoveryType); /** * Configuration to use for EDS updates for the Cluster. */ - 'eds_cluster_config'?: (_envoy_api_v2_Cluster_EdsClusterConfig); + 'eds_cluster_config'?: (_envoy_config_cluster_v3_Cluster_EdsClusterConfig); /** * The timeout for new network connections to hosts in the cluster. */ @@ -948,28 +1179,16 @@ export interface Cluster { /** * The :ref:`load balancer type ` to use * when picking a host in the cluster. + * [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] */ - 'lb_policy'?: (_envoy_api_v2_Cluster_LbPolicy | keyof typeof _envoy_api_v2_Cluster_LbPolicy); - /** - * If the service discovery type is - * :ref:`STATIC`, - * :ref:`STRICT_DNS` - * or :ref:`LOGICAL_DNS`, - * then hosts is required. - * - * .. attention:: - * - * **This field is deprecated**. Set the - * :ref:`load_assignment` field instead. - */ - 'hosts'?: (_envoy_api_v2_core_Address)[]; + 'lb_policy'?: (_envoy_config_cluster_v3_Cluster_LbPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); /** * Optional :ref:`active health checking ` * configuration for the cluster. If no * configuration is specified no health checking will be done and all cluster * members will be considered healthy at all times. */ - 'health_checks'?: (_envoy_api_v2_core_HealthCheck)[]; + 'health_checks'?: (_envoy_config_core_v3_HealthCheck)[]; /** * Optional maximum requests for a single upstream connection. This parameter * is respected by both the HTTP/1.1 and HTTP/2 connection pool @@ -980,20 +1199,18 @@ export interface Cluster { /** * Optional :ref:`circuit breaking ` for the cluster. */ - 'circuit_breakers'?: (_envoy_api_v2_cluster_CircuitBreakers); - /** - * The TLS configuration for connections to the upstream cluster. - * - * .. attention:: - * - * **This field is deprecated**. Use `transport_socket` with name `tls` instead. If both are - * set, `transport_socket` takes priority. - */ - 'tls_context'?: (_envoy_api_v2_auth_UpstreamTlsContext); + 'circuit_breakers'?: (_envoy_config_cluster_v3_CircuitBreakers); /** * Additional options when handling HTTP1 requests. + * This has been deprecated in favor of http_protocol_options fields in the in the + * :ref:`http_protocol_options ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'http_protocol_options'?: (_envoy_api_v2_core_Http1ProtocolOptions); + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions); /** * Even if default HTTP2 protocol options are desired, this field must be * set so that Envoy will assume that the upstream supports HTTP/2 when @@ -1001,48 +1218,58 @@ export interface Cluster { * supports prior knowledge for upstream connections. Even if TLS is used * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 * connections to happen over plain text. + * This has been deprecated in favor of http2_protocol_options fields in the in the + * :ref:`http_protocol_options ` + * message. http2_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'http2_protocol_options'?: (_envoy_api_v2_core_Http2ProtocolOptions); + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions); /** * If the DNS refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used as the cluster’s DNS refresh * rate. The value configured must be at least 1ms. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. */ 'dns_refresh_rate'?: (_google_protobuf_Duration); /** * The DNS IP address resolution policy. If this setting is not specified, the * value defaults to - * :ref:`AUTO`. + * :ref:`AUTO`. */ - 'dns_lookup_family'?: (_envoy_api_v2_Cluster_DnsLookupFamily | keyof typeof _envoy_api_v2_Cluster_DnsLookupFamily); + 'dns_lookup_family'?: (_envoy_config_cluster_v3_Cluster_DnsLookupFamily | keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); /** * If DNS resolvers are specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used to specify the cluster’s dns resolvers. * If this setting is not specified, the value defaults to the default * resolver, which uses /etc/resolv.conf for configuration. For cluster types * other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. */ - 'dns_resolvers'?: (_envoy_api_v2_core_Address)[]; + 'dns_resolvers'?: (_envoy_config_core_v3_Address)[]; /** * If specified, outlier detection will be enabled for this upstream cluster. * Each of the configuration values can be overridden via * :ref:`runtime values `. */ - 'outlier_detection'?: (_envoy_api_v2_cluster_OutlierDetection); + 'outlier_detection'?: (_envoy_config_cluster_v3_OutlierDetection); /** * The interval for removing stale hosts from a cluster type - * :ref:`ORIGINAL_DST`. + * :ref:`ORIGINAL_DST`. * Hosts are considered stale if they have not been used * as upstream destinations during this interval. New hosts are added * to original destination clusters on demand as new connections are @@ -1052,7 +1279,7 @@ export interface Cluster { * them remain open, saving the latency that would otherwise be spent * on opening new connections. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`ORIGINAL_DST` + * :ref:`ORIGINAL_DST` * this setting is ignored. */ 'cleanup_interval'?: (_google_protobuf_Duration); @@ -1061,23 +1288,23 @@ export interface Cluster { * This overrides any bind_config specified in the bootstrap proto. * If the address and port are empty, no bind will be performed. */ - 'upstream_bind_config'?: (_envoy_api_v2_core_BindConfig); + 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig); /** * Configuration for load balancing subsetting. */ - 'lb_subset_config'?: (_envoy_api_v2_Cluster_LbSubsetConfig); + 'lb_subset_config'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig); /** * Optional configuration for the Ring Hash load balancing policy. */ - 'ring_hash_lb_config'?: (_envoy_api_v2_Cluster_RingHashLbConfig); + 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig); /** * Optional custom transport socket implementation to use for upstream connections. * To setup TLS, set a transport socket with name `tls` and - * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * :ref:`UpstreamTlsContexts ` in the `typed_config`. * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ - 'transport_socket'?: (_envoy_api_v2_core_TransportSocket); + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket); /** * The Metadata field can be used to provide additional information about the * cluster. It can be used for stats, logging, and varying filter behavior. @@ -1085,15 +1312,20 @@ export interface Cluster { * will need the information. For instance, if the metadata is intended for * the Router filter, the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_api_v2_core_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata); /** * Determines how Envoy selects the protocol used to speak to upstream hosts. + * This has been deprecated in favor of setting explicit protocol selection + * in the :ref:`http_protocol_options + * ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. */ - 'protocol_selection'?: (_envoy_api_v2_Cluster_ClusterProtocolSelection | keyof typeof _envoy_api_v2_Cluster_ClusterProtocolSelection); + 'protocol_selection'?: (_envoy_config_cluster_v3_Cluster_ClusterProtocolSelection | keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); /** * Common configuration for all load balancer implementations. */ - 'common_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig); + 'common_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig); /** * An optional alternative to the cluster name to be used while emitting stats. * Any ``:`` in the name will be converted to ``_`` when emitting statistics. This should not be @@ -1104,12 +1336,20 @@ export interface Cluster { /** * Additional options when handling HTTP requests upstream. These options will be applicable to * both HTTP1 and HTTP2 requests. + * This has been deprecated in favor of + * :ref:`common_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * common_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'common_http_protocol_options'?: (_envoy_api_v2_core_HttpProtocolOptions); + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions); /** * Optional options for upstream connections. */ - 'upstream_connection_options'?: (_envoy_api_v2_UpstreamConnectionOptions); + 'upstream_connection_options'?: (_envoy_config_cluster_v3_UpstreamConnectionOptions); /** * If an upstream host becomes unhealthy (as determined by the configured health checks * or outlier detection), immediately close all connections to the failed host. @@ -1131,46 +1371,40 @@ export interface Cluster { * from service discovery. This means that if active health checking is used, Envoy will *not* * wait for the endpoint to go unhealthy before removing it. */ - 'drain_connections_on_host_removal'?: (boolean); + 'ignore_health_on_host_removal'?: (boolean); /** * Setting this is required for specifying members of - * :ref:`STATIC`, - * :ref:`STRICT_DNS` - * or :ref:`LOGICAL_DNS` clusters. + * :ref:`STATIC`, + * :ref:`STRICT_DNS` + * or :ref:`LOGICAL_DNS` clusters. * This field supersedes the *hosts* field in the v2 API. * * .. attention:: * * Setting this allows non-EDS cluster types to contain embedded EDS equivalent - * :ref:`endpoint assignments`. + * :ref:`endpoint assignments`. */ - 'load_assignment'?: (_envoy_api_v2_ClusterLoadAssignment); + 'load_assignment'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment); /** * Optional configuration for the Original Destination load balancing policy. */ - 'original_dst_lb_config'?: (_envoy_api_v2_Cluster_OriginalDstLbConfig); - /** - * The extension_protocol_options field is used to provide extension-specific protocol options - * for upstream connections. The key should match the extension filter name, such as - * "envoy.filters.network.thrift_proxy". See the extension's documentation for details on - * specific options. - */ - 'extension_protocol_options'?: ({[key: string]: _google_protobuf_Struct}); + 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig); /** * The extension_protocol_options field is used to provide extension-specific protocol options * for upstream connections. The key should match the extension filter name, such as * "envoy.filters.network.thrift_proxy". See the extension's documentation for details on * specific options. + * [#next-major-version: make this a list of typed extensions.] */ 'typed_extension_protocol_options'?: ({[key: string]: _google_protobuf_Any}); /** * Optional configuration for the LeastRequest load balancing policy. */ - 'least_request_lb_config'?: (_envoy_api_v2_Cluster_LeastRequestLbConfig); + 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig); /** * The custom cluster type. */ - 'cluster_type'?: (_envoy_api_v2_Cluster_CustomClusterType); + 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType); /** * Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, * cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS @@ -1182,13 +1416,13 @@ export interface Cluster { * The chain will be applied to all outgoing connections that Envoy makes to the upstream * servers of this cluster. */ - 'filters'?: (_envoy_api_v2_cluster_Filter)[]; + 'filters'?: (_envoy_config_cluster_v3_Filter)[]; /** * [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - * :ref:`lb_policy` field has the value - * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + * :ref:`lb_policy` field has the value + * :ref:`LOAD_BALANCING_POLICY_CONFIG`. */ - 'load_balancing_policy'?: (_envoy_api_v2_LoadBalancingPolicy); + 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy); /** * [#not-implemented-hide:] * If present, tells the client where to send load reports via LRS. If not present, the @@ -1205,13 +1439,13 @@ export interface Cluster { * maybe by allowing LRS to go on the ADS stream, or maybe by moving some of the negotiation * from the LRS stream here.] */ - 'lrs_server'?: (_envoy_api_v2_core_ConfigSource); + 'lrs_server'?: (_envoy_config_core_v3_ConfigSource); /** * Configuration to use different transport sockets for different endpoints. * The entry of *envoy.transport_socket_match* in the - * :ref:`LbEndpoint.Metadata ` + * :ref:`LbEndpoint.Metadata ` * is used to match against the transport sockets as they appear in the list. The first - * :ref:`match ` is used. + * :ref:`match ` is used. * For example, with the following match * * .. code-block:: yaml @@ -1231,7 +1465,7 @@ export interface Cluster { * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. * - * If a :ref:`socket match ` with empty match + * If a :ref:`socket match ` with empty match * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" * socket match in case above. * @@ -1251,61 +1485,119 @@ export interface Cluster { * *TransportSocketMatch* in this field. Other client Envoys receive CDS without * *transport_socket_match* set, and still send plain text traffic to the same cluster. * + * This field can be used to specify custom transport socket configurations for health + * checks by adding matching key/value pairs in a health check's + * :ref:`transport socket match criteria ` field. + * * [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] */ - 'transport_socket_matches'?: (_envoy_api_v2_Cluster_TransportSocketMatch)[]; + 'transport_socket_matches'?: (_envoy_config_cluster_v3_Cluster_TransportSocketMatch)[]; /** * If the DNS failure refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is * not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - * other than :ref:`STRICT_DNS` and - * :ref:`LOGICAL_DNS` this setting is + * other than :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS` this setting is * ignored. */ - 'dns_failure_refresh_rate'?: (_envoy_api_v2_Cluster_RefreshRate); + 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate); /** * [#next-major-version: Reconcile DNS options in a single message.] * Always use TCP queries instead of UDP queries for DNS lookups. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. */ 'use_tcp_for_dns_lookups'?: (boolean); /** * HTTP protocol options that are applied only to upstream HTTP connections. * These options apply to all HTTP versions. + * This has been deprecated in favor of + * :ref:`upstream_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * upstream_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'upstream_http_protocol_options'?: (_envoy_api_v2_core_UpstreamHttpProtocolOptions); + 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions); /** * If track_timeout_budgets is true, the :ref:`timeout budget histograms * ` will be published for each * request. These show what percentage of a request's per try and global timeout was used. A value * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value * of 100 would indicate that the request took the entirety of the timeout given to it. + * + * .. attention:: + * + * This field has been deprecated in favor of `timeout_budgets`, part of + * :ref:`track_cluster_stats `. */ 'track_timeout_budgets'?: (boolean); + /** + * Optional customization and configuration of upstream connection pool, and upstream type. + * + * Currently this field only applies for HTTP traffic but is designed for eventual use for custom + * TCP upstreams. + * + * For HTTP traffic, Envoy will generally take downstream HTTP and send it upstream as upstream + * HTTP, using the http connection pool and the codec from `http2_protocol_options` + * + * For routes where CONNECT termination is configured, Envoy will take downstream CONNECT + * requests and forward the CONNECT payload upstream over raw TCP using the tcp connection pool. + * + * The default pool used is the generic connection pool which creates the HTTP upstream for most + * HTTP requests, and the TCP upstream if CONNECT termination is configured. + * + * If users desire custom connection pool or upstream behavior, for example terminating + * CONNECT only if a custom filter indicates it is appropriate, the custom factories + * can be registered and configured here. + */ + 'upstream_config'?: (_envoy_config_core_v3_TypedExtensionConfig); + /** + * Configuration to track optional cluster stats. + */ + 'track_cluster_stats'?: (_envoy_config_cluster_v3_TrackClusterStats); + /** + * Preconnect configuration for this cluster. + */ + 'preconnect_policy'?: (_envoy_config_cluster_v3_Cluster_PreconnectPolicy); + /** + * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate + * connection pool for every downstream connection + */ + 'connection_pool_per_downstream_connection'?: (boolean); + /** + * Optional configuration for the Maglev load balancing policy. + */ + 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig); 'cluster_discovery_type'?: "type"|"cluster_type"; /** * Optional configuration for the load balancing algorithm selected by * LbPolicy. Currently only - * :ref:`RING_HASH` and - * :ref:`LEAST_REQUEST` + * :ref:`RING_HASH`, + * :ref:`MAGLEV` and + * :ref:`LEAST_REQUEST` * has additional configuration options. - * Specifying ring_hash_lb_config or least_request_lb_config without setting the corresponding + * Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding * LbPolicy will generate an error at runtime. */ - 'lb_config'?: "ring_hash_lb_config"|"original_dst_lb_config"|"least_request_lb_config"; + 'lb_config'?: "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"; } /** * Configuration for a single upstream cluster. - * [#next-free-field: 48] + * [#next-free-field: 53] */ export interface Cluster__Output { /** * Supplies the name of the cluster which must be unique across all clusters. * The cluster name is used when emitting * :ref:`statistics ` if :ref:`alt_stat_name - * ` is not provided. + * ` is not provided. * Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. */ 'name': (string); @@ -1313,11 +1605,11 @@ export interface Cluster__Output { * The :ref:`service discovery type ` * to use for resolving the cluster. */ - 'type'?: (keyof typeof _envoy_api_v2_Cluster_DiscoveryType); + 'type'?: (keyof typeof _envoy_config_cluster_v3_Cluster_DiscoveryType); /** * Configuration to use for EDS updates for the Cluster. */ - 'eds_cluster_config'?: (_envoy_api_v2_Cluster_EdsClusterConfig__Output); + 'eds_cluster_config'?: (_envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output); /** * The timeout for new network connections to hosts in the cluster. */ @@ -1330,28 +1622,16 @@ export interface Cluster__Output { /** * The :ref:`load balancer type ` to use * when picking a host in the cluster. + * [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] */ - 'lb_policy': (keyof typeof _envoy_api_v2_Cluster_LbPolicy); - /** - * If the service discovery type is - * :ref:`STATIC`, - * :ref:`STRICT_DNS` - * or :ref:`LOGICAL_DNS`, - * then hosts is required. - * - * .. attention:: - * - * **This field is deprecated**. Set the - * :ref:`load_assignment` field instead. - */ - 'hosts': (_envoy_api_v2_core_Address__Output)[]; + 'lb_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); /** * Optional :ref:`active health checking ` * configuration for the cluster. If no * configuration is specified no health checking will be done and all cluster * members will be considered healthy at all times. */ - 'health_checks': (_envoy_api_v2_core_HealthCheck__Output)[]; + 'health_checks': (_envoy_config_core_v3_HealthCheck__Output)[]; /** * Optional maximum requests for a single upstream connection. This parameter * is respected by both the HTTP/1.1 and HTTP/2 connection pool @@ -1362,20 +1642,18 @@ export interface Cluster__Output { /** * Optional :ref:`circuit breaking ` for the cluster. */ - 'circuit_breakers'?: (_envoy_api_v2_cluster_CircuitBreakers__Output); - /** - * The TLS configuration for connections to the upstream cluster. - * - * .. attention:: - * - * **This field is deprecated**. Use `transport_socket` with name `tls` instead. If both are - * set, `transport_socket` takes priority. - */ - 'tls_context'?: (_envoy_api_v2_auth_UpstreamTlsContext__Output); + 'circuit_breakers'?: (_envoy_config_cluster_v3_CircuitBreakers__Output); /** * Additional options when handling HTTP1 requests. + * This has been deprecated in favor of http_protocol_options fields in the in the + * :ref:`http_protocol_options ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'http_protocol_options'?: (_envoy_api_v2_core_Http1ProtocolOptions__Output); + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions__Output); /** * Even if default HTTP2 protocol options are desired, this field must be * set so that Envoy will assume that the upstream supports HTTP/2 when @@ -1383,48 +1661,58 @@ export interface Cluster__Output { * supports prior knowledge for upstream connections. Even if TLS is used * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 * connections to happen over plain text. + * This has been deprecated in favor of http2_protocol_options fields in the in the + * :ref:`http_protocol_options ` + * message. http2_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'http2_protocol_options'?: (_envoy_api_v2_core_Http2ProtocolOptions__Output); + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions__Output); /** * If the DNS refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used as the cluster’s DNS refresh * rate. The value configured must be at least 1ms. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. */ 'dns_refresh_rate'?: (_google_protobuf_Duration__Output); /** * The DNS IP address resolution policy. If this setting is not specified, the * value defaults to - * :ref:`AUTO`. + * :ref:`AUTO`. */ - 'dns_lookup_family': (keyof typeof _envoy_api_v2_Cluster_DnsLookupFamily); + 'dns_lookup_family': (keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); /** * If DNS resolvers are specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used to specify the cluster’s dns resolvers. * If this setting is not specified, the value defaults to the default * resolver, which uses /etc/resolv.conf for configuration. For cluster types * other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. */ - 'dns_resolvers': (_envoy_api_v2_core_Address__Output)[]; + 'dns_resolvers': (_envoy_config_core_v3_Address__Output)[]; /** * If specified, outlier detection will be enabled for this upstream cluster. * Each of the configuration values can be overridden via * :ref:`runtime values `. */ - 'outlier_detection'?: (_envoy_api_v2_cluster_OutlierDetection__Output); + 'outlier_detection'?: (_envoy_config_cluster_v3_OutlierDetection__Output); /** * The interval for removing stale hosts from a cluster type - * :ref:`ORIGINAL_DST`. + * :ref:`ORIGINAL_DST`. * Hosts are considered stale if they have not been used * as upstream destinations during this interval. New hosts are added * to original destination clusters on demand as new connections are @@ -1434,7 +1722,7 @@ export interface Cluster__Output { * them remain open, saving the latency that would otherwise be spent * on opening new connections. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`ORIGINAL_DST` + * :ref:`ORIGINAL_DST` * this setting is ignored. */ 'cleanup_interval'?: (_google_protobuf_Duration__Output); @@ -1443,23 +1731,23 @@ export interface Cluster__Output { * This overrides any bind_config specified in the bootstrap proto. * If the address and port are empty, no bind will be performed. */ - 'upstream_bind_config'?: (_envoy_api_v2_core_BindConfig__Output); + 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig__Output); /** * Configuration for load balancing subsetting. */ - 'lb_subset_config'?: (_envoy_api_v2_Cluster_LbSubsetConfig__Output); + 'lb_subset_config'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output); /** * Optional configuration for the Ring Hash load balancing policy. */ - 'ring_hash_lb_config'?: (_envoy_api_v2_Cluster_RingHashLbConfig__Output); + 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output); /** * Optional custom transport socket implementation to use for upstream connections. * To setup TLS, set a transport socket with name `tls` and - * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * :ref:`UpstreamTlsContexts ` in the `typed_config`. * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ - 'transport_socket'?: (_envoy_api_v2_core_TransportSocket__Output); + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket__Output); /** * The Metadata field can be used to provide additional information about the * cluster. It can be used for stats, logging, and varying filter behavior. @@ -1467,15 +1755,20 @@ export interface Cluster__Output { * will need the information. For instance, if the metadata is intended for * the Router filter, the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_api_v2_core_Metadata__Output); + 'metadata'?: (_envoy_config_core_v3_Metadata__Output); /** * Determines how Envoy selects the protocol used to speak to upstream hosts. + * This has been deprecated in favor of setting explicit protocol selection + * in the :ref:`http_protocol_options + * ` message. + * http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. */ - 'protocol_selection': (keyof typeof _envoy_api_v2_Cluster_ClusterProtocolSelection); + 'protocol_selection': (keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); /** * Common configuration for all load balancer implementations. */ - 'common_lb_config'?: (_envoy_api_v2_Cluster_CommonLbConfig__Output); + 'common_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig__Output); /** * An optional alternative to the cluster name to be used while emitting stats. * Any ``:`` in the name will be converted to ``_`` when emitting statistics. This should not be @@ -1486,12 +1779,20 @@ export interface Cluster__Output { /** * Additional options when handling HTTP requests upstream. These options will be applicable to * both HTTP1 and HTTP2 requests. + * This has been deprecated in favor of + * :ref:`common_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * common_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'common_http_protocol_options'?: (_envoy_api_v2_core_HttpProtocolOptions__Output); + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions__Output); /** * Optional options for upstream connections. */ - 'upstream_connection_options'?: (_envoy_api_v2_UpstreamConnectionOptions__Output); + 'upstream_connection_options'?: (_envoy_config_cluster_v3_UpstreamConnectionOptions__Output); /** * If an upstream host becomes unhealthy (as determined by the configured health checks * or outlier detection), immediately close all connections to the failed host. @@ -1513,46 +1814,40 @@ export interface Cluster__Output { * from service discovery. This means that if active health checking is used, Envoy will *not* * wait for the endpoint to go unhealthy before removing it. */ - 'drain_connections_on_host_removal': (boolean); + 'ignore_health_on_host_removal': (boolean); /** * Setting this is required for specifying members of - * :ref:`STATIC`, - * :ref:`STRICT_DNS` - * or :ref:`LOGICAL_DNS` clusters. + * :ref:`STATIC`, + * :ref:`STRICT_DNS` + * or :ref:`LOGICAL_DNS` clusters. * This field supersedes the *hosts* field in the v2 API. * * .. attention:: * * Setting this allows non-EDS cluster types to contain embedded EDS equivalent - * :ref:`endpoint assignments`. + * :ref:`endpoint assignments`. */ - 'load_assignment'?: (_envoy_api_v2_ClusterLoadAssignment__Output); + 'load_assignment'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment__Output); /** * Optional configuration for the Original Destination load balancing policy. */ - 'original_dst_lb_config'?: (_envoy_api_v2_Cluster_OriginalDstLbConfig__Output); - /** - * The extension_protocol_options field is used to provide extension-specific protocol options - * for upstream connections. The key should match the extension filter name, such as - * "envoy.filters.network.thrift_proxy". See the extension's documentation for details on - * specific options. - */ - 'extension_protocol_options'?: ({[key: string]: _google_protobuf_Struct__Output}); + 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output); /** * The extension_protocol_options field is used to provide extension-specific protocol options * for upstream connections. The key should match the extension filter name, such as * "envoy.filters.network.thrift_proxy". See the extension's documentation for details on * specific options. + * [#next-major-version: make this a list of typed extensions.] */ 'typed_extension_protocol_options'?: ({[key: string]: _google_protobuf_Any__Output}); /** * Optional configuration for the LeastRequest load balancing policy. */ - 'least_request_lb_config'?: (_envoy_api_v2_Cluster_LeastRequestLbConfig__Output); + 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output); /** * The custom cluster type. */ - 'cluster_type'?: (_envoy_api_v2_Cluster_CustomClusterType__Output); + 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType__Output); /** * Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, * cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS @@ -1564,13 +1859,13 @@ export interface Cluster__Output { * The chain will be applied to all outgoing connections that Envoy makes to the upstream * servers of this cluster. */ - 'filters': (_envoy_api_v2_cluster_Filter__Output)[]; + 'filters': (_envoy_config_cluster_v3_Filter__Output)[]; /** * [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - * :ref:`lb_policy` field has the value - * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + * :ref:`lb_policy` field has the value + * :ref:`LOAD_BALANCING_POLICY_CONFIG`. */ - 'load_balancing_policy'?: (_envoy_api_v2_LoadBalancingPolicy__Output); + 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy__Output); /** * [#not-implemented-hide:] * If present, tells the client where to send load reports via LRS. If not present, the @@ -1587,13 +1882,13 @@ export interface Cluster__Output { * maybe by allowing LRS to go on the ADS stream, or maybe by moving some of the negotiation * from the LRS stream here.] */ - 'lrs_server'?: (_envoy_api_v2_core_ConfigSource__Output); + 'lrs_server'?: (_envoy_config_core_v3_ConfigSource__Output); /** * Configuration to use different transport sockets for different endpoints. * The entry of *envoy.transport_socket_match* in the - * :ref:`LbEndpoint.Metadata ` + * :ref:`LbEndpoint.Metadata ` * is used to match against the transport sockets as they appear in the list. The first - * :ref:`match ` is used. + * :ref:`match ` is used. * For example, with the following match * * .. code-block:: yaml @@ -1613,7 +1908,7 @@ export interface Cluster__Output { * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. * - * If a :ref:`socket match ` with empty match + * If a :ref:`socket match ` with empty match * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" * socket match in case above. * @@ -1633,47 +1928,105 @@ export interface Cluster__Output { * *TransportSocketMatch* in this field. Other client Envoys receive CDS without * *transport_socket_match* set, and still send plain text traffic to the same cluster. * + * This field can be used to specify custom transport socket configurations for health + * checks by adding matching key/value pairs in a health check's + * :ref:`transport socket match criteria ` field. + * * [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] */ - 'transport_socket_matches': (_envoy_api_v2_Cluster_TransportSocketMatch__Output)[]; + 'transport_socket_matches': (_envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output)[]; /** * If the DNS failure refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is * not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - * other than :ref:`STRICT_DNS` and - * :ref:`LOGICAL_DNS` this setting is + * other than :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS` this setting is * ignored. */ - 'dns_failure_refresh_rate'?: (_envoy_api_v2_Cluster_RefreshRate__Output); + 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate__Output); /** * [#next-major-version: Reconcile DNS options in a single message.] * Always use TCP queries instead of UDP queries for DNS lookups. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. */ 'use_tcp_for_dns_lookups': (boolean); /** * HTTP protocol options that are applied only to upstream HTTP connections. * These options apply to all HTTP versions. + * This has been deprecated in favor of + * :ref:`upstream_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. + * upstream_http_protocol_options can be set via the cluster's + * :ref:`extension_protocol_options`. + * See ref:`upstream_http_protocol_options + * ` + * for example usage. */ - 'upstream_http_protocol_options'?: (_envoy_api_v2_core_UpstreamHttpProtocolOptions__Output); + 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions__Output); /** * If track_timeout_budgets is true, the :ref:`timeout budget histograms * ` will be published for each * request. These show what percentage of a request's per try and global timeout was used. A value * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value * of 100 would indicate that the request took the entirety of the timeout given to it. + * + * .. attention:: + * + * This field has been deprecated in favor of `timeout_budgets`, part of + * :ref:`track_cluster_stats `. */ 'track_timeout_budgets': (boolean); + /** + * Optional customization and configuration of upstream connection pool, and upstream type. + * + * Currently this field only applies for HTTP traffic but is designed for eventual use for custom + * TCP upstreams. + * + * For HTTP traffic, Envoy will generally take downstream HTTP and send it upstream as upstream + * HTTP, using the http connection pool and the codec from `http2_protocol_options` + * + * For routes where CONNECT termination is configured, Envoy will take downstream CONNECT + * requests and forward the CONNECT payload upstream over raw TCP using the tcp connection pool. + * + * The default pool used is the generic connection pool which creates the HTTP upstream for most + * HTTP requests, and the TCP upstream if CONNECT termination is configured. + * + * If users desire custom connection pool or upstream behavior, for example terminating + * CONNECT only if a custom filter indicates it is appropriate, the custom factories + * can be registered and configured here. + */ + 'upstream_config'?: (_envoy_config_core_v3_TypedExtensionConfig__Output); + /** + * Configuration to track optional cluster stats. + */ + 'track_cluster_stats'?: (_envoy_config_cluster_v3_TrackClusterStats__Output); + /** + * Preconnect configuration for this cluster. + */ + 'preconnect_policy'?: (_envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output); + /** + * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate + * connection pool for every downstream connection + */ + 'connection_pool_per_downstream_connection': (boolean); + /** + * Optional configuration for the Maglev load balancing policy. + */ + 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output); 'cluster_discovery_type': "type"|"cluster_type"; /** * Optional configuration for the load balancing algorithm selected by * LbPolicy. Currently only - * :ref:`RING_HASH` and - * :ref:`LEAST_REQUEST` + * :ref:`RING_HASH`, + * :ref:`MAGLEV` and + * :ref:`LEAST_REQUEST` * has additional configuration options. - * Specifying ring_hash_lb_config or least_request_lb_config without setting the corresponding + * Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding * LbPolicy will generate an error at runtime. */ - 'lb_config': "ring_hash_lb_config"|"original_dst_lb_config"|"least_request_lb_config"; + 'lb_config': "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts new file mode 100644 index 000000000..aab9c6281 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts @@ -0,0 +1,19 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +import type { CollectionEntry as _xds_core_v3_CollectionEntry, CollectionEntry__Output as _xds_core_v3_CollectionEntry__Output } from '../../../../xds/core/v3/CollectionEntry'; + +/** + * Cluster list collections. Entries are *Cluster* resources or references. + * [#not-implemented-hide:] + */ +export interface ClusterCollection { + 'entries'?: (_xds_core_v3_CollectionEntry); +} + +/** + * Cluster list collections. Entries are *Cluster* resources or references. + * [#not-implemented-hide:] + */ +export interface ClusterCollection__Output { + 'entries'?: (_xds_core_v3_CollectionEntry__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts similarity index 92% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/Filter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts index 5608fadf2..53feedb98 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/Filter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/cluster/filter.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/filter.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/LoadBalancingPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts similarity index 80% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/LoadBalancingPolicy.ts rename to packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts index f79112a1c..29e343218 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/LoadBalancingPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts @@ -1,31 +1,20 @@ -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -export interface _envoy_api_v2_LoadBalancingPolicy_Policy { +export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy { /** * Required. The name of the LB policy. */ 'name'?: (string); - /** - * Optional config for the LB policy. - * No more than one of these two fields may be populated. - */ - 'config'?: (_google_protobuf_Struct); 'typed_config'?: (_google_protobuf_Any); } -export interface _envoy_api_v2_LoadBalancingPolicy_Policy__Output { +export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output { /** * Required. The name of the LB policy. */ 'name': (string); - /** - * Optional config for the LB policy. - * No more than one of these two fields may be populated. - */ - 'config'?: (_google_protobuf_Struct__Output); 'typed_config'?: (_google_protobuf_Any__Output); } @@ -56,7 +45,7 @@ export interface LoadBalancingPolicy { * supports. This provides a mechanism for starting to use new LB policies that are not yet * supported by all clients. */ - 'policies'?: (_envoy_api_v2_LoadBalancingPolicy_Policy)[]; + 'policies'?: (_envoy_config_cluster_v3_LoadBalancingPolicy_Policy)[]; } /** @@ -86,5 +75,5 @@ export interface LoadBalancingPolicy__Output { * supports. This provides a mechanism for starting to use new LB policies that are not yet * supported by all clients. */ - 'policies': (_envoy_api_v2_LoadBalancingPolicy_Policy__Output)[]; + 'policies': (_envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/OutlierDetection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts similarity index 85% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/OutlierDetection.ts rename to packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts index 53c1b4609..f37001691 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/cluster/OutlierDetection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/cluster/outlier_detection.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/outlier_detection.proto import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; @@ -6,7 +6,7 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google /** * See the :ref:`architecture overview ` for * more information on outlier detection. - * [#next-free-field: 21] + * [#next-free-field: 22] */ export interface OutlierDetection { /** @@ -23,7 +23,8 @@ export interface OutlierDetection { 'interval'?: (_google_protobuf_Duration); /** * The base time that a host is ejected for. The real time is equal to the - * base time multiplied by the number of times the host has been ejected. + * base time multiplied by the number of times the host has been ejected and is + * capped by :ref:`max_ejection_time`. * Defaults to 30000ms or 30s. */ 'base_ejection_time'?: (_google_protobuf_Duration); @@ -83,17 +84,17 @@ export interface OutlierDetection { /** * Determines whether to distinguish local origin failures from external errors. If set to true * the following configuration parameters are taken into account: - * :ref:`consecutive_local_origin_failure`, - * :ref:`enforcing_consecutive_local_origin_failure` + * :ref:`consecutive_local_origin_failure`, + * :ref:`enforcing_consecutive_local_origin_failure` * and - * :ref:`enforcing_local_origin_success_rate`. + * :ref:`enforcing_local_origin_success_rate`. * Defaults to false. */ 'split_external_local_origin_errors'?: (boolean); /** * The number of consecutive locally originated failures before ejection * occurs. Defaults to 5. Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value); @@ -102,7 +103,7 @@ export interface OutlierDetection { * is detected through consecutive locally originated failures. This setting can be * used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value); @@ -111,7 +112,7 @@ export interface OutlierDetection { * is detected through success rate statistics for locally originated errors. * This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_local_origin_success_rate'?: (_google_protobuf_UInt32Value); @@ -149,12 +150,18 @@ export interface OutlierDetection { * this host. Defaults to 50. */ 'failure_percentage_request_volume'?: (_google_protobuf_UInt32Value); + /** + * The maximum time that a host is ejected for. See :ref:`base_ejection_time` + * for more information. + * Defaults to 300000ms or 300s. + */ + 'max_ejection_time'?: (_google_protobuf_Duration); } /** * See the :ref:`architecture overview ` for * more information on outlier detection. - * [#next-free-field: 21] + * [#next-free-field: 22] */ export interface OutlierDetection__Output { /** @@ -171,7 +178,8 @@ export interface OutlierDetection__Output { 'interval'?: (_google_protobuf_Duration__Output); /** * The base time that a host is ejected for. The real time is equal to the - * base time multiplied by the number of times the host has been ejected. + * base time multiplied by the number of times the host has been ejected and is + * capped by :ref:`max_ejection_time`. * Defaults to 30000ms or 30s. */ 'base_ejection_time'?: (_google_protobuf_Duration__Output); @@ -231,17 +239,17 @@ export interface OutlierDetection__Output { /** * Determines whether to distinguish local origin failures from external errors. If set to true * the following configuration parameters are taken into account: - * :ref:`consecutive_local_origin_failure`, - * :ref:`enforcing_consecutive_local_origin_failure` + * :ref:`consecutive_local_origin_failure`, + * :ref:`enforcing_consecutive_local_origin_failure` * and - * :ref:`enforcing_local_origin_success_rate`. + * :ref:`enforcing_local_origin_success_rate`. * Defaults to false. */ 'split_external_local_origin_errors': (boolean); /** * The number of consecutive locally originated failures before ejection * occurs. Defaults to 5. Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value__Output); @@ -250,7 +258,7 @@ export interface OutlierDetection__Output { * is detected through consecutive locally originated failures. This setting can be * used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value__Output); @@ -259,7 +267,7 @@ export interface OutlierDetection__Output { * is detected through success rate statistics for locally originated errors. * This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_local_origin_success_rate'?: (_google_protobuf_UInt32Value__Output); @@ -297,4 +305,10 @@ export interface OutlierDetection__Output { * this host. Defaults to 50. */ 'failure_percentage_request_volume'?: (_google_protobuf_UInt32Value__Output); + /** + * The maximum time that a host is ejected for. See :ref:`base_ejection_time` + * for more information. + * Defaults to 300000ms or 300s. + */ + 'max_ejection_time'?: (_google_protobuf_Duration__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/TrackClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/TrackClusterStats.ts new file mode 100644 index 000000000..a65d1fd8d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/TrackClusterStats.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + + +export interface TrackClusterStats { + /** + * If timeout_budgets is true, the :ref:`timeout budget histograms + * ` will be published for each + * request. These show what percentage of a request's per try and global timeout was used. A value + * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value + * of 100 would indicate that the request took the entirety of the timeout given to it. + */ + 'timeout_budgets'?: (boolean); + /** + * If request_response_sizes is true, then the :ref:`histograms + * ` tracking header and body sizes + * of requests and responses will be published. + */ + 'request_response_sizes'?: (boolean); +} + +export interface TrackClusterStats__Output { + /** + * If timeout_budgets is true, the :ref:`timeout budget histograms + * ` will be published for each + * request. These show what percentage of a request's per try and global timeout was used. A value + * of 0 would indicate that none of the timeout was used or that the timeout was infinite. A value + * of 100 would indicate that the request took the entirety of the timeout given to it. + */ + 'timeout_budgets': (boolean); + /** + * If request_response_sizes is true, then the :ref:`histograms + * ` tracking header and body sizes + * of requests and responses will be published. + */ + 'request_response_sizes': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/UpstreamBindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts similarity index 59% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/UpstreamBindConfig.ts rename to packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts index 966ccca85..0f95c0816 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/UpstreamBindConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts @@ -1,6 +1,6 @@ -// Original file: deps/envoy-api/envoy/api/v2/cluster.proto +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto -import type { Address as _envoy_api_v2_core_Address, Address__Output as _envoy_api_v2_core_Address__Output } from '../../../envoy/api/v2/core/Address'; +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; /** * An extensible structure containing the address Envoy should bind to when @@ -10,7 +10,7 @@ export interface UpstreamBindConfig { /** * The address Envoy should bind to when establishing upstream connections. */ - 'source_address'?: (_envoy_api_v2_core_Address); + 'source_address'?: (_envoy_config_core_v3_Address); } /** @@ -21,5 +21,5 @@ export interface UpstreamBindConfig__Output { /** * The address Envoy should bind to when establishing upstream connections. */ - 'source_address'?: (_envoy_api_v2_core_Address__Output); + 'source_address'?: (_envoy_config_core_v3_Address__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts new file mode 100644 index 000000000..2e7f23894 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +import type { TcpKeepalive as _envoy_config_core_v3_TcpKeepalive, TcpKeepalive__Output as _envoy_config_core_v3_TcpKeepalive__Output } from '../../../../envoy/config/core/v3/TcpKeepalive'; + +export interface UpstreamConnectionOptions { + /** + * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. + */ + 'tcp_keepalive'?: (_envoy_config_core_v3_TcpKeepalive); +} + +export interface UpstreamConnectionOptions__Output { + /** + * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. + */ + 'tcp_keepalive'?: (_envoy_config_core_v3_TcpKeepalive__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts new file mode 100644 index 000000000..4d5881baf --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { SocketAddress as _envoy_config_core_v3_SocketAddress, SocketAddress__Output as _envoy_config_core_v3_SocketAddress__Output } from '../../../../envoy/config/core/v3/SocketAddress'; +import type { Pipe as _envoy_config_core_v3_Pipe, Pipe__Output as _envoy_config_core_v3_Pipe__Output } from '../../../../envoy/config/core/v3/Pipe'; +import type { EnvoyInternalAddress as _envoy_config_core_v3_EnvoyInternalAddress, EnvoyInternalAddress__Output as _envoy_config_core_v3_EnvoyInternalAddress__Output } from '../../../../envoy/config/core/v3/EnvoyInternalAddress'; + +/** + * Addresses specify either a logical or physical address and port, which are + * used to tell Envoy where to bind/listen, connect to upstream and find + * management servers. + */ +export interface Address { + 'socket_address'?: (_envoy_config_core_v3_SocketAddress); + 'pipe'?: (_envoy_config_core_v3_Pipe); + /** + * [#not-implemented-hide:] + */ + 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress); + 'address'?: "socket_address"|"pipe"|"envoy_internal_address"; +} + +/** + * Addresses specify either a logical or physical address and port, which are + * used to tell Envoy where to bind/listen, connect to upstream and find + * management servers. + */ +export interface Address__Output { + 'socket_address'?: (_envoy_config_core_v3_SocketAddress__Output); + 'pipe'?: (_envoy_config_core_v3_Pipe__Output); + /** + * [#not-implemented-hide:] + */ + 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress__Output); + 'address': "socket_address"|"pipe"|"envoy_internal_address"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AggregatedConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts similarity index 57% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/AggregatedConfigSource.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts index 6837dd0db..824ef93c8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AggregatedConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts @@ -1,9 +1,9 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/config_source.proto +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto /** * Aggregated Discovery Service (ADS) options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to + * set in :ref:`ConfigSource ` can be used to * specify that ADS is to be used. */ export interface AggregatedConfigSource { @@ -11,7 +11,7 @@ export interface AggregatedConfigSource { /** * Aggregated Discovery Service (ADS) options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to + * set in :ref:`ConfigSource ` can be used to * specify that ADS is to be used. */ export interface AggregatedConfigSource__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ApiConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts similarity index 66% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/ApiConfigSource.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts index a99924d9b..99afe9a26 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ApiConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts @@ -1,21 +1,21 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/config_source.proto +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { GrpcService as _envoy_api_v2_core_GrpcService, GrpcService__Output as _envoy_api_v2_core_GrpcService__Output } from '../../../../envoy/api/v2/core/GrpcService'; -import type { RateLimitSettings as _envoy_api_v2_core_RateLimitSettings, RateLimitSettings__Output as _envoy_api_v2_core_RateLimitSettings__Output } from '../../../../envoy/api/v2/core/RateLimitSettings'; -import type { ApiVersion as _envoy_api_v2_core_ApiVersion } from '../../../../envoy/api/v2/core/ApiVersion'; +import type { GrpcService as _envoy_config_core_v3_GrpcService, GrpcService__Output as _envoy_config_core_v3_GrpcService__Output } from '../../../../envoy/config/core/v3/GrpcService'; +import type { RateLimitSettings as _envoy_config_core_v3_RateLimitSettings, RateLimitSettings__Output as _envoy_config_core_v3_RateLimitSettings__Output } from '../../../../envoy/config/core/v3/RateLimitSettings'; +import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; -// Original file: deps/envoy-api/envoy/api/v2/core/config_source.proto +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto /** * APIs may be fetched via either REST or gRPC. */ -export enum _envoy_api_v2_core_ApiConfigSource_ApiType { +export enum _envoy_config_core_v3_ApiConfigSource_ApiType { /** * Ideally this would be 'reserved 0' but one can't reserve the default * value. Instead we throw an exception if this is ever used. */ - UNSUPPORTED_REST_LEGACY = 0, + DEPRECATED_AND_UNAVAILABLE_DO_NOT_USE = 0, /** * REST-JSON v2 API. The `canonical JSON encoding * `_ for @@ -23,7 +23,7 @@ export enum _envoy_api_v2_core_ApiConfigSource_ApiType { */ REST = 1, /** - * gRPC v2 API. + * SotW gRPC service. */ GRPC = 2, /** @@ -32,6 +32,18 @@ export enum _envoy_api_v2_core_ApiConfigSource_ApiType { * with every update, the xDS server only sends what has changed since the last update. */ DELTA_GRPC = 3, + /** + * SotW xDS gRPC with ADS. All resources which resolve to this configuration source will be + * multiplexed on a single connection to an ADS endpoint. + * [#not-implemented-hide:] + */ + AGGREGATED_GRPC = 5, + /** + * Delta xDS gRPC with ADS. All resources which resolve to this configuration source will be + * multiplexed on a single connection to an ADS endpoint. + * [#not-implemented-hide:] + */ + AGGREGATED_DELTA_GRPC = 6, } /** @@ -43,7 +55,7 @@ export interface ApiConfigSource { /** * API type (gRPC, REST, delta gRPC) */ - 'api_type'?: (_envoy_api_v2_core_ApiConfigSource_ApiType | keyof typeof _envoy_api_v2_core_ApiConfigSource_ApiType); + 'api_type'?: (_envoy_config_core_v3_ApiConfigSource_ApiType | keyof typeof _envoy_config_core_v3_ApiConfigSource_ApiType); /** * Cluster names should be used only with REST. If > 1 * cluster is defined, clusters will be cycled through if any kind of failure @@ -63,7 +75,7 @@ export interface ApiConfigSource { * Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, * services will be cycled through if any kind of failure occurs. */ - 'grpc_services'?: (_envoy_api_v2_core_GrpcService)[]; + 'grpc_services'?: (_envoy_config_core_v3_GrpcService)[]; /** * For REST APIs, the request timeout. If not set, a default value of 1s will be used. */ @@ -72,7 +84,7 @@ export interface ApiConfigSource { * For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be * rate limited. */ - 'rate_limit_settings'?: (_envoy_api_v2_core_RateLimitSettings); + 'rate_limit_settings'?: (_envoy_config_core_v3_RateLimitSettings); /** * Skip the node identifier in subsequent discovery requests for streaming gRPC config types. */ @@ -81,7 +93,7 @@ export interface ApiConfigSource { * API version for xDS transport protocol. This describes the xDS gRPC/REST * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ - 'transport_api_version'?: (_envoy_api_v2_core_ApiVersion | keyof typeof _envoy_api_v2_core_ApiVersion); + 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); } /** @@ -93,7 +105,7 @@ export interface ApiConfigSource__Output { /** * API type (gRPC, REST, delta gRPC) */ - 'api_type': (keyof typeof _envoy_api_v2_core_ApiConfigSource_ApiType); + 'api_type': (keyof typeof _envoy_config_core_v3_ApiConfigSource_ApiType); /** * Cluster names should be used only with REST. If > 1 * cluster is defined, clusters will be cycled through if any kind of failure @@ -113,7 +125,7 @@ export interface ApiConfigSource__Output { * Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, * services will be cycled through if any kind of failure occurs. */ - 'grpc_services': (_envoy_api_v2_core_GrpcService__Output)[]; + 'grpc_services': (_envoy_config_core_v3_GrpcService__Output)[]; /** * For REST APIs, the request timeout. If not set, a default value of 1s will be used. */ @@ -122,7 +134,7 @@ export interface ApiConfigSource__Output { * For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be * rate limited. */ - 'rate_limit_settings'?: (_envoy_api_v2_core_RateLimitSettings__Output); + 'rate_limit_settings'?: (_envoy_config_core_v3_RateLimitSettings__Output); /** * Skip the node identifier in subsequent discovery requests for streaming gRPC config types. */ @@ -131,5 +143,5 @@ export interface ApiConfigSource__Output { * API version for xDS transport protocol. This describes the xDS gRPC/REST * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ - 'transport_api_version': (keyof typeof _envoy_api_v2_core_ApiVersion); + 'transport_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ApiVersion.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts similarity index 69% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/ApiVersion.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts index 7f03a5995..b46f6ec43 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ApiVersion.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts @@ -1,7 +1,7 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/config_source.proto +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto /** - * xDS API version. This is used to describe both resource and transport + * xDS API and non-xDS services version. This is used to describe both resource and transport * protocol versions (in distinct configuration fields). */ export enum ApiVersion { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts new file mode 100644 index 000000000..476ea851f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; +import type { RemoteDataSource as _envoy_config_core_v3_RemoteDataSource, RemoteDataSource__Output as _envoy_config_core_v3_RemoteDataSource__Output } from '../../../../envoy/config/core/v3/RemoteDataSource'; + +/** + * Async data source which support async data fetch. + */ +export interface AsyncDataSource { + /** + * Local async data source. + */ + 'local'?: (_envoy_config_core_v3_DataSource); + /** + * Remote async data source. + */ + 'remote'?: (_envoy_config_core_v3_RemoteDataSource); + 'specifier'?: "local"|"remote"; +} + +/** + * Async data source which support async data fetch. + */ +export interface AsyncDataSource__Output { + /** + * Local async data source. + */ + 'local'?: (_envoy_config_core_v3_DataSource__Output); + /** + * Remote async data source. + */ + 'remote'?: (_envoy_config_core_v3_RemoteDataSource__Output); + 'specifier': "local"|"remote"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts new file mode 100644 index 000000000..9878375d6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/backoff.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * Configuration defining a jittered exponential back off strategy. + */ +export interface BackoffStrategy { + /** + * The base interval to be used for the next back off computation. It should + * be greater than zero and less than or equal to :ref:`max_interval + * `. + */ + 'base_interval'?: (_google_protobuf_Duration); + /** + * Specifies the maximum interval between retries. This parameter is optional, + * but must be greater than or equal to the :ref:`base_interval + * ` if set. The default + * is 10 times the :ref:`base_interval + * `. + */ + 'max_interval'?: (_google_protobuf_Duration); +} + +/** + * Configuration defining a jittered exponential back off strategy. + */ +export interface BackoffStrategy__Output { + /** + * The base interval to be used for the next back off computation. It should + * be greater than zero and less than or equal to :ref:`max_interval + * `. + */ + 'base_interval'?: (_google_protobuf_Duration__Output); + /** + * Specifies the maximum interval between retries. This parameter is optional, + * but must be greater than or equal to the :ref:`base_interval + * ` if set. The default + * is 10 times the :ref:`base_interval + * `. + */ + 'max_interval'?: (_google_protobuf_Duration__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts new file mode 100644 index 000000000..eb2bc7626 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts @@ -0,0 +1,49 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { SocketAddress as _envoy_config_core_v3_SocketAddress, SocketAddress__Output as _envoy_config_core_v3_SocketAddress__Output } from '../../../../envoy/config/core/v3/SocketAddress'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; + +export interface BindConfig { + /** + * The address to bind to when creating a socket. + */ + 'source_address'?: (_envoy_config_core_v3_SocketAddress); + /** + * Whether to set the *IP_FREEBIND* option when creating the socket. When this + * flag is set to true, allows the :ref:`source_address + * ` to be an IP address + * that is not configured on the system running Envoy. When this flag is set + * to false, the option *IP_FREEBIND* is disabled on the socket. When this + * flag is not set (default), the socket is not modified, i.e. the option is + * neither enabled nor disabled. + */ + 'freebind'?: (_google_protobuf_BoolValue); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; +} + +export interface BindConfig__Output { + /** + * The address to bind to when creating a socket. + */ + 'source_address'?: (_envoy_config_core_v3_SocketAddress__Output); + /** + * Whether to set the *IP_FREEBIND* option when creating the socket. When this + * flag is set to true, allows the :ref:`source_address + * ` to be an IP address + * that is not configured on the system running Envoy. When this flag is set + * to false, the option *IP_FREEBIND* is disabled on the socket. When this + * flag is not set (default), the socket is not modified, i.e. the option is + * neither enabled nor disabled. + */ + 'freebind'?: (_google_protobuf_BoolValue__Output); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts new file mode 100644 index 000000000..a3a33e4a3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { SemanticVersion as _envoy_type_v3_SemanticVersion, SemanticVersion__Output as _envoy_type_v3_SemanticVersion__Output } from '../../../../envoy/type/v3/SemanticVersion'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; + +/** + * BuildVersion combines SemVer version of extension with free-form build information + * (i.e. 'alpha', 'private-build') as a set of strings. + */ +export interface BuildVersion { + /** + * SemVer version of extension. + */ + 'version'?: (_envoy_type_v3_SemanticVersion); + /** + * Free-form build information. + * Envoy defines several well known keys in the source/common/version/version.h file + */ + 'metadata'?: (_google_protobuf_Struct); +} + +/** + * BuildVersion combines SemVer version of extension with free-form build information + * (i.e. 'alpha', 'private-build') as a set of strings. + */ +export interface BuildVersion__Output { + /** + * SemVer version of extension. + */ + 'version'?: (_envoy_type_v3_SemanticVersion__Output); + /** + * Free-form build information. + * Envoy defines several well known keys in the source/common/version/version.h file + */ + 'metadata'?: (_google_protobuf_Struct__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts new file mode 100644 index 000000000..c4a01b0da --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts @@ -0,0 +1,33 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * CidrRange specifies an IP Address and a prefix length to construct + * the subnet mask for a `CIDR `_ range. + */ +export interface CidrRange { + /** + * IPv4 or IPv6 address, e.g. ``192.0.0.0`` or ``2001:db8::``. + */ + 'address_prefix'?: (string); + /** + * Length of prefix, e.g. 0, 32. + */ + 'prefix_len'?: (_google_protobuf_UInt32Value); +} + +/** + * CidrRange specifies an IP Address and a prefix length to construct + * the subnet mask for a `CIDR `_ range. + */ +export interface CidrRange__Output { + /** + * IPv4 or IPv6 address, e.g. ``192.0.0.0`` or ``2001:db8::``. + */ + 'address_prefix': (string); + /** + * Length of prefix, e.g. 0, 32. + */ + 'prefix_len'?: (_google_protobuf_UInt32Value__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts similarity index 70% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/ConfigSource.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts index 766af6148..9462f4844 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts @@ -1,24 +1,25 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/config_source.proto +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto -import type { ApiConfigSource as _envoy_api_v2_core_ApiConfigSource, ApiConfigSource__Output as _envoy_api_v2_core_ApiConfigSource__Output } from '../../../../envoy/api/v2/core/ApiConfigSource'; -import type { AggregatedConfigSource as _envoy_api_v2_core_AggregatedConfigSource, AggregatedConfigSource__Output as _envoy_api_v2_core_AggregatedConfigSource__Output } from '../../../../envoy/api/v2/core/AggregatedConfigSource'; +import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; +import type { AggregatedConfigSource as _envoy_config_core_v3_AggregatedConfigSource, AggregatedConfigSource__Output as _envoy_config_core_v3_AggregatedConfigSource__Output } from '../../../../envoy/config/core/v3/AggregatedConfigSource'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { SelfConfigSource as _envoy_api_v2_core_SelfConfigSource, SelfConfigSource__Output as _envoy_api_v2_core_SelfConfigSource__Output } from '../../../../envoy/api/v2/core/SelfConfigSource'; -import type { ApiVersion as _envoy_api_v2_core_ApiVersion } from '../../../../envoy/api/v2/core/ApiVersion'; +import type { SelfConfigSource as _envoy_config_core_v3_SelfConfigSource, SelfConfigSource__Output as _envoy_config_core_v3_SelfConfigSource__Output } from '../../../../envoy/config/core/v3/SelfConfigSource'; +import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; +import type { Authority as _xds_core_v3_Authority, Authority__Output as _xds_core_v3_Authority__Output } from '../../../../xds/core/v3/Authority'; /** * Configuration for :ref:`listeners `, :ref:`clusters * `, :ref:`routes - * `, :ref:`endpoints + * `, :ref:`endpoints * ` etc. may either be sourced from the * filesystem or from an xDS API source. Filesystem configs are watched with * inotify for updates. - * [#next-free-field: 7] + * [#next-free-field: 8] */ export interface ConfigSource { /** * Path on the filesystem to source and watch for configuration updates. - * When sourcing configuration for :ref:`secret `, + * When sourcing configuration for :ref:`secret `, * the certificate and key files are also watched for updates. * * .. note:: @@ -35,12 +36,12 @@ export interface ConfigSource { /** * API configuration source. */ - 'api_config_source'?: (_envoy_api_v2_core_ApiConfigSource); + 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource); /** * When set, ADS will be used to fetch resources. The ADS API configuration * source in the bootstrap configuration is used. */ - 'ads'?: (_envoy_api_v2_core_AggregatedConfigSource); + 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource); /** * When this timeout is specified, Envoy will wait no longer than the specified time for first * config response on this xDS subscription during the :ref:`initialization process @@ -64,29 +65,36 @@ export interface ConfigSource { * this field can implicitly mean to use the same stream in the case where the ConfigSource * is provided via ADS and the specified data can also be obtained via ADS.] */ - 'self'?: (_envoy_api_v2_core_SelfConfigSource); + 'self'?: (_envoy_config_core_v3_SelfConfigSource); /** * API version for xDS resources. This implies the type URLs that the client * will request for resources and the resource type that the client will in * turn expect to be delivered. */ - 'resource_api_version'?: (_envoy_api_v2_core_ApiVersion | keyof typeof _envoy_api_v2_core_ApiVersion); + 'resource_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); + /** + * Authorities that this config source may be used for. An authority specified in a xdstp:// URL + * is resolved to a *ConfigSource* prior to configuration fetch. This field provides the + * association between authority name and configuration source. + * [#not-implemented-hide:] + */ + 'authorities'?: (_xds_core_v3_Authority)[]; 'config_source_specifier'?: "path"|"api_config_source"|"ads"|"self"; } /** * Configuration for :ref:`listeners `, :ref:`clusters * `, :ref:`routes - * `, :ref:`endpoints + * `, :ref:`endpoints * ` etc. may either be sourced from the * filesystem or from an xDS API source. Filesystem configs are watched with * inotify for updates. - * [#next-free-field: 7] + * [#next-free-field: 8] */ export interface ConfigSource__Output { /** * Path on the filesystem to source and watch for configuration updates. - * When sourcing configuration for :ref:`secret `, + * When sourcing configuration for :ref:`secret `, * the certificate and key files are also watched for updates. * * .. note:: @@ -103,12 +111,12 @@ export interface ConfigSource__Output { /** * API configuration source. */ - 'api_config_source'?: (_envoy_api_v2_core_ApiConfigSource__Output); + 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource__Output); /** * When set, ADS will be used to fetch resources. The ADS API configuration * source in the bootstrap configuration is used. */ - 'ads'?: (_envoy_api_v2_core_AggregatedConfigSource__Output); + 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource__Output); /** * When this timeout is specified, Envoy will wait no longer than the specified time for first * config response on this xDS subscription during the :ref:`initialization process @@ -132,12 +140,19 @@ export interface ConfigSource__Output { * this field can implicitly mean to use the same stream in the case where the ConfigSource * is provided via ADS and the specified data can also be obtained via ADS.] */ - 'self'?: (_envoy_api_v2_core_SelfConfigSource__Output); + 'self'?: (_envoy_config_core_v3_SelfConfigSource__Output); /** * API version for xDS resources. This implies the type URLs that the client * will request for resources and the resource type that the client will in * turn expect to be delivered. */ - 'resource_api_version': (keyof typeof _envoy_api_v2_core_ApiVersion); + 'resource_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); + /** + * Authorities that this config source may be used for. An authority specified in a xdstp:// URL + * is resolved to a *ConfigSource* prior to configuration fetch. This field provides the + * association between authority name and configuration source. + * [#not-implemented-hide:] + */ + 'authorities': (_xds_core_v3_Authority__Output)[]; 'config_source_specifier': "path"|"api_config_source"|"ads"|"self"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ControlPlane.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ControlPlane.ts new file mode 100644 index 000000000..dc55f8042 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ControlPlane.ts @@ -0,0 +1,26 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Identifies a specific ControlPlane instance that Envoy is connected to. + */ +export interface ControlPlane { + /** + * An opaque control plane identifier that uniquely identifies an instance + * of control plane. This can be used to identify which control plane instance, + * the Envoy is connected to. + */ + 'identifier'?: (string); +} + +/** + * Identifies a specific ControlPlane instance that Envoy is connected to. + */ +export interface ControlPlane__Output { + /** + * An opaque control plane identifier that uniquely identifies an instance + * of control plane. This can be used to identify which control plane instance, + * the Envoy is connected to. + */ + 'identifier': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts new file mode 100644 index 000000000..cc29e084f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts @@ -0,0 +1,40 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Data source consisting of either a file or an inline value. + */ +export interface DataSource { + /** + * Local filesystem data source. + */ + 'filename'?: (string); + /** + * Bytes inlined in the configuration. + */ + 'inline_bytes'?: (Buffer | Uint8Array | string); + /** + * String inlined in the configuration. + */ + 'inline_string'?: (string); + 'specifier'?: "filename"|"inline_bytes"|"inline_string"; +} + +/** + * Data source consisting of either a file or an inline value. + */ +export interface DataSource__Output { + /** + * Local filesystem data source. + */ + 'filename'?: (string); + /** + * Bytes inlined in the configuration. + */ + 'inline_bytes'?: (Buffer); + /** + * String inlined in the configuration. + */ + 'inline_string'?: (string); + 'specifier': "filename"|"inline_bytes"|"inline_string"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts new file mode 100644 index 000000000..dfe6a52b4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts @@ -0,0 +1,28 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + + +/** + * [#not-implemented-hide:] The address represents an envoy internal listener. + * TODO(lambdai): Make this address available for listener and endpoint. + * TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30. + */ +export interface EnvoyInternalAddress { + /** + * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + */ + 'server_listener_name'?: (string); + 'address_name_specifier'?: "server_listener_name"; +} + +/** + * [#not-implemented-hide:] The address represents an envoy internal listener. + * TODO(lambdai): Make this address available for listener and endpoint. + * TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30. + */ +export interface EnvoyInternalAddress__Output { + /** + * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + */ + 'server_listener_name'?: (string); + 'address_name_specifier': "server_listener_name"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/EventServiceConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts similarity index 58% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/EventServiceConfig.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts index d0637578c..72aedc6ab 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/EventServiceConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts @@ -1,6 +1,6 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/event_service_config.proto +// Original file: deps/envoy-api/envoy/config/core/v3/event_service_config.proto -import type { GrpcService as _envoy_api_v2_core_GrpcService, GrpcService__Output as _envoy_api_v2_core_GrpcService__Output } from '../../../../envoy/api/v2/core/GrpcService'; +import type { GrpcService as _envoy_config_core_v3_GrpcService, GrpcService__Output as _envoy_config_core_v3_GrpcService__Output } from '../../../../envoy/config/core/v3/GrpcService'; /** * [#not-implemented-hide:] @@ -10,7 +10,7 @@ export interface EventServiceConfig { /** * Specifies the gRPC service that hosts the event reporting service. */ - 'grpc_service'?: (_envoy_api_v2_core_GrpcService); + 'grpc_service'?: (_envoy_config_core_v3_GrpcService); 'config_source_specifier'?: "grpc_service"; } @@ -22,6 +22,6 @@ export interface EventServiceConfig__Output { /** * Specifies the gRPC service that hosts the event reporting service. */ - 'grpc_service'?: (_envoy_api_v2_core_GrpcService__Output); + 'grpc_service'?: (_envoy_config_core_v3_GrpcService__Output); 'config_source_specifier': "grpc_service"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts new file mode 100644 index 000000000..0c7e0abd3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts @@ -0,0 +1,75 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { BuildVersion as _envoy_config_core_v3_BuildVersion, BuildVersion__Output as _envoy_config_core_v3_BuildVersion__Output } from '../../../../envoy/config/core/v3/BuildVersion'; + +/** + * Version and identification for an Envoy extension. + * [#next-free-field: 6] + */ +export interface Extension { + /** + * This is the name of the Envoy filter as specified in the Envoy + * configuration, e.g. envoy.filters.http.router, com.acme.widget. + */ + 'name'?: (string); + /** + * Category of the extension. + * Extension category names use reverse DNS notation. For instance "envoy.filters.listener" + * for Envoy's built-in listener filters or "com.acme.filters.http" for HTTP filters from + * acme.com vendor. + * [#comment:TODO(yanavlasov): Link to the doc with existing envoy category names.] + */ + 'category'?: (string); + /** + * [#not-implemented-hide:] Type descriptor of extension configuration proto. + * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] + * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] + */ + 'type_descriptor'?: (string); + /** + * The version is a property of the extension and maintained independently + * of other extensions and the Envoy API. + * This field is not set when extension did not provide version information. + */ + 'version'?: (_envoy_config_core_v3_BuildVersion); + /** + * Indicates that the extension is present but was disabled via dynamic configuration. + */ + 'disabled'?: (boolean); +} + +/** + * Version and identification for an Envoy extension. + * [#next-free-field: 6] + */ +export interface Extension__Output { + /** + * This is the name of the Envoy filter as specified in the Envoy + * configuration, e.g. envoy.filters.http.router, com.acme.widget. + */ + 'name': (string); + /** + * Category of the extension. + * Extension category names use reverse DNS notation. For instance "envoy.filters.listener" + * for Envoy's built-in listener filters or "com.acme.filters.http" for HTTP filters from + * acme.com vendor. + * [#comment:TODO(yanavlasov): Link to the doc with existing envoy category names.] + */ + 'category': (string); + /** + * [#not-implemented-hide:] Type descriptor of extension configuration proto. + * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] + * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] + */ + 'type_descriptor': (string); + /** + * The version is a property of the extension and maintained independently + * of other extensions and the Envoy API. + * This field is not set when extension did not provide version information. + */ + 'version'?: (_envoy_config_core_v3_BuildVersion__Output); + /** + * Indicates that the extension is present but was disabled via dynamic configuration. + */ + 'disabled': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts new file mode 100644 index 000000000..085324b6d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts @@ -0,0 +1,72 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/extension.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Configuration source specifier for a late-bound extension configuration. The + * parent resource is warmed until all the initial extension configurations are + * received, unless the flag to apply the default configuration is set. + * Subsequent extension updates are atomic on a per-worker basis. Once an + * extension configuration is applied to a request or a connection, it remains + * constant for the duration of processing. If the initial delivery of the + * extension configuration fails, due to a timeout for example, the optional + * default configuration is applied. Without a default configuration, the + * extension is disabled, until an extension configuration is received. The + * behavior of a disabled extension depends on the context. For example, a + * filter chain with a disabled extension filter rejects all incoming streams. + */ +export interface ExtensionConfigSource { + 'config_source'?: (_envoy_config_core_v3_ConfigSource); + /** + * Optional default configuration to use as the initial configuration if + * there is a failure to receive the initial extension configuration or if + * `apply_default_config_without_warming` flag is set. + */ + 'default_config'?: (_google_protobuf_Any); + /** + * Use the default config as the initial configuration without warming and + * waiting for the first discovery response. Requires the default configuration + * to be supplied. + */ + 'apply_default_config_without_warming'?: (boolean); + /** + * A set of permitted extension type URLs. Extension configuration updates are rejected + * if they do not match any type URL in the set. + */ + 'type_urls'?: (string)[]; +} + +/** + * Configuration source specifier for a late-bound extension configuration. The + * parent resource is warmed until all the initial extension configurations are + * received, unless the flag to apply the default configuration is set. + * Subsequent extension updates are atomic on a per-worker basis. Once an + * extension configuration is applied to a request or a connection, it remains + * constant for the duration of processing. If the initial delivery of the + * extension configuration fails, due to a timeout for example, the optional + * default configuration is applied. Without a default configuration, the + * extension is disabled, until an extension configuration is received. The + * behavior of a disabled extension depends on the context. For example, a + * filter chain with a disabled extension filter rejects all incoming streams. + */ +export interface ExtensionConfigSource__Output { + 'config_source'?: (_envoy_config_core_v3_ConfigSource__Output); + /** + * Optional default configuration to use as the initial configuration if + * there is a failure to receive the initial extension configuration or if + * `apply_default_config_without_warming` flag is set. + */ + 'default_config'?: (_google_protobuf_Any__Output); + /** + * Use the default config as the initial configuration without warming and + * waiting for the first discovery response. Requires the default configuration + * to be supplied. + */ + 'apply_default_config_without_warming': (boolean); + /** + * A set of permitted extension type URLs. Extension configuration updates are rejected + * if they do not match any type URL in the set. + */ + 'type_urls': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts new file mode 100644 index 000000000..b0486cc37 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { Http2ProtocolOptions as _envoy_config_core_v3_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_config_core_v3_Http2ProtocolOptions__Output } from '../../../../envoy/config/core/v3/Http2ProtocolOptions'; + +/** + * [#not-implemented-hide:] + */ +export interface GrpcProtocolOptions { + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions); +} + +/** + * [#not-implemented-hide:] + */ +export interface GrpcProtocolOptions__Output { + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/GrpcService.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts similarity index 59% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/GrpcService.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts index 3bc894b57..24249309b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/GrpcService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts @@ -1,9 +1,10 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/grpc_service.proto +// Original file: deps/envoy-api/envoy/config/core/v3/grpc_service.proto import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { HeaderValue as _envoy_api_v2_core_HeaderValue, HeaderValue__Output as _envoy_api_v2_core_HeaderValue__Output } from '../../../../envoy/api/v2/core/HeaderValue'; +import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { DataSource as _envoy_api_v2_core_DataSource, DataSource__Output as _envoy_api_v2_core_DataSource__Output } from '../../../../envoy/api/v2/core/DataSource'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; import type { Empty as _google_protobuf_Empty, Empty__Output as _google_protobuf_Empty__Output } from '../../../../google/protobuf/Empty'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { Long } from '@grpc/proto-loader'; @@ -11,7 +12,7 @@ import type { Long } from '@grpc/proto-loader'; /** * [#next-free-field: 8] */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials { /** * Access token credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#ad3a80da696ffdaea943f0f858d7a360d. @@ -31,31 +32,31 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials { * Service Account JWT Access credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a92a9f959d6102461f66ee973d8e9d3aa. */ - 'service_account_jwt_access'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials); + 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials); /** * Google IAM credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a9fc1fc101b41e680d47028166e76f9d0. */ - 'google_iam'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials); + 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials); /** * Custom authenticator credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a823c6a4b19ffc71fb33e90154ee2ad07. * https://grpc.io/docs/guides/auth.html#extending-grpc-to-support-other-authentication-mechanisms. */ - 'from_plugin'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin); + 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin); /** * Custom security token service which implements OAuth 2.0 token exchange. * https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 * See https://github.com/grpc/grpc/pull/19587. */ - 'sts_service'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_StsService); + 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService); 'credential_specifier'?: "access_token"|"google_compute_engine"|"google_refresh_token"|"service_account_jwt_access"|"google_iam"|"from_plugin"|"sts_service"; } /** * [#next-free-field: 8] */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials__Output { /** * Access token credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#ad3a80da696ffdaea943f0f858d7a360d. @@ -75,38 +76,58 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials__Outp * Service Account JWT Access credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a92a9f959d6102461f66ee973d8e9d3aa. */ - 'service_account_jwt_access'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output); + 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output); /** * Google IAM credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a9fc1fc101b41e680d47028166e76f9d0. */ - 'google_iam'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output); + 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output); /** * Custom authenticator credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a823c6a4b19ffc71fb33e90154ee2ad07. * https://grpc.io/docs/guides/auth.html#extending-grpc-to-support-other-authentication-mechanisms. */ - 'from_plugin'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output); + 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output); /** * Custom security token service which implements OAuth 2.0 token exchange. * https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 * See https://github.com/grpc/grpc/pull/19587. */ - 'sts_service'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_StsService__Output); + 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService__Output); 'credential_specifier': "access_token"|"google_compute_engine"|"google_refresh_token"|"service_account_jwt_access"|"google_iam"|"from_plugin"|"sts_service"; } +/** + * Channel arguments. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs { + /** + * See grpc_types.h GRPC_ARG #defines for keys that work here. + */ + 'args'?: ({[key: string]: _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value}); +} + +/** + * Channel arguments. + */ +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Output { + /** + * See grpc_types.h GRPC_ARG #defines for keys that work here. + */ + 'args'?: ({[key: string]: _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value__Output}); +} + /** * See https://grpc.io/docs/guides/auth.html#credential-types to understand Channel and Call * credential types. */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_ChannelCredentials { - 'ssl_credentials'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_SslCredentials); +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials { + 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials); /** * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 */ 'google_default'?: (_google_protobuf_Empty); - 'local_credentials'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_GoogleLocalCredentials); + 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials); 'credential_specifier'?: "ssl_credentials"|"google_default"|"local_credentials"; } @@ -114,50 +135,60 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_ChannelCredentials { * See https://grpc.io/docs/guides/auth.html#credential-types to understand Channel and Call * credential types. */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_ChannelCredentials__Output { - 'ssl_credentials'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_SslCredentials__Output); +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output { + 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials__Output); /** * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 */ 'google_default'?: (_google_protobuf_Empty__Output); - 'local_credentials'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output); + 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output); 'credential_specifier': "ssl_credentials"|"google_default"|"local_credentials"; } -export interface _envoy_api_v2_core_GrpcService_EnvoyGrpc { +export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc { /** * The name of the upstream gRPC cluster. SSL credentials will be supplied - * in the :ref:`Cluster ` :ref:`transport_socket - * `. + * in the :ref:`Cluster ` :ref:`transport_socket + * `. */ 'cluster_name'?: (string); + /** + * The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. + * Note that this authority does not override the SNI. The SNI is provided by the transport socket of the cluster. + */ + 'authority'?: (string); } -export interface _envoy_api_v2_core_GrpcService_EnvoyGrpc__Output { +export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc__Output { /** * The name of the upstream gRPC cluster. SSL credentials will be supplied - * in the :ref:`Cluster ` :ref:`transport_socket - * `. + * in the :ref:`Cluster ` :ref:`transport_socket + * `. */ 'cluster_name': (string); + /** + * The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. + * Note that this authority does not override the SNI. The SNI is provided by the transport socket of the cluster. + */ + 'authority': (string); } /** - * [#next-free-field: 7] + * [#next-free-field: 9] */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc { /** * The target URI when using the `Google C++ gRPC client * `_. SSL credentials will be supplied in - * :ref:`channel_credentials `. + * :ref:`channel_credentials `. */ 'target_uri'?: (string); - 'channel_credentials'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_ChannelCredentials); + 'channel_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials); /** * A set of call credentials that can be composed with `channel credentials * `_. */ - 'call_credentials'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials)[]; + 'call_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials)[]; /** * The human readable prefix to use when emitting statistics for the gRPC * service. @@ -181,24 +212,33 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc { * gRPC library. */ 'config'?: (_google_protobuf_Struct); + /** + * How many bytes each stream can buffer internally. + * If not set an implementation defined default is applied (1MiB). + */ + 'per_stream_buffer_limit_bytes'?: (_google_protobuf_UInt32Value); + /** + * Custom channels args. + */ + 'channel_args'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs); } /** - * [#next-free-field: 7] + * [#next-free-field: 9] */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc__Output { /** * The target URI when using the `Google C++ gRPC client * `_. SSL credentials will be supplied in - * :ref:`channel_credentials `. + * :ref:`channel_credentials `. */ 'target_uri': (string); - 'channel_credentials'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc_ChannelCredentials__Output); + 'channel_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output); /** * A set of call credentials that can be composed with `channel credentials * `_. */ - 'call_credentials': (_envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials__Output)[]; + 'call_credentials': (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials__Output)[]; /** * The human readable prefix to use when emitting statistics for the gRPC * service. @@ -222,14 +262,23 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc__Output { * gRPC library. */ 'config'?: (_google_protobuf_Struct__Output); + /** + * How many bytes each stream can buffer internally. + * If not set an implementation defined default is applied (1MiB). + */ + 'per_stream_buffer_limit_bytes'?: (_google_protobuf_UInt32Value__Output); + /** + * Custom channels args. + */ + 'channel_args'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Output); } -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials { 'authorization_token'?: (string); 'authority_selector'?: (string); } -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output { 'authorization_token': (string); 'authority_selector': (string); } @@ -238,36 +287,34 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_Googl * Local channel credentials. Only UDS is supported for now. * See https://github.com/grpc/grpc/pull/15909. */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_GoogleLocalCredentials { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials { } /** * Local channel credentials. Only UDS is supported for now. * See https://github.com/grpc/grpc/pull/15909. */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output { } -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin { 'name'?: (string); - 'config'?: (_google_protobuf_Struct); 'typed_config'?: (_google_protobuf_Any); - 'config_type'?: "config"|"typed_config"; + 'config_type'?: "typed_config"; } -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output { 'name': (string); - 'config'?: (_google_protobuf_Struct__Output); 'typed_config'?: (_google_protobuf_Any__Output); - 'config_type': "config"|"typed_config"; + 'config_type': "typed_config"; } -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials { 'json_key'?: (string); 'token_lifetime_seconds'?: (number | string | Long); } -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output { 'json_key': (string); 'token_lifetime_seconds': (string); } @@ -275,37 +322,37 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_Servi /** * See https://grpc.io/grpc/cpp/structgrpc_1_1_ssl_credentials_options.html. */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_SslCredentials { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials { /** * PEM encoded server root certificates. */ - 'root_certs'?: (_envoy_api_v2_core_DataSource); + 'root_certs'?: (_envoy_config_core_v3_DataSource); /** * PEM encoded client private key. */ - 'private_key'?: (_envoy_api_v2_core_DataSource); + 'private_key'?: (_envoy_config_core_v3_DataSource); /** * PEM encoded client certificate chain. */ - 'cert_chain'?: (_envoy_api_v2_core_DataSource); + 'cert_chain'?: (_envoy_config_core_v3_DataSource); } /** * See https://grpc.io/grpc/cpp/structgrpc_1_1_ssl_credentials_options.html. */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_SslCredentials__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials__Output { /** * PEM encoded server root certificates. */ - 'root_certs'?: (_envoy_api_v2_core_DataSource__Output); + 'root_certs'?: (_envoy_config_core_v3_DataSource__Output); /** * PEM encoded client private key. */ - 'private_key'?: (_envoy_api_v2_core_DataSource__Output); + 'private_key'?: (_envoy_config_core_v3_DataSource__Output); /** * PEM encoded client certificate chain. */ - 'cert_chain'?: (_envoy_api_v2_core_DataSource__Output); + 'cert_chain'?: (_envoy_config_core_v3_DataSource__Output); } /** @@ -315,7 +362,7 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_SslCredentials__Outpu * https://github.com/grpc/grpc/pull/19587. * [#next-free-field: 10] */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_StsService { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService { /** * URI of the token exchange service that handles token exchange requests. * [#comment:TODO(asraa): Add URI validation when implemented. Tracked by @@ -369,7 +416,7 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_StsSe * https://github.com/grpc/grpc/pull/19587. * [#next-free-field: 10] */ -export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_StsService__Output { +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService__Output { /** * URI of the token exchange service that handles token exchange requests. * [#comment:TODO(asraa): Add URI validation when implemented. Tracked by @@ -416,9 +463,29 @@ export interface _envoy_api_v2_core_GrpcService_GoogleGrpc_CallCredentials_StsSe 'actor_token_type': (string); } +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value { + 'string_value'?: (string); + 'int_value'?: (number | string | Long); + /** + * Pointer values are not supported, since they don't make any sense when + * delivered via the API. + */ + 'value_specifier'?: "string_value"|"int_value"; +} + +export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value__Output { + 'string_value'?: (string); + 'int_value'?: (string); + /** + * Pointer values are not supported, since they don't make any sense when + * delivered via the API. + */ + 'value_specifier': "string_value"|"int_value"; +} + /** * gRPC service configuration. This is used by :ref:`ApiConfigSource - * ` and filter configurations. + * ` and filter configurations. * [#next-free-field: 6] */ export interface GrpcService { @@ -427,30 +494,32 @@ export interface GrpcService { * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'envoy_grpc'?: (_envoy_api_v2_core_GrpcService_EnvoyGrpc); + 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc); /** * `Google C++ gRPC client `_ * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'google_grpc'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc); + 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc); /** * The timeout for the gRPC request. This is the timeout for a specific * request. */ 'timeout'?: (_google_protobuf_Duration); /** - * Additional metadata to include in streams initiated to the GrpcService. - * This can be used for scenarios in which additional ad hoc authorization - * headers (e.g. ``x-foo-bar: baz-key``) are to be injected. + * Additional metadata to include in streams initiated to the GrpcService. This can be used for + * scenarios in which additional ad hoc authorization headers (e.g. ``x-foo-bar: baz-key``) are to + * be injected. For more information, including details on header value syntax, see the + * documentation on :ref:`custom request headers + * `. */ - 'initial_metadata'?: (_envoy_api_v2_core_HeaderValue)[]; + 'initial_metadata'?: (_envoy_config_core_v3_HeaderValue)[]; 'target_specifier'?: "envoy_grpc"|"google_grpc"; } /** * gRPC service configuration. This is used by :ref:`ApiConfigSource - * ` and filter configurations. + * ` and filter configurations. * [#next-free-field: 6] */ export interface GrpcService__Output { @@ -459,23 +528,25 @@ export interface GrpcService__Output { * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'envoy_grpc'?: (_envoy_api_v2_core_GrpcService_EnvoyGrpc__Output); + 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc__Output); /** * `Google C++ gRPC client `_ * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'google_grpc'?: (_envoy_api_v2_core_GrpcService_GoogleGrpc__Output); + 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc__Output); /** * The timeout for the gRPC request. This is the timeout for a specific * request. */ 'timeout'?: (_google_protobuf_Duration__Output); /** - * Additional metadata to include in streams initiated to the GrpcService. - * This can be used for scenarios in which additional ad hoc authorization - * headers (e.g. ``x-foo-bar: baz-key``) are to be injected. + * Additional metadata to include in streams initiated to the GrpcService. This can be used for + * scenarios in which additional ad hoc authorization headers (e.g. ``x-foo-bar: baz-key``) are to + * be injected. For more information, including details on header value syntax, see the + * documentation on :ref:`custom request headers + * `. */ - 'initial_metadata': (_envoy_api_v2_core_HeaderValue__Output)[]; + 'initial_metadata': (_envoy_config_core_v3_HeaderValue__Output)[]; 'target_specifier': "envoy_grpc"|"google_grpc"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderMap.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderMap.ts new file mode 100644 index 000000000..3ee496152 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderMap.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; + +/** + * Wrapper for a set of headers. + */ +export interface HeaderMap { + 'headers'?: (_envoy_config_core_v3_HeaderValue)[]; +} + +/** + * Wrapper for a set of headers. + */ +export interface HeaderMap__Output { + 'headers': (_envoy_config_core_v3_HeaderValue__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts new file mode 100644 index 000000000..a9fb6c07a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts @@ -0,0 +1,38 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Header name/value pair. + */ +export interface HeaderValue { + /** + * Header name. + */ + 'key'?: (string); + /** + * Header value. + * + * The same :ref:`format specifier ` as used for + * :ref:`HTTP access logging ` applies here, however + * unknown header values are replaced with the empty string instead of `-`. + */ + 'value'?: (string); +} + +/** + * Header name/value pair. + */ +export interface HeaderValue__Output { + /** + * Header name. + */ + 'key': (string); + /** + * Header value. + * + * The same :ref:`format specifier ` as used for + * :ref:`HTTP access logging ` applies here, however + * unknown header values are replaced with the empty string instead of `-`. + */ + 'value': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts new file mode 100644 index 000000000..4a0fe7120 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +/** + * Header name/value pair plus option to control append behavior. + */ +export interface HeaderValueOption { + /** + * Header name/value pair that this option applies to. + */ + 'header'?: (_envoy_config_core_v3_HeaderValue); + /** + * Should the value be appended? If true (default), the value is appended to + * existing values. Otherwise it replaces any existing values. + */ + 'append'?: (_google_protobuf_BoolValue); +} + +/** + * Header name/value pair plus option to control append behavior. + */ +export interface HeaderValueOption__Output { + /** + * Header name/value pair that this option applies to. + */ + 'header'?: (_envoy_config_core_v3_HeaderValue__Output); + /** + * Should the value be appended? If true (default), the value is appended to + * existing values. Otherwise it replaces any existing values. + */ + 'append'?: (_google_protobuf_BoolValue__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HealthCheck.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts similarity index 67% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/HealthCheck.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts index 0b45042f3..3a7320a08 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HealthCheck.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts @@ -1,49 +1,47 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/health_check.proto +// Original file: deps/envoy-api/envoy/config/core/v3/health_check.proto import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { EventServiceConfig as _envoy_api_v2_core_EventServiceConfig, EventServiceConfig__Output as _envoy_api_v2_core_EventServiceConfig__Output } from '../../../../envoy/api/v2/core/EventServiceConfig'; -import type { HeaderValueOption as _envoy_api_v2_core_HeaderValueOption, HeaderValueOption__Output as _envoy_api_v2_core_HeaderValueOption__Output } from '../../../../envoy/api/v2/core/HeaderValueOption'; -import type { Int64Range as _envoy_type_Int64Range, Int64Range__Output as _envoy_type_Int64Range__Output } from '../../../../envoy/type/Int64Range'; -import type { CodecClientType as _envoy_type_CodecClientType } from '../../../../envoy/type/CodecClientType'; -import type { StringMatcher as _envoy_type_matcher_StringMatcher, StringMatcher__Output as _envoy_type_matcher_StringMatcher__Output } from '../../../../envoy/type/matcher/StringMatcher'; +import type { EventServiceConfig as _envoy_config_core_v3_EventServiceConfig, EventServiceConfig__Output as _envoy_config_core_v3_EventServiceConfig__Output } from '../../../../envoy/config/core/v3/EventServiceConfig'; import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; +import type { CodecClientType as _envoy_type_v3_CodecClientType } from '../../../../envoy/type/v3/CodecClientType'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { Long } from '@grpc/proto-loader'; /** * Custom health check. */ -export interface _envoy_api_v2_core_HealthCheck_CustomHealthCheck { +export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck { /** * The registered name of the custom health checker. */ 'name'?: (string); - 'config'?: (_google_protobuf_Struct); 'typed_config'?: (_google_protobuf_Any); /** * A custom health checker specific configuration which depends on the custom health checker * being instantiated. See :api:`envoy/config/health_checker` for reference. */ - 'config_type'?: "config"|"typed_config"; + 'config_type'?: "typed_config"; } /** * Custom health check. */ -export interface _envoy_api_v2_core_HealthCheck_CustomHealthCheck__Output { +export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output { /** * The registered name of the custom health checker. */ 'name': (string); - 'config'?: (_google_protobuf_Struct__Output); 'typed_config'?: (_google_protobuf_Any__Output); /** * A custom health checker specific configuration which depends on the custom health checker * being instantiated. See :api:`envoy/config/health_checker` for reference. */ - 'config_type': "config"|"typed_config"; + 'config_type': "typed_config"; } /** @@ -52,7 +50,7 @@ export interface _envoy_api_v2_core_HealthCheck_CustomHealthCheck__Output { * healthcheck. See `gRPC doc `_ * for details. */ -export interface _envoy_api_v2_core_HealthCheck_GrpcHealthCheck { +export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck { /** * An optional service name parameter which will be sent to gRPC service in * `grpc.health.v1.HealthCheckRequest @@ -65,7 +63,7 @@ export interface _envoy_api_v2_core_HealthCheck_GrpcHealthCheck { * The value of the :authority header in the gRPC health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The authority header can be customized for a specific endpoint by setting - * the :ref:`hostname ` field. + * the :ref:`hostname ` field. */ 'authority'?: (string); } @@ -76,7 +74,7 @@ export interface _envoy_api_v2_core_HealthCheck_GrpcHealthCheck { * healthcheck. See `gRPC doc `_ * for details. */ -export interface _envoy_api_v2_core_HealthCheck_GrpcHealthCheck__Output { +export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output { /** * An optional service name parameter which will be sent to gRPC service in * `grpc.health.v1.HealthCheckRequest @@ -89,7 +87,7 @@ export interface _envoy_api_v2_core_HealthCheck_GrpcHealthCheck__Output { * The value of the :authority header in the gRPC health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The authority header can be customized for a specific endpoint by setting - * the :ref:`hostname ` field. + * the :ref:`hostname ` field. */ 'authority': (string); } @@ -97,12 +95,12 @@ export interface _envoy_api_v2_core_HealthCheck_GrpcHealthCheck__Output { /** * [#next-free-field: 12] */ -export interface _envoy_api_v2_core_HealthCheck_HttpHealthCheck { +export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { /** * The value of the host header in the HTTP health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The host header can be customized for a specific endpoint by setting the - * :ref:`hostname ` field. + * :ref:`hostname ` field. */ 'host'?: (string); /** @@ -113,69 +111,52 @@ export interface _envoy_api_v2_core_HealthCheck_HttpHealthCheck { /** * [#not-implemented-hide:] HTTP specific payload. */ - 'send'?: (_envoy_api_v2_core_HealthCheck_Payload); + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload); /** * [#not-implemented-hide:] HTTP specific response. */ - 'receive'?: (_envoy_api_v2_core_HealthCheck_Payload); - /** - * An optional service name parameter which is used to validate the identity of - * the health checked cluster. See the :ref:`architecture overview - * ` for more information. - * - * .. attention:: - * - * This field has been deprecated in favor of `service_name_matcher` for better flexibility - * over matching with service-cluster name. - */ - 'service_name'?: (string); + 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload); /** * Specifies a list of HTTP headers that should be added to each request that is sent to the * health checked cluster. For more information, including details on header value syntax, see * the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of HTTP headers that should be removed from each request that is sent to the * health checked cluster. */ 'request_headers_to_remove'?: (string)[]; - /** - * If set, health checks will be made using http/2. - * Deprecated, use :ref:`codec_client_type - * ` instead. - */ - 'use_http2'?: (boolean); /** * Specifies a list of HTTP response statuses considered healthy. If provided, replaces default * 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - * semantics of :ref:`Int64Range `. The start and end of each + * semantics of :ref:`Int64Range `. The start and end of each * range are required. Only statuses in the range [100, 600) are allowed. */ - 'expected_statuses'?: (_envoy_type_Int64Range)[]; + 'expected_statuses'?: (_envoy_type_v3_Int64Range)[]; /** * Use specified application protocol for health checks. */ - 'codec_client_type'?: (_envoy_type_CodecClientType | keyof typeof _envoy_type_CodecClientType); + 'codec_client_type'?: (_envoy_type_v3_CodecClientType | keyof typeof _envoy_type_v3_CodecClientType); /** * An optional service name parameter which is used to validate the identity of * the health checked cluster using a :ref:`StringMatcher - * `. See the :ref:`architecture overview + * `. See the :ref:`architecture overview * ` for more information. */ - 'service_name_matcher'?: (_envoy_type_matcher_StringMatcher); + 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher); } /** * [#next-free-field: 12] */ -export interface _envoy_api_v2_core_HealthCheck_HttpHealthCheck__Output { +export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { /** * The value of the host header in the HTTP health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The host header can be customized for a specific endpoint by setting the - * :ref:`hostname ` field. + * :ref:`hostname ` field. */ 'host': (string); /** @@ -186,64 +167,47 @@ export interface _envoy_api_v2_core_HealthCheck_HttpHealthCheck__Output { /** * [#not-implemented-hide:] HTTP specific payload. */ - 'send'?: (_envoy_api_v2_core_HealthCheck_Payload__Output); + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload__Output); /** * [#not-implemented-hide:] HTTP specific response. */ - 'receive'?: (_envoy_api_v2_core_HealthCheck_Payload__Output); - /** - * An optional service name parameter which is used to validate the identity of - * the health checked cluster. See the :ref:`architecture overview - * ` for more information. - * - * .. attention:: - * - * This field has been deprecated in favor of `service_name_matcher` for better flexibility - * over matching with service-cluster name. - */ - 'service_name': (string); + 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload__Output); /** * Specifies a list of HTTP headers that should be added to each request that is sent to the * health checked cluster. For more information, including details on header value syntax, see * the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of HTTP headers that should be removed from each request that is sent to the * health checked cluster. */ 'request_headers_to_remove': (string)[]; - /** - * If set, health checks will be made using http/2. - * Deprecated, use :ref:`codec_client_type - * ` instead. - */ - 'use_http2': (boolean); /** * Specifies a list of HTTP response statuses considered healthy. If provided, replaces default * 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - * semantics of :ref:`Int64Range `. The start and end of each + * semantics of :ref:`Int64Range `. The start and end of each * range are required. Only statuses in the range [100, 600) are allowed. */ - 'expected_statuses': (_envoy_type_Int64Range__Output)[]; + 'expected_statuses': (_envoy_type_v3_Int64Range__Output)[]; /** * Use specified application protocol for health checks. */ - 'codec_client_type': (keyof typeof _envoy_type_CodecClientType); + 'codec_client_type': (keyof typeof _envoy_type_v3_CodecClientType); /** * An optional service name parameter which is used to validate the identity of * the health checked cluster using a :ref:`StringMatcher - * `. See the :ref:`architecture overview + * `. See the :ref:`architecture overview * ` for more information. */ - 'service_name_matcher'?: (_envoy_type_matcher_StringMatcher__Output); + 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher__Output); } /** * Describes the encoding of the payload bytes in the payload. */ -export interface _envoy_api_v2_core_HealthCheck_Payload { +export interface _envoy_config_core_v3_HealthCheck_Payload { /** * Hex encoded payload. E.g., "000000FF". */ @@ -258,7 +222,7 @@ export interface _envoy_api_v2_core_HealthCheck_Payload { /** * Describes the encoding of the payload bytes in the payload. */ -export interface _envoy_api_v2_core_HealthCheck_Payload__Output { +export interface _envoy_config_core_v3_HealthCheck_Payload__Output { /** * Hex encoded payload. E.g., "000000FF". */ @@ -270,7 +234,7 @@ export interface _envoy_api_v2_core_HealthCheck_Payload__Output { 'payload': "text"|"binary"; } -export interface _envoy_api_v2_core_HealthCheck_RedisHealthCheck { +export interface _envoy_config_core_v3_HealthCheck_RedisHealthCheck { /** * If set, optionally perform ``EXISTS `` instead of ``PING``. A return value * from Redis of 0 (does not exist) is considered a passing healthcheck. A return value other @@ -280,7 +244,7 @@ export interface _envoy_api_v2_core_HealthCheck_RedisHealthCheck { 'key'?: (string); } -export interface _envoy_api_v2_core_HealthCheck_RedisHealthCheck__Output { +export interface _envoy_config_core_v3_HealthCheck_RedisHealthCheck__Output { /** * If set, optionally perform ``EXISTS `` instead of ``PING``. A return value * from Redis of 0 (does not exist) is considered a passing healthcheck. A return value other @@ -290,30 +254,30 @@ export interface _envoy_api_v2_core_HealthCheck_RedisHealthCheck__Output { 'key': (string); } -export interface _envoy_api_v2_core_HealthCheck_TcpHealthCheck { +export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck { /** * Empty payloads imply a connect-only health check. */ - 'send'?: (_envoy_api_v2_core_HealthCheck_Payload); + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload); /** * When checking the response, “fuzzy” matching is performed such that each * binary block must be found, and in the order specified, but not * necessarily contiguous. */ - 'receive'?: (_envoy_api_v2_core_HealthCheck_Payload)[]; + 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload)[]; } -export interface _envoy_api_v2_core_HealthCheck_TcpHealthCheck__Output { +export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output { /** * Empty payloads imply a connect-only health check. */ - 'send'?: (_envoy_api_v2_core_HealthCheck_Payload__Output); + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload__Output); /** * When checking the response, “fuzzy” matching is performed such that each * binary block must be found, and in the order specified, but not * necessarily contiguous. */ - 'receive': (_envoy_api_v2_core_HealthCheck_Payload__Output)[]; + 'receive': (_envoy_config_core_v3_HealthCheck_Payload__Output)[]; } /** @@ -322,11 +286,11 @@ export interface _envoy_api_v2_core_HealthCheck_TcpHealthCheck__Output { * * This allows overriding the cluster TLS settings, just for health check connections. */ -export interface _envoy_api_v2_core_HealthCheck_TlsOptions { +export interface _envoy_config_core_v3_HealthCheck_TlsOptions { /** * Specifies the ALPN protocols for health check connections. This is useful if the * corresponding upstream is using ALPN-based :ref:`FilterChainMatch - * ` along with different protocols for health checks + * ` along with different protocols for health checks * versus data connections. If empty, no ALPN protocols will be set on health check connections. */ 'alpn_protocols'?: (string)[]; @@ -338,18 +302,18 @@ export interface _envoy_api_v2_core_HealthCheck_TlsOptions { * * This allows overriding the cluster TLS settings, just for health check connections. */ -export interface _envoy_api_v2_core_HealthCheck_TlsOptions__Output { +export interface _envoy_config_core_v3_HealthCheck_TlsOptions__Output { /** * Specifies the ALPN protocols for health check connections. This is useful if the * corresponding upstream is using ALPN-based :ref:`FilterChainMatch - * ` along with different protocols for health checks + * ` along with different protocols for health checks * versus data connections. If empty, no ALPN protocols will be set on health check connections. */ 'alpn_protocols': (string)[]; } /** - * [#next-free-field: 23] + * [#next-free-field: 25] */ export interface HealthCheck { /** @@ -389,15 +353,15 @@ export interface HealthCheck { /** * HTTP health check. */ - 'http_health_check'?: (_envoy_api_v2_core_HealthCheck_HttpHealthCheck); + 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck); /** * TCP health check. */ - 'tcp_health_check'?: (_envoy_api_v2_core_HealthCheck_TcpHealthCheck); + 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck); /** * gRPC health check. */ - 'grpc_health_check'?: (_envoy_api_v2_core_HealthCheck_GrpcHealthCheck); + 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck); /** * The "no traffic interval" is a special health check interval that is used when a cluster has * never had traffic routed to it. This lower interval allows cluster information to be kept up to @@ -412,7 +376,7 @@ export interface HealthCheck { /** * Custom health check. */ - 'custom_health_check'?: (_envoy_api_v2_core_HealthCheck_CustomHealthCheck); + 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck); /** * The "unhealthy interval" is a health check interval that is used for hosts that are marked as * unhealthy. As soon as the host is marked as healthy, Envoy will shift back to using the @@ -467,18 +431,67 @@ export interface HealthCheck { /** * This allows overriding the cluster TLS settings, just for health check connections. */ - 'tls_options'?: (_envoy_api_v2_core_HealthCheck_TlsOptions); + 'tls_options'?: (_envoy_config_core_v3_HealthCheck_TlsOptions); /** * [#not-implemented-hide:] * The gRPC service for the health check event service. * If empty, health check events won't be sent to a remote endpoint. */ - 'event_service'?: (_envoy_api_v2_core_EventServiceConfig); + 'event_service'?: (_envoy_config_core_v3_EventServiceConfig); + /** + * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's + * :ref:`tranport socket matches `. + * For example, the following match criteria + * + * .. code-block:: yaml + * + * transport_socket_match_criteria: + * useMTLS: true + * + * Will match the following :ref:`cluster socket match ` + * + * .. code-block:: yaml + * + * transport_socket_matches: + * - name: "useMTLS" + * match: + * useMTLS: true + * transport_socket: + * name: envoy.transport_sockets.tls + * config: { ... } # tls socket configuration + * + * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the + * :ref:`LbEndpoint.Metadata `. + * This allows using different transport socket capabilities for health checking versus proxying to the + * endpoint. + * + * If the key/values pairs specified do not match any + * :ref:`transport socket matches `, + * the cluster's :ref:`transport socket ` + * will be used for health check socket configuration. + */ + 'transport_socket_match_criteria'?: (_google_protobuf_Struct); + /** + * The "no traffic healthy interval" is a special health check interval that + * is used for hosts that are currently passing active health checking + * (including new hosts) when the cluster has received no traffic. + * + * This is useful for when we want to send frequent health checks with + * `no_traffic_interval` but then revert to lower frequency `no_traffic_healthy_interval` once + * a host in the cluster is marked as healthy. + * + * Once a cluster has been used for traffic routing, Envoy will shift back to using the + * standard health check interval that is defined. + * + * If no_traffic_healthy_interval is not set, it will default to the + * no traffic interval and send that interval regardless of health state. + */ + 'no_traffic_healthy_interval'?: (_google_protobuf_Duration); 'health_checker'?: "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; } /** - * [#next-free-field: 23] + * [#next-free-field: 25] */ export interface HealthCheck__Output { /** @@ -518,15 +531,15 @@ export interface HealthCheck__Output { /** * HTTP health check. */ - 'http_health_check'?: (_envoy_api_v2_core_HealthCheck_HttpHealthCheck__Output); + 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output); /** * TCP health check. */ - 'tcp_health_check'?: (_envoy_api_v2_core_HealthCheck_TcpHealthCheck__Output); + 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output); /** * gRPC health check. */ - 'grpc_health_check'?: (_envoy_api_v2_core_HealthCheck_GrpcHealthCheck__Output); + 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output); /** * The "no traffic interval" is a special health check interval that is used when a cluster has * never had traffic routed to it. This lower interval allows cluster information to be kept up to @@ -541,7 +554,7 @@ export interface HealthCheck__Output { /** * Custom health check. */ - 'custom_health_check'?: (_envoy_api_v2_core_HealthCheck_CustomHealthCheck__Output); + 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output); /** * The "unhealthy interval" is a health check interval that is used for hosts that are marked as * unhealthy. As soon as the host is marked as healthy, Envoy will shift back to using the @@ -596,12 +609,61 @@ export interface HealthCheck__Output { /** * This allows overriding the cluster TLS settings, just for health check connections. */ - 'tls_options'?: (_envoy_api_v2_core_HealthCheck_TlsOptions__Output); + 'tls_options'?: (_envoy_config_core_v3_HealthCheck_TlsOptions__Output); /** * [#not-implemented-hide:] * The gRPC service for the health check event service. * If empty, health check events won't be sent to a remote endpoint. */ - 'event_service'?: (_envoy_api_v2_core_EventServiceConfig__Output); + 'event_service'?: (_envoy_config_core_v3_EventServiceConfig__Output); + /** + * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's + * :ref:`tranport socket matches `. + * For example, the following match criteria + * + * .. code-block:: yaml + * + * transport_socket_match_criteria: + * useMTLS: true + * + * Will match the following :ref:`cluster socket match ` + * + * .. code-block:: yaml + * + * transport_socket_matches: + * - name: "useMTLS" + * match: + * useMTLS: true + * transport_socket: + * name: envoy.transport_sockets.tls + * config: { ... } # tls socket configuration + * + * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the + * :ref:`LbEndpoint.Metadata `. + * This allows using different transport socket capabilities for health checking versus proxying to the + * endpoint. + * + * If the key/values pairs specified do not match any + * :ref:`transport socket matches `, + * the cluster's :ref:`transport socket ` + * will be used for health check socket configuration. + */ + 'transport_socket_match_criteria'?: (_google_protobuf_Struct__Output); + /** + * The "no traffic healthy interval" is a special health check interval that + * is used for hosts that are currently passing active health checking + * (including new hosts) when the cluster has received no traffic. + * + * This is useful for when we want to send frequent health checks with + * `no_traffic_interval` but then revert to lower frequency `no_traffic_healthy_interval` once + * a host in the cluster is marked as healthy. + * + * Once a cluster has been used for traffic routing, Envoy will shift back to using the + * standard health check interval that is defined. + * + * If no_traffic_healthy_interval is not set, it will default to the + * no traffic interval and send that interval regardless of health state. + */ + 'no_traffic_healthy_interval'?: (_google_protobuf_Duration__Output); 'health_checker': "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HealthStatus.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts similarity index 91% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/HealthStatus.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts index e1d572fa4..6ecbca272 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HealthStatus.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/health_check.proto +// Original file: deps/envoy-api/envoy/config/core/v3/health_check.proto /** * Endpoint health status. diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Http1ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts similarity index 57% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/Http1ProtocolOptions.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts index b9bb0ce54..982f58b0e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Http1ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts @@ -1,8 +1,8 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/protocol.proto +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -export interface _envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat { +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat { /** * Formats the header by proper casing words: the first character and any character following * a special character will be capitalized if it's an alpha character. For example, @@ -10,11 +10,11 @@ export interface _envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat { * Note that while this results in most headers following conventional casing, certain headers * are not covered. For example, the "TE" header will be formatted as "Te". */ - 'proper_case_words'?: (_envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords); + 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords); 'header_format'?: "proper_case_words"; } -export interface _envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat__Output { +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Output { /** * Formats the header by proper casing words: the first character and any character following * a special character will be capitalized if it's an alpha character. For example, @@ -22,18 +22,18 @@ export interface _envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat__Output * Note that while this results in most headers following conventional casing, certain headers * are not covered. For example, the "TE" header will be formatted as "Te". */ - 'proper_case_words'?: (_envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output); + 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output); 'header_format': "proper_case_words"; } -export interface _envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords { +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords { } -export interface _envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output { +export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output { } /** - * [#next-free-field: 6] + * [#next-free-field: 8] */ export interface Http1ProtocolOptions { /** @@ -60,7 +60,7 @@ export interface Http1ProtocolOptions { * Describes how the keys for response headers should be formatted. By default, all header keys * are lower cased. */ - 'header_key_format'?: (_envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat); + 'header_key_format'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat); /** * Enables trailers for HTTP/1. By default the HTTP/1 codec drops proxied trailers. * @@ -73,10 +73,30 @@ export interface Http1ProtocolOptions { * - The content length header is not present. */ 'enable_trailers'?: (boolean); + /** + * Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding` + * headers set. By default such messages are rejected, but if option is enabled - Envoy will + * remove Content-Length header and process message. + * See `RFC7230, sec. 3.3.3 ` for details. + * + * .. attention:: + * Enabling this option might lead to request smuggling vulnerability, especially if traffic + * is proxied via multiple layers of proxies. + */ + 'allow_chunked_length'?: (boolean); + /** + * Allows invalid HTTP messaging. When this option is false, then Envoy will terminate + * HTTP/1.1 connections upon receiving an invalid HTTP message. However, + * when this option is true, then Envoy will leave the HTTP/1.1 connection + * open where possible. + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue); } /** - * [#next-free-field: 6] + * [#next-free-field: 8] */ export interface Http1ProtocolOptions__Output { /** @@ -103,7 +123,7 @@ export interface Http1ProtocolOptions__Output { * Describes how the keys for response headers should be formatted. By default, all header keys * are lower cased. */ - 'header_key_format'?: (_envoy_api_v2_core_Http1ProtocolOptions_HeaderKeyFormat__Output); + 'header_key_format'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Output); /** * Enables trailers for HTTP/1. By default the HTTP/1 codec drops proxied trailers. * @@ -116,4 +136,24 @@ export interface Http1ProtocolOptions__Output { * - The content length header is not present. */ 'enable_trailers': (boolean); + /** + * Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding` + * headers set. By default such messages are rejected, but if option is enabled - Envoy will + * remove Content-Length header and process message. + * See `RFC7230, sec. 3.3.3 ` for details. + * + * .. attention:: + * Enabling this option might lead to request smuggling vulnerability, especially if traffic + * is proxied via multiple layers of proxies. + */ + 'allow_chunked_length': (boolean); + /** + * Allows invalid HTTP messaging. When this option is false, then Envoy will terminate + * HTTP/1.1 connections upon receiving an invalid HTTP message. However, + * when this option is true, then Envoy will leave the HTTP/1.1 connection + * open where possible. + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Http2ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts similarity index 65% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/Http2ProtocolOptions.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts index 893f61a15..e379d44be 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Http2ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts @@ -1,12 +1,14 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/protocol.proto +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { KeepaliveSettings as _envoy_config_core_v3_KeepaliveSettings, KeepaliveSettings__Output as _envoy_config_core_v3_KeepaliveSettings__Output } from '../../../../envoy/config/core/v3/KeepaliveSettings'; /** * Defines a parameter to be sent in the SETTINGS frame. * See `RFC7540, sec. 6.5.1 `_ for details. */ -export interface _envoy_api_v2_core_Http2ProtocolOptions_SettingsParameter { +export interface _envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter { /** * The 16 bit parameter identifier. */ @@ -21,7 +23,7 @@ export interface _envoy_api_v2_core_Http2ProtocolOptions_SettingsParameter { * Defines a parameter to be sent in the SETTINGS frame. * See `RFC7540, sec. 6.5.1 `_ for details. */ -export interface _envoy_api_v2_core_Http2ProtocolOptions_SettingsParameter__Output { +export interface _envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter__Output { /** * The 16 bit parameter identifier. */ @@ -33,7 +35,7 @@ export interface _envoy_api_v2_core_Http2ProtocolOptions_SettingsParameter__Outp } /** - * [#next-free-field: 14] + * [#next-free-field: 16] */ export interface Http2ProtocolOptions { /** @@ -81,7 +83,7 @@ export interface Http2ProtocolOptions { * Still under implementation. DO NOT USE. * * Allows metadata. See [metadata - * docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + * docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more * information. */ 'allow_metadata'?: (boolean); @@ -90,7 +92,8 @@ export interface Http2ProtocolOptions { * be written into the socket). Exceeding this limit triggers flood mitigation and connection is * terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due * to flood mitigation. The default limit is 10000. - * [#comment:TODO: implement same limits for upstream outbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_frames'?: (_google_protobuf_UInt32Value); /** @@ -99,7 +102,8 @@ export interface Http2ProtocolOptions { * this limit triggers flood mitigation and connection is terminated. The * ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood * mitigation. The default limit is 1000. - * [#comment:TODO: implement same limits for upstream outbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_control_frames'?: (_google_protobuf_UInt32Value); /** @@ -109,7 +113,8 @@ export interface Http2ProtocolOptions { * stat tracks the number of connections terminated due to flood mitigation. * Setting this to 0 will terminate connection upon receiving first frame with an empty payload * and no end stream flag. The default limit is 1. - * [#comment:TODO: implement same limits for upstream inbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_consecutive_inbound_frames_with_empty_payload'?: (_google_protobuf_UInt32Value); /** @@ -117,11 +122,15 @@ export interface Http2ProtocolOptions { * of PRIORITY frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * max_inbound_priority_frames_per_stream * (1 + inbound_streams) + * max_inbound_priority_frames_per_stream * (1 + opened_streams) * - * the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connection the + * `opened_streams` is incremented when Envoy send the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks * the number of connections terminated due to flood mitigation. The default limit is 100. - * [#comment:TODO: implement same limits for upstream inbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_priority_frames_per_stream'?: (_google_protobuf_UInt32Value); /** @@ -129,14 +138,18 @@ export interface Http2ProtocolOptions { * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * 1 + 2 * (inbound_streams + + * 5 + 2 * (opened_streams + * max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames) * - * the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks - * the number of connections terminated due to flood mitigation. The default limit is 10. + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connections the + * `opened_streams` is incremented when Envoy sends the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks the number of connections terminated due to + * flood mitigation. The default max_inbound_window_update_frames_per_data_frame_sent value is 10. * Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control, * but more complex implementations that try to estimate available bandwidth require at least 2. - * [#comment:TODO: implement same limits for upstream inbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_window_update_frames_per_data_frame_sent'?: (_google_protobuf_UInt32Value); /** @@ -144,6 +157,13 @@ export interface Http2ProtocolOptions { * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, * when this option is enabled, only the offending stream is terminated. * + * This is overridden by HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * iff present. + * + * This is deprecated in favor of :ref:`override_stream_error_on_invalid_http_message + * ` + * * See `RFC7540, sec. 8.1 `_ for details. */ 'stream_error_on_invalid_http_messaging'?: (boolean); @@ -175,11 +195,27 @@ export interface Http2ProtocolOptions { * `_ for * standardized identifiers. */ - 'custom_settings_parameters'?: (_envoy_api_v2_core_Http2ProtocolOptions_SettingsParameter)[]; + 'custom_settings_parameters'?: (_envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter)[]; + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * This overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * + * See `RFC7540, sec. 8.1 `_ for details. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue); + /** + * Send HTTP/2 PING frames to verify that the connection is still healthy. If the remote peer + * does not respond within the configured timeout, the connection will be aborted. + */ + 'connection_keepalive'?: (_envoy_config_core_v3_KeepaliveSettings); } /** - * [#next-free-field: 14] + * [#next-free-field: 16] */ export interface Http2ProtocolOptions__Output { /** @@ -227,7 +263,7 @@ export interface Http2ProtocolOptions__Output { * Still under implementation. DO NOT USE. * * Allows metadata. See [metadata - * docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + * docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more * information. */ 'allow_metadata': (boolean); @@ -236,7 +272,8 @@ export interface Http2ProtocolOptions__Output { * be written into the socket). Exceeding this limit triggers flood mitigation and connection is * terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due * to flood mitigation. The default limit is 10000. - * [#comment:TODO: implement same limits for upstream outbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_frames'?: (_google_protobuf_UInt32Value__Output); /** @@ -245,7 +282,8 @@ export interface Http2ProtocolOptions__Output { * this limit triggers flood mitigation and connection is terminated. The * ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood * mitigation. The default limit is 1000. - * [#comment:TODO: implement same limits for upstream outbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_control_frames'?: (_google_protobuf_UInt32Value__Output); /** @@ -255,7 +293,8 @@ export interface Http2ProtocolOptions__Output { * stat tracks the number of connections terminated due to flood mitigation. * Setting this to 0 will terminate connection upon receiving first frame with an empty payload * and no end stream flag. The default limit is 1. - * [#comment:TODO: implement same limits for upstream inbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_consecutive_inbound_frames_with_empty_payload'?: (_google_protobuf_UInt32Value__Output); /** @@ -263,11 +302,15 @@ export interface Http2ProtocolOptions__Output { * of PRIORITY frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * max_inbound_priority_frames_per_stream * (1 + inbound_streams) + * max_inbound_priority_frames_per_stream * (1 + opened_streams) * - * the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connection the + * `opened_streams` is incremented when Envoy send the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks * the number of connections terminated due to flood mitigation. The default limit is 100. - * [#comment:TODO: implement same limits for upstream inbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_priority_frames_per_stream'?: (_google_protobuf_UInt32Value__Output); /** @@ -275,14 +318,18 @@ export interface Http2ProtocolOptions__Output { * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * 1 + 2 * (inbound_streams + + * 5 + 2 * (opened_streams + * max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames) * - * the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks - * the number of connections terminated due to flood mitigation. The default limit is 10. + * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * Envoy receives complete response headers from the upstream server. For upstream connections the + * `opened_streams` is incremented when Envoy sends the HEADERS frame for a new stream. The + * ``http2.inbound_priority_frames_flood`` stat tracks the number of connections terminated due to + * flood mitigation. The default max_inbound_window_update_frames_per_data_frame_sent value is 10. * Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control, * but more complex implementations that try to estimate available bandwidth require at least 2. - * [#comment:TODO: implement same limits for upstream inbound frames as well.] + * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the + * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_window_update_frames_per_data_frame_sent'?: (_google_protobuf_UInt32Value__Output); /** @@ -290,6 +337,13 @@ export interface Http2ProtocolOptions__Output { * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, * when this option is enabled, only the offending stream is terminated. * + * This is overridden by HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * iff present. + * + * This is deprecated in favor of :ref:`override_stream_error_on_invalid_http_message + * ` + * * See `RFC7540, sec. 8.1 `_ for details. */ 'stream_error_on_invalid_http_messaging': (boolean); @@ -321,5 +375,21 @@ export interface Http2ProtocolOptions__Output { * `_ for * standardized identifiers. */ - 'custom_settings_parameters': (_envoy_api_v2_core_Http2ProtocolOptions_SettingsParameter__Output)[]; + 'custom_settings_parameters': (_envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter__Output)[]; + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * This overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * ` + * + * See `RFC7540, sec. 8.1 `_ for details. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue__Output); + /** + * Send HTTP/2 PING frames to verify that the connection is still healthy. If the remote peer + * does not respond within the configured timeout, the connection will be aborted. + */ + 'connection_keepalive'?: (_envoy_config_core_v3_KeepaliveSettings__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts new file mode 100644 index 000000000..320285d87 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts @@ -0,0 +1,22 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + + +/** + * [#not-implemented-hide:] + * + * A message which allows using HTTP/3 as an upstream protocol. + * + * Eventually this will include configuration for tuning HTTP/3. + */ +export interface Http3ProtocolOptions { +} + +/** + * [#not-implemented-hide:] + * + * A message which allows using HTTP/3 as an upstream protocol. + * + * Eventually this will include configuration for tuning HTTP/3. + */ +export interface Http3ProtocolOptions__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts similarity index 75% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpProtocolOptions.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts index 219fdb0c7..4a5efeebd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts @@ -1,9 +1,9 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/protocol.proto +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -// Original file: deps/envoy-api/envoy/api/v2/core/protocol.proto +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto /** * Action to take when Envoy receives client request with header names containing underscore @@ -12,7 +12,7 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a * as a security measure due to systems that treat '_' and '-' as interchangeable. Envoy by default allows client request headers with underscore * characters. */ -export enum _envoy_api_v2_core_HttpProtocolOptions_HeadersWithUnderscoresAction { +export enum _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction { /** * Allow headers with underscores. This is the default behavior. */ @@ -41,13 +41,17 @@ export interface HttpProtocolOptions { * idle timeout is reached the connection will be closed. If the connection is an HTTP/2 * downstream connection a drain sequence will occur prior to closing the connection, see * :ref:`drain_timeout - * `. + * `. * Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. * If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. * * .. warning:: * Disabling this timeout has a highly likelihood of yielding connection leaks due to lost TCP * FIN packets, etc. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled for downstream connections according to the value for + * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. */ 'idle_timeout'?: (_google_protobuf_Duration); /** @@ -61,7 +65,7 @@ export interface HttpProtocolOptions { * was established. If not set, there is no max duration. When max_connection_duration is reached * the connection will be closed. Drain sequence will occur prior to closing the connection if * if's applicable. See :ref:`drain_timeout - * `. + * `. * Note: not implemented for upstream connections. */ 'max_connection_duration'?: (_google_protobuf_Duration); @@ -75,7 +79,7 @@ export interface HttpProtocolOptions { * If this setting is not specified, the value defaults to ALLOW. * Note: upstream responses are not affected by this setting. */ - 'headers_with_underscores_action'?: (_envoy_api_v2_core_HttpProtocolOptions_HeadersWithUnderscoresAction | keyof typeof _envoy_api_v2_core_HttpProtocolOptions_HeadersWithUnderscoresAction); + 'headers_with_underscores_action'?: (_envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction | keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); } /** @@ -88,13 +92,17 @@ export interface HttpProtocolOptions__Output { * idle timeout is reached the connection will be closed. If the connection is an HTTP/2 * downstream connection a drain sequence will occur prior to closing the connection, see * :ref:`drain_timeout - * `. + * `. * Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. * If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. * * .. warning:: * Disabling this timeout has a highly likelihood of yielding connection leaks due to lost TCP * FIN packets, etc. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled for downstream connections according to the value for + * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. */ 'idle_timeout'?: (_google_protobuf_Duration__Output); /** @@ -108,7 +116,7 @@ export interface HttpProtocolOptions__Output { * was established. If not set, there is no max duration. When max_connection_duration is reached * the connection will be closed. Drain sequence will occur prior to closing the connection if * if's applicable. See :ref:`drain_timeout - * `. + * `. * Note: not implemented for upstream connections. */ 'max_connection_duration'?: (_google_protobuf_Duration__Output); @@ -122,5 +130,5 @@ export interface HttpProtocolOptions__Output { * If this setting is not specified, the value defaults to ALLOW. * Note: upstream responses are not affected by this setting. */ - 'headers_with_underscores_action': (keyof typeof _envoy_api_v2_core_HttpProtocolOptions_HeadersWithUnderscoresAction); + 'headers_with_underscores_action': (keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts new file mode 100644 index 000000000..5da6ed4c0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts @@ -0,0 +1,79 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/http_uri.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * Envoy external URI descriptor + */ +export interface HttpUri { + /** + * The HTTP server URI. It should be a full FQDN with protocol, host and path. + * + * Example: + * + * .. code-block:: yaml + * + * uri: https://www.googleapis.com/oauth2/v1/certs + */ + 'uri'?: (string); + /** + * A cluster is created in the Envoy "cluster_manager" config + * section. This field specifies the cluster name. + * + * Example: + * + * .. code-block:: yaml + * + * cluster: jwks_cluster + */ + 'cluster'?: (string); + /** + * Sets the maximum duration in milliseconds that a response can take to arrive upon request. + */ + 'timeout'?: (_google_protobuf_Duration); + /** + * Specify how `uri` is to be fetched. Today, this requires an explicit + * cluster, but in the future we may support dynamic cluster creation or + * inline DNS resolution. See `issue + * `_. + */ + 'http_upstream_type'?: "cluster"; +} + +/** + * Envoy external URI descriptor + */ +export interface HttpUri__Output { + /** + * The HTTP server URI. It should be a full FQDN with protocol, host and path. + * + * Example: + * + * .. code-block:: yaml + * + * uri: https://www.googleapis.com/oauth2/v1/certs + */ + 'uri': (string); + /** + * A cluster is created in the Envoy "cluster_manager" config + * section. This field specifies the cluster name. + * + * Example: + * + * .. code-block:: yaml + * + * cluster: jwks_cluster + */ + 'cluster'?: (string); + /** + * Sets the maximum duration in milliseconds that a response can take to arrive upon request. + */ + 'timeout'?: (_google_protobuf_Duration__Output); + /** + * Specify how `uri` is to be fetched. Today, this requires an explicit + * cluster, but in the future we may support dynamic cluster creation or + * inline DNS resolution. See `issue + * `_. + */ + 'http_upstream_type': "cluster"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts new file mode 100644 index 000000000..cb2e14c58 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts @@ -0,0 +1,40 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; + +export interface KeepaliveSettings { + /** + * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. + */ + 'interval'?: (_google_protobuf_Duration); + /** + * How long to wait for a response to a keepalive PING. If a response is not received within this + * time period, the connection will be aborted. + */ + 'timeout'?: (_google_protobuf_Duration); + /** + * A random jitter amount as a percentage of interval that will be added to each interval. + * A value of zero means there will be no jitter. + * The default value is 15%. + */ + 'interval_jitter'?: (_envoy_type_v3_Percent); +} + +export interface KeepaliveSettings__Output { + /** + * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. + */ + 'interval'?: (_google_protobuf_Duration__Output); + /** + * How long to wait for a response to a keepalive PING. If a response is not received within this + * time period, the connection will be aborted. + */ + 'timeout'?: (_google_protobuf_Duration__Output); + /** + * A random jitter amount as a percentage of interval that will be added to each interval. + * A value of zero means there will be no jitter. + * The default value is 15%. + */ + 'interval_jitter'?: (_envoy_type_v3_Percent__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts new file mode 100644 index 000000000..2b4b42a75 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts @@ -0,0 +1,56 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Identifies location of where either Envoy runs or where upstream hosts run. + */ +export interface Locality { + /** + * Region this :ref:`zone ` belongs to. + */ + 'region'?: (string); + /** + * Defines the local service zone where Envoy is running. Though optional, it + * should be set if discovery service routing is used and the discovery + * service exposes :ref:`zone data `, + * either in this message or via :option:`--service-zone`. The meaning of zone + * is context dependent, e.g. `Availability Zone (AZ) + * `_ + * on AWS, `Zone `_ on + * GCP, etc. + */ + 'zone'?: (string); + /** + * When used for locality of upstream hosts, this field further splits zone + * into smaller chunks of sub-zones so they can be load balanced + * independently. + */ + 'sub_zone'?: (string); +} + +/** + * Identifies location of where either Envoy runs or where upstream hosts run. + */ +export interface Locality__Output { + /** + * Region this :ref:`zone ` belongs to. + */ + 'region': (string); + /** + * Defines the local service zone where Envoy is running. Though optional, it + * should be set if discovery service routing is used and the discovery + * service exposes :ref:`zone data `, + * either in this message or via :option:`--service-zone`. The meaning of zone + * is context dependent, e.g. `Availability Zone (AZ) + * `_ + * on AWS, `Zone `_ on + * GCP, etc. + */ + 'zone': (string); + /** + * When used for locality of upstream hosts, this field further splits zone + * into smaller chunks of sub-zones so they can be load balanced + * independently. + */ + 'sub_zone': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts new file mode 100644 index 000000000..6d7181f18 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; + +/** + * Metadata provides additional inputs to filters based on matched listeners, + * filter chains, routes and endpoints. It is structured as a map, usually from + * filter name (in reverse DNS format) to metadata specific to the filter. Metadata + * key-values for a filter are merged as connection and request handling occurs, + * with later values for the same key overriding earlier values. + * + * An example use of metadata is providing additional values to + * http_connection_manager in the envoy.http_connection_manager.access_log + * namespace. + * + * Another example use of metadata is to per service config info in cluster metadata, which may get + * consumed by multiple filters. + * + * For load balancing, Metadata provides a means to subset cluster endpoints. + * Endpoints have a Metadata object associated and routes contain a Metadata + * object to match against. There are some well defined metadata used today for + * this purpose: + * + * * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an + * endpoint and is also used during header processing + * (x-envoy-upstream-canary) and for stats purposes. + * [#next-major-version: move to type/metadata/v2] + */ +export interface Metadata { + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + */ + 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct}); +} + +/** + * Metadata provides additional inputs to filters based on matched listeners, + * filter chains, routes and endpoints. It is structured as a map, usually from + * filter name (in reverse DNS format) to metadata specific to the filter. Metadata + * key-values for a filter are merged as connection and request handling occurs, + * with later values for the same key overriding earlier values. + * + * An example use of metadata is providing additional values to + * http_connection_manager in the envoy.http_connection_manager.access_log + * namespace. + * + * Another example use of metadata is to per service config info in cluster metadata, which may get + * consumed by multiple filters. + * + * For load balancing, Metadata provides a means to subset cluster endpoints. + * Endpoints have a Metadata object associated and routes contain a Metadata + * object to match against. There are some well defined metadata used today for + * this purpose: + * + * * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an + * endpoint and is also used during header processing + * (x-envoy-upstream-canary) and for stats purposes. + * [#next-major-version: move to type/metadata/v2] + */ +export interface Metadata__Output { + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + */ + 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct__Output}); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts new file mode 100644 index 000000000..717263976 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts @@ -0,0 +1,159 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _envoy_config_core_v3_Locality__Output } from '../../../../envoy/config/core/v3/Locality'; +import type { BuildVersion as _envoy_config_core_v3_BuildVersion, BuildVersion__Output as _envoy_config_core_v3_BuildVersion__Output } from '../../../../envoy/config/core/v3/BuildVersion'; +import type { Extension as _envoy_config_core_v3_Extension, Extension__Output as _envoy_config_core_v3_Extension__Output } from '../../../../envoy/config/core/v3/Extension'; +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; + +/** + * Identifies a specific Envoy instance. The node identifier is presented to the + * management server, which may use this identifier to distinguish per Envoy + * configuration for serving. + * [#next-free-field: 12] + */ +export interface Node { + /** + * An opaque node identifier for the Envoy node. This also provides the local + * service node name. It should be set if any of the following features are + * used: :ref:`statsd `, :ref:`CDS + * `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-node`. + */ + 'id'?: (string); + /** + * Defines the local service cluster name where Envoy is running. Though + * optional, it should be set if any of the following features are used: + * :ref:`statsd `, :ref:`health check cluster + * verification + * `, + * :ref:`runtime override directory `, + * :ref:`user agent addition + * `, + * :ref:`HTTP global rate limiting `, + * :ref:`CDS `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-cluster`. + */ + 'cluster'?: (string); + /** + * Opaque metadata extending the node identifier. Envoy will pass this + * directly to the management server. + */ + 'metadata'?: (_google_protobuf_Struct); + /** + * Locality specifying where the Envoy instance is running. + */ + 'locality'?: (_envoy_config_core_v3_Locality); + /** + * Free-form string that identifies the entity requesting config. + * E.g. "envoy" or "grpc" + */ + 'user_agent_name'?: (string); + /** + * Free-form string that identifies the version of the entity requesting config. + * E.g. "1.12.2" or "abcd1234", or "SpecialEnvoyBuild" + */ + 'user_agent_version'?: (string); + /** + * Structured version of the entity requesting config. + */ + 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion); + /** + * List of extensions and their versions supported by the node. + */ + 'extensions'?: (_envoy_config_core_v3_Extension)[]; + /** + * Client feature support list. These are well known features described + * in the Envoy API repository for a given major version of an API. Client features + * use reverse DNS naming scheme, for example `com.acme.feature`. + * See :ref:`the list of features ` that xDS client may + * support. + */ + 'client_features'?: (string)[]; + /** + * Known listening ports on the node as a generic hint to the management server + * for filtering :ref:`listeners ` to be returned. For example, + * if there is a listener bound to port 80, the list can optionally contain the + * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. + */ + 'listening_addresses'?: (_envoy_config_core_v3_Address)[]; + 'user_agent_version_type'?: "user_agent_version"|"user_agent_build_version"; +} + +/** + * Identifies a specific Envoy instance. The node identifier is presented to the + * management server, which may use this identifier to distinguish per Envoy + * configuration for serving. + * [#next-free-field: 12] + */ +export interface Node__Output { + /** + * An opaque node identifier for the Envoy node. This also provides the local + * service node name. It should be set if any of the following features are + * used: :ref:`statsd `, :ref:`CDS + * `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-node`. + */ + 'id': (string); + /** + * Defines the local service cluster name where Envoy is running. Though + * optional, it should be set if any of the following features are used: + * :ref:`statsd `, :ref:`health check cluster + * verification + * `, + * :ref:`runtime override directory `, + * :ref:`user agent addition + * `, + * :ref:`HTTP global rate limiting `, + * :ref:`CDS `, and :ref:`HTTP tracing + * `, either in this message or via + * :option:`--service-cluster`. + */ + 'cluster': (string); + /** + * Opaque metadata extending the node identifier. Envoy will pass this + * directly to the management server. + */ + 'metadata'?: (_google_protobuf_Struct__Output); + /** + * Locality specifying where the Envoy instance is running. + */ + 'locality'?: (_envoy_config_core_v3_Locality__Output); + /** + * Free-form string that identifies the entity requesting config. + * E.g. "envoy" or "grpc" + */ + 'user_agent_name': (string); + /** + * Free-form string that identifies the version of the entity requesting config. + * E.g. "1.12.2" or "abcd1234", or "SpecialEnvoyBuild" + */ + 'user_agent_version'?: (string); + /** + * Structured version of the entity requesting config. + */ + 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion__Output); + /** + * List of extensions and their versions supported by the node. + */ + 'extensions': (_envoy_config_core_v3_Extension__Output)[]; + /** + * Client feature support list. These are well known features described + * in the Envoy API repository for a given major version of an API. Client features + * use reverse DNS naming scheme, for example `com.acme.feature`. + * See :ref:`the list of features ` that xDS client may + * support. + */ + 'client_features': (string)[]; + /** + * Known listening ports on the node as a generic hint to the management server + * for filtering :ref:`listeners ` to be returned. For example, + * if there is a listener bound to port 80, the list can optionally contain the + * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. + */ + 'listening_addresses': (_envoy_config_core_v3_Address__Output)[]; + 'user_agent_version_type': "user_agent_version"|"user_agent_build_version"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Pipe.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Pipe.ts new file mode 100644 index 000000000..3d8fdb1c8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Pipe.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + + +export interface Pipe { + /** + * Unix Domain Socket path. On Linux, paths starting with '@' will use the + * abstract namespace. The starting '@' is replaced by a null byte by Envoy. + * Paths starting with '@' will result in an error in environments other than + * Linux. + */ + 'path'?: (string); + /** + * The mode for the Pipe. Not applicable for abstract sockets. + */ + 'mode'?: (number); +} + +export interface Pipe__Output { + /** + * Unix Domain Socket path. On Linux, paths starting with '@' will use the + * abstract namespace. The starting '@' is replaced by a null byte by Envoy. + * Paths starting with '@' will result in an error in environments other than + * Linux. + */ + 'path': (string); + /** + * The mode for the Pipe. Not applicable for abstract sockets. + */ + 'mode': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts new file mode 100644 index 000000000..a28de2dbe --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts @@ -0,0 +1,29 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto + + +// Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto + +export enum _envoy_config_core_v3_ProxyProtocolConfig_Version { + /** + * PROXY protocol version 1. Human readable format. + */ + V1 = 0, + /** + * PROXY protocol version 2. Binary format. + */ + V2 = 1, +} + +export interface ProxyProtocolConfig { + /** + * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details + */ + 'version'?: (_envoy_config_core_v3_ProxyProtocolConfig_Version | keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); +} + +export interface ProxyProtocolConfig__Output { + /** + * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details + */ + 'version': (keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RateLimitSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts similarity index 94% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/RateLimitSettings.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts index 222c86eb4..8f4093fd4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RateLimitSettings.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/config_source.proto +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { DoubleValue as _google_protobuf_DoubleValue, DoubleValue__Output as _google_protobuf_DoubleValue__Output } from '../../../../google/protobuf/DoubleValue'; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts new file mode 100644 index 000000000..99634015f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts @@ -0,0 +1,40 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { HttpUri as _envoy_config_core_v3_HttpUri, HttpUri__Output as _envoy_config_core_v3_HttpUri__Output } from '../../../../envoy/config/core/v3/HttpUri'; +import type { RetryPolicy as _envoy_config_core_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_core_v3_RetryPolicy__Output } from '../../../../envoy/config/core/v3/RetryPolicy'; + +/** + * The message specifies how to fetch data from remote and how to verify it. + */ +export interface RemoteDataSource { + /** + * The HTTP URI to fetch the remote data. + */ + 'http_uri'?: (_envoy_config_core_v3_HttpUri); + /** + * SHA256 string for verifying data. + */ + 'sha256'?: (string); + /** + * Retry policy for fetching remote data. + */ + 'retry_policy'?: (_envoy_config_core_v3_RetryPolicy); +} + +/** + * The message specifies how to fetch data from remote and how to verify it. + */ +export interface RemoteDataSource__Output { + /** + * The HTTP URI to fetch the remote data. + */ + 'http_uri'?: (_envoy_config_core_v3_HttpUri__Output); + /** + * SHA256 string for verifying data. + */ + 'sha256': (string); + /** + * Retry policy for fetching remote data. + */ + 'retry_policy'?: (_envoy_config_core_v3_RetryPolicy__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts new file mode 100644 index 000000000..9be1aa6d1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * HTTP request method. + */ +export enum RequestMethod { + METHOD_UNSPECIFIED = 0, + GET = 1, + HEAD = 2, + POST = 3, + PUT = 4, + DELETE = 5, + CONNECT = 6, + OPTIONS = 7, + TRACE = 8, + PATCH = 9, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts new file mode 100644 index 000000000..6d0d9927b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts @@ -0,0 +1,38 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { BackoffStrategy as _envoy_config_core_v3_BackoffStrategy, BackoffStrategy__Output as _envoy_config_core_v3_BackoffStrategy__Output } from '../../../../envoy/config/core/v3/BackoffStrategy'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * The message specifies the retry policy of remote data source when fetching fails. + */ +export interface RetryPolicy { + /** + * Specifies parameters that control :ref:`retry backoff strategy `. + * This parameter is optional, in which case the default base interval is 1000 milliseconds. The + * default maximum interval is 10 times the base interval. + */ + 'retry_back_off'?: (_envoy_config_core_v3_BackoffStrategy); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. + */ + 'num_retries'?: (_google_protobuf_UInt32Value); +} + +/** + * The message specifies the retry policy of remote data source when fetching fails. + */ +export interface RetryPolicy__Output { + /** + * Specifies parameters that control :ref:`retry backoff strategy `. + * This parameter is optional, in which case the default base interval is 1000 milliseconds. The + * default maximum interval is 10 times the base interval. + */ + 'retry_back_off'?: (_envoy_config_core_v3_BackoffStrategy__Output); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. + */ + 'num_retries'?: (_google_protobuf_UInt32Value__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts new file mode 100644 index 000000000..917d8a3df --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts @@ -0,0 +1,15 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * Envoy supports :ref:`upstream priority routing + * ` both at the route and the virtual + * cluster level. The current priority implementation uses different connection + * pool and circuit breaking settings for each priority level. This means that + * even for HTTP/2 requests, two physical connections will be used to an + * upstream host. In the future Envoy will likely support true HTTP/2 priority + * over a single upstream connection. + */ +export enum RoutingPriority { + DEFAULT = 0, + HIGH = 1, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeDouble.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeDouble.ts new file mode 100644 index 000000000..a0f849ab5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeDouble.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Runtime derived double with a default when not specified. + */ +export interface RuntimeDouble { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (number | string); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived double with a default when not specified. + */ +export interface RuntimeDouble__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value': (number); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts new file mode 100644 index 000000000..413a4f931 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; + +/** + * Runtime derived bool with a default when not specified. + */ +export interface RuntimeFeatureFlag { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (_google_protobuf_BoolValue); + /** + * Runtime key to get value for comparison. This value is used if defined. The boolean value must + * be represented via its + * `canonical JSON encoding `_. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived bool with a default when not specified. + */ +export interface RuntimeFeatureFlag__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (_google_protobuf_BoolValue__Output); + /** + * Runtime key to get value for comparison. This value is used if defined. The boolean value must + * be represented via its + * `canonical JSON encoding `_. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts new file mode 100644 index 000000000..5610f7bdd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts @@ -0,0 +1,49 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; + +/** + * Runtime derived FractionalPercent with defaults for when the numerator or denominator is not + * specified via a runtime key. + * + * .. note:: + * + * Parsing of the runtime key's data is implemented such that it may be represented as a + * :ref:`FractionalPercent ` proto represented as JSON/YAML + * and may also be represented as an integer with the assumption that the value is an integral + * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse + * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. + */ +export interface RuntimeFractionalPercent { + /** + * Default value if the runtime value's for the numerator/denominator keys are not available. + */ + 'default_value'?: (_envoy_type_v3_FractionalPercent); + /** + * Runtime key for a YAML representation of a FractionalPercent. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived FractionalPercent with defaults for when the numerator or denominator is not + * specified via a runtime key. + * + * .. note:: + * + * Parsing of the runtime key's data is implemented such that it may be represented as a + * :ref:`FractionalPercent ` proto represented as JSON/YAML + * and may also be represented as an integer with the assumption that the value is an integral + * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse + * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. + */ +export interface RuntimeFractionalPercent__Output { + /** + * Default value if the runtime value's for the numerator/denominator keys are not available. + */ + 'default_value'?: (_envoy_type_v3_FractionalPercent__Output); + /** + * Runtime key for a YAML representation of a FractionalPercent. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts new file mode 100644 index 000000000..b317b5aa2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts @@ -0,0 +1,31 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; + +/** + * Runtime derived percentage with a default when not specified. + */ +export interface RuntimePercent { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (_envoy_type_v3_Percent); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived percentage with a default when not specified. + */ +export interface RuntimePercent__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (_envoy_type_v3_Percent__Output); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeUInt32.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeUInt32.ts new file mode 100644 index 000000000..6cc9eead6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeUInt32.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Runtime derived uint32 with a default when not specified. + */ +export interface RuntimeUInt32 { + /** + * Default value if runtime value is not available. + */ + 'default_value'?: (number); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key'?: (string); +} + +/** + * Runtime derived uint32 with a default when not specified. + */ +export interface RuntimeUInt32__Output { + /** + * Default value if runtime value is not available. + */ + 'default_value': (number); + /** + * Runtime key to get value for comparison. This value is used if defined. + */ + 'runtime_key': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts new file mode 100644 index 000000000..e387adca2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts @@ -0,0 +1,31 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; + +/** + * [#not-implemented-hide:] + * Self-referencing config source options. This is currently empty, but when + * set in :ref:`ConfigSource ` can be used to + * specify that other data can be obtained from the same server. + */ +export interface SelfConfigSource { + /** + * API version for xDS transport protocol. This describes the xDS gRPC/REST + * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + */ + 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); +} + +/** + * [#not-implemented-hide:] + * Self-referencing config source options. This is currently empty, but when + * set in :ref:`ConfigSource ` can be used to + * specify that other data can be obtained from the same server. + */ +export interface SelfConfigSource__Output { + /** + * API version for xDS transport protocol. This describes the xDS gRPC/REST + * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. + */ + 'transport_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts new file mode 100644 index 000000000..e3f342d16 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts @@ -0,0 +1,97 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + + +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +export enum _envoy_config_core_v3_SocketAddress_Protocol { + TCP = 0, + UDP = 1, +} + +/** + * [#next-free-field: 7] + */ +export interface SocketAddress { + 'protocol'?: (_envoy_config_core_v3_SocketAddress_Protocol | keyof typeof _envoy_config_core_v3_SocketAddress_Protocol); + /** + * The address for this socket. :ref:`Listeners ` will bind + * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` + * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: + * It is possible to distinguish a Listener address via the prefix/suffix matching + * in :ref:`FilterChainMatch `.] When used + * within an upstream :ref:`BindConfig `, the address + * controls the source address of outbound connections. For :ref:`clusters + * `, the cluster type determines whether the + * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS + * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized + * via :ref:`resolver_name `. + */ + 'address'?: (string); + 'port_value'?: (number); + /** + * This is only valid if :ref:`resolver_name + * ` is specified below and the + * named resolver is capable of named port resolution. + */ + 'named_port'?: (string); + /** + * The name of the custom resolver. This must have been registered with Envoy. If + * this is empty, a context dependent default applies. If the address is a concrete + * IP address, no resolution will occur. If address is a hostname this + * should be set for resolution other than DNS. Specifying a custom resolver with + * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. + */ + 'resolver_name'?: (string); + /** + * When binding to an IPv6 address above, this enables `IPv4 compatibility + * `_. Binding to ``::`` will + * allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into + * IPv6 space as ``::FFFF:``. + */ + 'ipv4_compat'?: (boolean); + 'port_specifier'?: "port_value"|"named_port"; +} + +/** + * [#next-free-field: 7] + */ +export interface SocketAddress__Output { + 'protocol': (keyof typeof _envoy_config_core_v3_SocketAddress_Protocol); + /** + * The address for this socket. :ref:`Listeners ` will bind + * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` + * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: + * It is possible to distinguish a Listener address via the prefix/suffix matching + * in :ref:`FilterChainMatch `.] When used + * within an upstream :ref:`BindConfig `, the address + * controls the source address of outbound connections. For :ref:`clusters + * `, the cluster type determines whether the + * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS + * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized + * via :ref:`resolver_name `. + */ + 'address': (string); + 'port_value'?: (number); + /** + * This is only valid if :ref:`resolver_name + * ` is specified below and the + * named resolver is capable of named port resolution. + */ + 'named_port'?: (string); + /** + * The name of the custom resolver. This must have been registered with Envoy. If + * this is empty, a context dependent default applies. If the address is a concrete + * IP address, no resolution will occur. If address is a hostname this + * should be set for resolution other than DNS. Specifying a custom resolver with + * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. + */ + 'resolver_name': (string); + /** + * When binding to an IPv6 address above, this enables `IPv4 compatibility + * `_. Binding to ``::`` will + * allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into + * IPv6 space as ``::FFFF:``. + */ + 'ipv4_compat': (boolean); + 'port_specifier': "port_value"|"named_port"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts new file mode 100644 index 000000000..56f13c339 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts @@ -0,0 +1,90 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/socket_option.proto + +import type { Long } from '@grpc/proto-loader'; + +// Original file: deps/envoy-api/envoy/config/core/v3/socket_option.proto + +export enum _envoy_config_core_v3_SocketOption_SocketState { + /** + * Socket options are applied after socket creation but before binding the socket to a port + */ + STATE_PREBIND = 0, + /** + * Socket options are applied after binding the socket to a port but before calling listen() + */ + STATE_BOUND = 1, + /** + * Socket options are applied after calling listen() + */ + STATE_LISTENING = 2, +} + +/** + * Generic socket option message. This would be used to set socket options that + * might not exist in upstream kernels or precompiled Envoy binaries. + * [#next-free-field: 7] + */ +export interface SocketOption { + /** + * An optional name to give this socket option for debugging, etc. + * Uniqueness is not required and no special meaning is assumed. + */ + 'description'?: (string); + /** + * Corresponding to the level value passed to setsockopt, such as IPPROTO_TCP + */ + 'level'?: (number | string | Long); + /** + * The numeric name as passed to setsockopt + */ + 'name'?: (number | string | Long); + /** + * Because many sockopts take an int value. + */ + 'int_value'?: (number | string | Long); + /** + * Otherwise it's a byte buffer. + */ + 'buf_value'?: (Buffer | Uint8Array | string); + /** + * The state in which the option will be applied. When used in BindConfig + * STATE_PREBIND is currently the only valid value. + */ + 'state'?: (_envoy_config_core_v3_SocketOption_SocketState | keyof typeof _envoy_config_core_v3_SocketOption_SocketState); + 'value'?: "int_value"|"buf_value"; +} + +/** + * Generic socket option message. This would be used to set socket options that + * might not exist in upstream kernels or precompiled Envoy binaries. + * [#next-free-field: 7] + */ +export interface SocketOption__Output { + /** + * An optional name to give this socket option for debugging, etc. + * Uniqueness is not required and no special meaning is assumed. + */ + 'description': (string); + /** + * Corresponding to the level value passed to setsockopt, such as IPPROTO_TCP + */ + 'level': (string); + /** + * The numeric name as passed to setsockopt + */ + 'name': (string); + /** + * Because many sockopts take an int value. + */ + 'int_value'?: (string); + /** + * Otherwise it's a byte buffer. + */ + 'buf_value'?: (Buffer); + /** + * The state in which the option will be applied. When used in BindConfig + * STATE_PREBIND is currently the only valid value. + */ + 'state': (keyof typeof _envoy_config_core_v3_SocketOption_SocketState); + 'value': "int_value"|"buf_value"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts new file mode 100644 index 000000000..41bd5e539 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts @@ -0,0 +1,197 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/substitution_format_string.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configuration to use multiple :ref:`command operators ` + * to generate a new string in either plain text or JSON format. + * [#next-free-field: 7] + */ +export interface SubstitutionFormatString { + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Deprecated in favor of :ref:`text_format_source `. To migrate text format strings, use the :ref:`inline_string ` field. + */ + 'text_format'?: (string); + /** + * Specify a format with command operators to form a JSON string. + * Its details is described in :ref:`format dictionary`. + * Values are rendered as strings, numbers, or boolean values as appropriate. + * Nested JSON objects may be produced by some command operators (e.g. FILTER_STATE or DYNAMIC_METADATA). + * See the documentation for a specific command operator for details. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * + * The following JSON object would be created: + * + * .. code-block:: json + * + * { + * "status": 500, + * "message": "My error message" + * } + */ + 'json_format'?: (_google_protobuf_Struct); + /** + * If set to true, when command operators are evaluated to null, + * + * * for ``text_format``, the output of the empty operator is changed from ``-`` to an + * empty string, so that empty values are omitted entirely. + * * for ``json_format`` the keys with null values are omitted in the output structure. + */ + 'omit_empty_values'?: (boolean); + /** + * Specify a *content_type* field. + * If this field is not set then ``text/plain`` is used for *text_format* and + * ``application/json`` is used for *json_format*. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * content_type: "text/html; charset=UTF-8" + */ + 'content_type'?: (string); + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format_source: + * inline_string: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + */ + 'text_format_source'?: (_envoy_config_core_v3_DataSource); + /** + * Specifies a collection of Formatter plugins that can be called from the access log configuration. + * See the formatters extensions documentation for details. + */ + 'formatters'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + 'format'?: "text_format"|"json_format"|"text_format_source"; +} + +/** + * Configuration to use multiple :ref:`command operators ` + * to generate a new string in either plain text or JSON format. + * [#next-free-field: 7] + */ +export interface SubstitutionFormatString__Output { + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Deprecated in favor of :ref:`text_format_source `. To migrate text format strings, use the :ref:`inline_string ` field. + */ + 'text_format'?: (string); + /** + * Specify a format with command operators to form a JSON string. + * Its details is described in :ref:`format dictionary`. + * Values are rendered as strings, numbers, or boolean values as appropriate. + * Nested JSON objects may be produced by some command operators (e.g. FILTER_STATE or DYNAMIC_METADATA). + * See the documentation for a specific command operator for details. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * + * The following JSON object would be created: + * + * .. code-block:: json + * + * { + * "status": 500, + * "message": "My error message" + * } + */ + 'json_format'?: (_google_protobuf_Struct__Output); + /** + * If set to true, when command operators are evaluated to null, + * + * * for ``text_format``, the output of the empty operator is changed from ``-`` to an + * empty string, so that empty values are omitted entirely. + * * for ``json_format`` the keys with null values are omitted in the output structure. + */ + 'omit_empty_values': (boolean); + /** + * Specify a *content_type* field. + * If this field is not set then ``text/plain`` is used for *text_format* and + * ``application/json`` is used for *json_format*. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * content_type: "text/html; charset=UTF-8" + */ + 'content_type': (string); + /** + * Specify a format with command operators to form a text string. + * Its details is described in :ref:`format string`. + * + * For example, setting ``text_format`` like below, + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format_source: + * inline_string: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * generates plain text similar to: + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + */ + 'text_format_source'?: (_envoy_config_core_v3_DataSource__Output); + /** + * Specifies a collection of Formatter plugins that can be called from the access log configuration. + * See the formatters extensions documentation for details. + */ + 'formatters': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + 'format': "text_format"|"json_format"|"text_format_source"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts new file mode 100644 index 000000000..a1bac359e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +export interface TcpKeepalive { + /** + * Maximum number of keepalive probes to send without response before deciding + * the connection is dead. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 9.) + */ + 'keepalive_probes'?: (_google_protobuf_UInt32Value); + /** + * The number of seconds a connection needs to be idle before keep-alive probes + * start being sent. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 7200s (i.e., 2 hours.) + */ + 'keepalive_time'?: (_google_protobuf_UInt32Value); + /** + * The number of seconds between keep-alive probes. Default is to use the OS + * level configuration (unless overridden, Linux defaults to 75s.) + */ + 'keepalive_interval'?: (_google_protobuf_UInt32Value); +} + +export interface TcpKeepalive__Output { + /** + * Maximum number of keepalive probes to send without response before deciding + * the connection is dead. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 9.) + */ + 'keepalive_probes'?: (_google_protobuf_UInt32Value__Output); + /** + * The number of seconds a connection needs to be idle before keep-alive probes + * start being sent. Default is to use the OS level configuration (unless + * overridden, Linux defaults to 7200s (i.e., 2 hours.) + */ + 'keepalive_time'?: (_google_protobuf_UInt32Value__Output); + /** + * The number of seconds between keep-alive probes. Default is to use the OS + * level configuration (unless overridden, Linux defaults to 75s.) + */ + 'keepalive_interval'?: (_google_protobuf_UInt32Value__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpProtocolOptions.ts similarity index 70% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpProtocolOptions.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpProtocolOptions.ts index bb7afc1d1..28239f63e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpProtocolOptions.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/protocol.proto +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts new file mode 100644 index 000000000..b68323b09 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts @@ -0,0 +1,19 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * Identifies the direction of the traffic relative to the local Envoy. + */ +export enum TrafficDirection { + /** + * Default option is unspecified. + */ + UNSPECIFIED = 0, + /** + * The transport is used for incoming traffic. + */ + INBOUND = 1, + /** + * The transport is used for outgoing traffic. + */ + OUTBOUND = 2, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts new file mode 100644 index 000000000..b14402783 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Configuration for transport socket in :ref:`listeners ` and + * :ref:`clusters `. If the configuration is + * empty, a default transport socket implementation and configuration will be + * chosen based on the platform and existence of tls_context. + */ +export interface TransportSocket { + /** + * The name of the transport socket to instantiate. The name must match a supported transport + * socket implementation. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any); + /** + * Implementation specific configuration which depends on the implementation being instantiated. + * See the supported transport socket implementations for further documentation. + */ + 'config_type'?: "typed_config"; +} + +/** + * Configuration for transport socket in :ref:`listeners ` and + * :ref:`clusters `. If the configuration is + * empty, a default transport socket implementation and configuration will be + * chosen based on the platform and existence of tls_context. + */ +export interface TransportSocket__Output { + /** + * The name of the transport socket to instantiate. The name must match a supported transport + * socket implementation. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output); + /** + * Implementation specific configuration which depends on the implementation being instantiated. + * See the supported transport socket implementations for further documentation. + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts new file mode 100644 index 000000000..788826a1b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/extension.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Message type for extension configuration. + * [#next-major-version: revisit all existing typed_config that doesn't use this wrapper.]. + */ +export interface TypedExtensionConfig { + /** + * The name of an extension. This is not used to select the extension, instead + * it serves the role of an opaque identifier. + */ + 'name'?: (string); + /** + * The typed config for the extension. The type URL will be used to identify + * the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*, + * the inner type URL of *TypedStruct* will be utilized. See the + * :ref:`extension configuration overview + * ` for further details. + */ + 'typed_config'?: (_google_protobuf_Any); +} + +/** + * Message type for extension configuration. + * [#next-major-version: revisit all existing typed_config that doesn't use this wrapper.]. + */ +export interface TypedExtensionConfig__Output { + /** + * The name of an extension. This is not used to select the extension, instead + * it serves the role of an opaque identifier. + */ + 'name': (string); + /** + * The typed config for the extension. The type URL will be used to identify + * the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*, + * the inner type URL of *TypedStruct* will be utilized. See the + * :ref:`extension configuration overview + * ` for further details. + */ + 'typed_config'?: (_google_protobuf_Any__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/UpstreamHttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts similarity index 95% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/core/UpstreamHttpProtocolOptions.ts rename to packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts index 9c55560e8..b765ec5a7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/UpstreamHttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/protocol.proto +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto export interface UpstreamHttpProtocolOptions { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/WatchedDirectory.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/WatchedDirectory.ts new file mode 100644 index 000000000..d6f0d124b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/WatchedDirectory.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * A directory that is watched for changes, e.g. by inotify on Linux. Move/rename + * events inside this directory trigger the watch. + */ +export interface WatchedDirectory { + /** + * Directory path to watch. + */ + 'path'?: (string); +} + +/** + * A directory that is watched for changes, e.g. by inotify on Linux. Move/rename + * events inside this directory trigger the watch. + */ +export interface WatchedDirectory__Output { + /** + * Directory path to watch. + */ + 'path': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/ClusterLoadAssignment.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts similarity index 67% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/ClusterLoadAssignment.ts rename to packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts index 14598bec1..a092f0dde 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/ClusterLoadAssignment.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts @@ -1,15 +1,15 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint.proto +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint.proto -import type { LocalityLbEndpoints as _envoy_api_v2_endpoint_LocalityLbEndpoints, LocalityLbEndpoints__Output as _envoy_api_v2_endpoint_LocalityLbEndpoints__Output } from '../../../envoy/api/v2/endpoint/LocalityLbEndpoints'; -import type { Endpoint as _envoy_api_v2_endpoint_Endpoint, Endpoint__Output as _envoy_api_v2_endpoint_Endpoint__Output } from '../../../envoy/api/v2/endpoint/Endpoint'; -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../google/protobuf/UInt32Value'; -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; -import type { FractionalPercent as _envoy_type_FractionalPercent, FractionalPercent__Output as _envoy_type_FractionalPercent__Output } from '../../../envoy/type/FractionalPercent'; +import type { LocalityLbEndpoints as _envoy_config_endpoint_v3_LocalityLbEndpoints, LocalityLbEndpoints__Output as _envoy_config_endpoint_v3_LocalityLbEndpoints__Output } from '../../../../envoy/config/endpoint/v3/LocalityLbEndpoints'; +import type { Endpoint as _envoy_config_endpoint_v3_Endpoint, Endpoint__Output as _envoy_config_endpoint_v3_Endpoint__Output } from '../../../../envoy/config/endpoint/v3/Endpoint'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; /** * [#not-implemented-hide:] */ -export interface _envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload { +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload { /** * Identifier for the policy specifying the drop. */ @@ -17,13 +17,13 @@ export interface _envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload { /** * Percentage of traffic that should be dropped for the category. */ - 'drop_percentage'?: (_envoy_type_FractionalPercent); + 'drop_percentage'?: (_envoy_type_v3_FractionalPercent); } /** * [#not-implemented-hide:] */ -export interface _envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload__Output { +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload__Output { /** * Identifier for the policy specifying the drop. */ @@ -31,14 +31,14 @@ export interface _envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload__Output /** * Percentage of traffic that should be dropped for the category. */ - 'drop_percentage'?: (_envoy_type_FractionalPercent__Output); + 'drop_percentage'?: (_envoy_type_v3_FractionalPercent__Output); } /** * Load balancing policy settings. * [#next-free-field: 6] */ -export interface _envoy_api_v2_ClusterLoadAssignment_Policy { +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy { /** * Action to trim the overall incoming traffic to protect the upstream * hosts. This action allows protection in case the hosts are unable to @@ -61,11 +61,11 @@ export interface _envoy_api_v2_ClusterLoadAssignment_Policy { * actual_outgoing_load = 20% // remaining after applying all categories. * [#not-implemented-hide:] */ - 'drop_overloads'?: (_envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload)[]; + 'drop_overloads'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload)[]; /** * Priority levels and localities are considered overprovisioned with this * factor (in percentage). This means that we don't consider a priority - * level or locality unhealthy until the percentage of healthy hosts + * level or locality unhealthy until the fraction of healthy hosts * multiplied by the overprovisioning factor drops below 100. * With the default value 140(1.4), Envoy doesn't consider a priority level * or a locality unhealthy until their percentage of healthy hosts drops @@ -86,24 +86,13 @@ export interface _envoy_api_v2_ClusterLoadAssignment_Policy { * Defaults to 0 which means endpoints never go stale. */ 'endpoint_stale_after'?: (_google_protobuf_Duration); - /** - * The flag to disable overprovisioning. If it is set to true, - * :ref:`overprovisioning factor - * ` will be ignored - * and Envoy will not perform graceful failover between priority levels or - * localities as endpoints become unhealthy. Otherwise Envoy will perform - * graceful failover as :ref:`overprovisioning factor - * ` suggests. - * [#not-implemented-hide:] - */ - 'disable_overprovisioning'?: (boolean); } /** * Load balancing policy settings. * [#next-free-field: 6] */ -export interface _envoy_api_v2_ClusterLoadAssignment_Policy__Output { +export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output { /** * Action to trim the overall incoming traffic to protect the upstream * hosts. This action allows protection in case the hosts are unable to @@ -126,11 +115,11 @@ export interface _envoy_api_v2_ClusterLoadAssignment_Policy__Output { * actual_outgoing_load = 20% // remaining after applying all categories. * [#not-implemented-hide:] */ - 'drop_overloads': (_envoy_api_v2_ClusterLoadAssignment_Policy_DropOverload__Output)[]; + 'drop_overloads': (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload__Output)[]; /** * Priority levels and localities are considered overprovisioned with this * factor (in percentage). This means that we don't consider a priority - * level or locality unhealthy until the percentage of healthy hosts + * level or locality unhealthy until the fraction of healthy hosts * multiplied by the overprovisioning factor drops below 100. * With the default value 140(1.4), Envoy doesn't consider a priority level * or a locality unhealthy until their percentage of healthy hosts drops @@ -151,17 +140,6 @@ export interface _envoy_api_v2_ClusterLoadAssignment_Policy__Output { * Defaults to 0 which means endpoints never go stale. */ 'endpoint_stale_after'?: (_google_protobuf_Duration__Output); - /** - * The flag to disable overprovisioning. If it is set to true, - * :ref:`overprovisioning factor - * ` will be ignored - * and Envoy will not perform graceful failover between priority levels or - * localities as endpoints become unhealthy. Otherwise Envoy will perform - * graceful failover as :ref:`overprovisioning factor - * ` suggests. - * [#not-implemented-hide:] - */ - 'disable_overprovisioning': (boolean); } /** @@ -179,24 +157,24 @@ export interface _envoy_api_v2_ClusterLoadAssignment_Policy__Output { export interface ClusterLoadAssignment { /** * Name of the cluster. This will be the :ref:`service_name - * ` value if specified + * ` value if specified * in the cluster :ref:`EdsClusterConfig - * `. + * `. */ 'cluster_name'?: (string); /** * List of endpoints to load balance to. */ - 'endpoints'?: (_envoy_api_v2_endpoint_LocalityLbEndpoints)[]; + 'endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints)[]; /** * Load balancing policy settings. */ - 'policy'?: (_envoy_api_v2_ClusterLoadAssignment_Policy); + 'policy'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy); /** * Map of named endpoints that can be referenced in LocalityLbEndpoints. * [#not-implemented-hide:] */ - 'named_endpoints'?: ({[key: string]: _envoy_api_v2_endpoint_Endpoint}); + 'named_endpoints'?: ({[key: string]: _envoy_config_endpoint_v3_Endpoint}); } /** @@ -214,22 +192,22 @@ export interface ClusterLoadAssignment { export interface ClusterLoadAssignment__Output { /** * Name of the cluster. This will be the :ref:`service_name - * ` value if specified + * ` value if specified * in the cluster :ref:`EdsClusterConfig - * `. + * `. */ 'cluster_name': (string); /** * List of endpoints to load balance to. */ - 'endpoints': (_envoy_api_v2_endpoint_LocalityLbEndpoints__Output)[]; + 'endpoints': (_envoy_config_endpoint_v3_LocalityLbEndpoints__Output)[]; /** * Load balancing policy settings. */ - 'policy'?: (_envoy_api_v2_ClusterLoadAssignment_Policy__Output); + 'policy'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output); /** * Map of named endpoints that can be referenced in LocalityLbEndpoints. * [#not-implemented-hide:] */ - 'named_endpoints'?: ({[key: string]: _envoy_api_v2_endpoint_Endpoint__Output}); + 'named_endpoints'?: ({[key: string]: _envoy_config_endpoint_v3_Endpoint__Output}); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts new file mode 100644 index 000000000..4d15bee14 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts @@ -0,0 +1,115 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { UpstreamLocalityStats as _envoy_config_endpoint_v3_UpstreamLocalityStats, UpstreamLocalityStats__Output as _envoy_config_endpoint_v3_UpstreamLocalityStats__Output } from '../../../../envoy/config/endpoint/v3/UpstreamLocalityStats'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Long } from '@grpc/proto-loader'; + +export interface _envoy_config_endpoint_v3_ClusterStats_DroppedRequests { + /** + * Identifier for the policy specifying the drop. + */ + 'category'?: (string); + /** + * Total number of deliberately dropped requests for the category. + */ + 'dropped_count'?: (number | string | Long); +} + +export interface _envoy_config_endpoint_v3_ClusterStats_DroppedRequests__Output { + /** + * Identifier for the policy specifying the drop. + */ + 'category': (string); + /** + * Total number of deliberately dropped requests for the category. + */ + 'dropped_count': (string); +} + +/** + * Per cluster load stats. Envoy reports these stats a management server in a + * :ref:`LoadStatsRequest` + * Next ID: 7 + * [#next-free-field: 7] + */ +export interface ClusterStats { + /** + * The name of the cluster. + */ + 'cluster_name'?: (string); + /** + * Need at least one. + */ + 'upstream_locality_stats'?: (_envoy_config_endpoint_v3_UpstreamLocalityStats)[]; + /** + * Cluster-level stats such as total_successful_requests may be computed by + * summing upstream_locality_stats. In addition, below there are additional + * cluster-wide stats. + * + * The total number of dropped requests. This covers requests + * deliberately dropped by the drop_overload policy and circuit breaking. + */ + 'total_dropped_requests'?: (number | string | Long); + /** + * Period over which the actual load report occurred. This will be guaranteed to include every + * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy + * and the *LoadStatsResponse* message sent from the management server, this may be longer than + * the requested load reporting interval in the *LoadStatsResponse*. + */ + 'load_report_interval'?: (_google_protobuf_Duration); + /** + * Information about deliberately dropped requests for each category specified + * in the DropOverload policy. + */ + 'dropped_requests'?: (_envoy_config_endpoint_v3_ClusterStats_DroppedRequests)[]; + /** + * The eds_cluster_config service_name of the cluster. + * It's possible that two clusters send the same service_name to EDS, + * in that case, the management server is supposed to do aggregation on the load reports. + */ + 'cluster_service_name'?: (string); +} + +/** + * Per cluster load stats. Envoy reports these stats a management server in a + * :ref:`LoadStatsRequest` + * Next ID: 7 + * [#next-free-field: 7] + */ +export interface ClusterStats__Output { + /** + * The name of the cluster. + */ + 'cluster_name': (string); + /** + * Need at least one. + */ + 'upstream_locality_stats': (_envoy_config_endpoint_v3_UpstreamLocalityStats__Output)[]; + /** + * Cluster-level stats such as total_successful_requests may be computed by + * summing upstream_locality_stats. In addition, below there are additional + * cluster-wide stats. + * + * The total number of dropped requests. This covers requests + * deliberately dropped by the drop_overload policy and circuit breaking. + */ + 'total_dropped_requests': (string); + /** + * Period over which the actual load report occurred. This will be guaranteed to include every + * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy + * and the *LoadStatsResponse* message sent from the management server, this may be longer than + * the requested load reporting interval in the *LoadStatsResponse*. + */ + 'load_report_interval'?: (_google_protobuf_Duration__Output); + /** + * Information about deliberately dropped requests for each category specified + * in the DropOverload policy. + */ + 'dropped_requests': (_envoy_config_endpoint_v3_ClusterStats_DroppedRequests__Output)[]; + /** + * The eds_cluster_config service_name of the cluster. + * It's possible that two clusters send the same service_name to EDS, + * in that case, the management server is supposed to do aggregation on the load reports. + */ + 'cluster_service_name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/Endpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts similarity index 69% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/Endpoint.ts rename to packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts index 68bef75e1..7e1a7b346 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/Endpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts @@ -1,11 +1,11 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto -import type { Address as _envoy_api_v2_core_Address, Address__Output as _envoy_api_v2_core_Address__Output } from '../../../../envoy/api/v2/core/Address'; +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; /** * The optional health check configuration. */ -export interface _envoy_api_v2_endpoint_Endpoint_HealthCheckConfig { +export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig { /** * Optional alternative health check port value. * @@ -17,8 +17,8 @@ export interface _envoy_api_v2_endpoint_Endpoint_HealthCheckConfig { 'port_value'?: (number); /** * By default, the host header for L7 health checks is controlled by cluster level configuration - * (see: :ref:`host ` and - * :ref:`authority `). Setting this + * (see: :ref:`host ` and + * :ref:`authority `). Setting this * to a non-empty value allows overriding the cluster level configuration for a specific * endpoint. */ @@ -28,7 +28,7 @@ export interface _envoy_api_v2_endpoint_Endpoint_HealthCheckConfig { /** * The optional health check configuration. */ -export interface _envoy_api_v2_endpoint_Endpoint_HealthCheckConfig__Output { +export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output { /** * Optional alternative health check port value. * @@ -40,8 +40,8 @@ export interface _envoy_api_v2_endpoint_Endpoint_HealthCheckConfig__Output { 'port_value': (number); /** * By default, the host header for L7 health checks is controlled by cluster level configuration - * (see: :ref:`host ` and - * :ref:`authority `). Setting this + * (see: :ref:`host ` and + * :ref:`authority `). Setting this * to a non-empty value allows overriding the cluster level configuration for a specific * endpoint. */ @@ -59,11 +59,11 @@ export interface Endpoint { * * The form of host address depends on the given cluster type. For STATIC or EDS, * it is expected to be a direct IP address (or something resolvable by the - * specified :ref:`resolver ` + * specified :ref:`resolver ` * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, * and will be resolved via DNS. */ - 'address'?: (_envoy_api_v2_core_Address); + 'address'?: (_envoy_config_core_v3_Address); /** * The optional health check configuration is used as configuration for the * health checker to contact the health checked host. @@ -73,12 +73,12 @@ export interface Endpoint { * This takes into effect only for upstream clusters with * :ref:`active health checking ` enabled. */ - 'health_check_config'?: (_envoy_api_v2_endpoint_Endpoint_HealthCheckConfig); + 'health_check_config'?: (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig); /** * The hostname associated with this endpoint. This hostname is not used for routing or address * resolution. If provided, it will be associated with the endpoint, and can be used for features * that require a hostname, like - * :ref:`auto_host_rewrite `. + * :ref:`auto_host_rewrite `. */ 'hostname'?: (string); } @@ -94,11 +94,11 @@ export interface Endpoint__Output { * * The form of host address depends on the given cluster type. For STATIC or EDS, * it is expected to be a direct IP address (or something resolvable by the - * specified :ref:`resolver ` + * specified :ref:`resolver ` * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, * and will be resolved via DNS. */ - 'address'?: (_envoy_api_v2_core_Address__Output); + 'address'?: (_envoy_config_core_v3_Address__Output); /** * The optional health check configuration is used as configuration for the * health checker to contact the health checked host. @@ -108,12 +108,12 @@ export interface Endpoint__Output { * This takes into effect only for upstream clusters with * :ref:`active health checking ` enabled. */ - 'health_check_config'?: (_envoy_api_v2_endpoint_Endpoint_HealthCheckConfig__Output); + 'health_check_config'?: (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output); /** * The hostname associated with this endpoint. This hostname is not used for routing or address * resolution. If provided, it will be associated with the endpoint, and can be used for features * that require a hostname, like - * :ref:`auto_host_rewrite `. + * :ref:`auto_host_rewrite `. */ 'hostname': (string); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/EndpointLoadMetricStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/EndpointLoadMetricStats.ts new file mode 100644 index 000000000..50f63f4ca --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/EndpointLoadMetricStats.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface EndpointLoadMetricStats { + /** + * Name of the metric; may be empty. + */ + 'metric_name'?: (string); + /** + * Number of calls that finished and included this metric. + */ + 'num_requests_finished_with_metric'?: (number | string | Long); + /** + * Sum of metric values across all calls that finished with this metric for + * load_reporting_interval. + */ + 'total_metric_value'?: (number | string); +} + +export interface EndpointLoadMetricStats__Output { + /** + * Name of the metric; may be empty. + */ + 'metric_name': (string); + /** + * Number of calls that finished and included this metric. + */ + 'num_requests_finished_with_metric': (string); + /** + * Sum of metric values across all calls that finished with this metric for + * load_reporting_interval. + */ + 'total_metric_value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/LbEndpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts similarity index 74% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/LbEndpoint.ts rename to packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts index 1f8be9305..449dd8aa3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/LbEndpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts @@ -1,8 +1,8 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto -import type { Endpoint as _envoy_api_v2_endpoint_Endpoint, Endpoint__Output as _envoy_api_v2_endpoint_Endpoint__Output } from '../../../../envoy/api/v2/endpoint/Endpoint'; -import type { HealthStatus as _envoy_api_v2_core_HealthStatus } from '../../../../envoy/api/v2/core/HealthStatus'; -import type { Metadata as _envoy_api_v2_core_Metadata, Metadata__Output as _envoy_api_v2_core_Metadata__Output } from '../../../../envoy/api/v2/core/Metadata'; +import type { Endpoint as _envoy_config_endpoint_v3_Endpoint, Endpoint__Output as _envoy_config_endpoint_v3_Endpoint__Output } from '../../../../envoy/config/endpoint/v3/Endpoint'; +import type { HealthStatus as _envoy_config_core_v3_HealthStatus } from '../../../../envoy/config/core/v3/HealthStatus'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; /** @@ -10,21 +10,21 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a * [#next-free-field: 6] */ export interface LbEndpoint { - 'endpoint'?: (_envoy_api_v2_endpoint_Endpoint); + 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint); /** * Optional health status when known and supplied by EDS server. */ - 'health_status'?: (_envoy_api_v2_core_HealthStatus | keyof typeof _envoy_api_v2_core_HealthStatus); + 'health_status'?: (_envoy_config_core_v3_HealthStatus | keyof typeof _envoy_config_core_v3_HealthStatus); /** * The endpoint metadata specifies values that may be used by the load * balancer to select endpoints in a cluster for a given request. The filter * name should be specified as *envoy.lb*. An example boolean key-value pair * is *canary*, providing the optional canary status of the upstream host. * This may be matched against in a route's - * :ref:`RouteAction ` metadata_match field + * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. */ - 'metadata'?: (_envoy_api_v2_core_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata); /** * The optional load balancing weight of the upstream host; at least 1. * Envoy uses the load balancing weight in some of the built in load @@ -52,21 +52,21 @@ export interface LbEndpoint { * [#next-free-field: 6] */ export interface LbEndpoint__Output { - 'endpoint'?: (_envoy_api_v2_endpoint_Endpoint__Output); + 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint__Output); /** * Optional health status when known and supplied by EDS server. */ - 'health_status': (keyof typeof _envoy_api_v2_core_HealthStatus); + 'health_status': (keyof typeof _envoy_config_core_v3_HealthStatus); /** * The endpoint metadata specifies values that may be used by the load * balancer to select endpoints in a cluster for a given request. The filter * name should be specified as *envoy.lb*. An example boolean key-value pair * is *canary*, providing the optional canary status of the upstream host. * This may be matched against in a route's - * :ref:`RouteAction ` metadata_match field + * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. */ - 'metadata'?: (_envoy_api_v2_core_Metadata__Output); + 'metadata'?: (_envoy_config_core_v3_Metadata__Output); /** * The optional load balancing weight of the upstream host; at least 1. * Envoy uses the load balancing weight in some of the built in load diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/LocalityLbEndpoints.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts similarity index 87% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/LocalityLbEndpoints.ts rename to packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts index 557d97072..9924d2466 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/LocalityLbEndpoints.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts @@ -1,7 +1,7 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto -import type { Locality as _envoy_api_v2_core_Locality, Locality__Output as _envoy_api_v2_core_Locality__Output } from '../../../../envoy/api/v2/core/Locality'; -import type { LbEndpoint as _envoy_api_v2_endpoint_LbEndpoint, LbEndpoint__Output as _envoy_api_v2_endpoint_LbEndpoint__Output } from '../../../../envoy/api/v2/endpoint/LbEndpoint'; +import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _envoy_config_core_v3_Locality__Output } from '../../../../envoy/config/core/v3/Locality'; +import type { LbEndpoint as _envoy_config_endpoint_v3_LbEndpoint, LbEndpoint__Output as _envoy_config_endpoint_v3_LbEndpoint__Output } from '../../../../envoy/config/endpoint/v3/LbEndpoint'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; /** @@ -15,11 +15,11 @@ export interface LocalityLbEndpoints { /** * Identifies location of where the upstream hosts run. */ - 'locality'?: (_envoy_api_v2_core_Locality); + 'locality'?: (_envoy_config_core_v3_Locality); /** * The group of endpoints belonging to the locality specified. */ - 'lb_endpoints'?: (_envoy_api_v2_endpoint_LbEndpoint)[]; + 'lb_endpoints'?: (_envoy_config_endpoint_v3_LbEndpoint)[]; /** * Optional: Per priority/region/zone/sub_zone weight; at least 1. The load * balancing weight for a locality is divided by the sum of the weights of all @@ -68,11 +68,11 @@ export interface LocalityLbEndpoints__Output { /** * Identifies location of where the upstream hosts run. */ - 'locality'?: (_envoy_api_v2_core_Locality__Output); + 'locality'?: (_envoy_config_core_v3_Locality__Output); /** * The group of endpoints belonging to the locality specified. */ - 'lb_endpoints': (_envoy_api_v2_endpoint_LbEndpoint__Output)[]; + 'lb_endpoints': (_envoy_config_endpoint_v3_LbEndpoint__Output)[]; /** * Optional: Per priority/region/zone/sub_zone weight; at least 1. The load * balancing weight for a locality is divided by the sum of the weights of all diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts new file mode 100644 index 000000000..8fe66d6c4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts @@ -0,0 +1,104 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { EndpointLoadMetricStats as _envoy_config_endpoint_v3_EndpointLoadMetricStats, EndpointLoadMetricStats__Output as _envoy_config_endpoint_v3_EndpointLoadMetricStats__Output } from '../../../../envoy/config/endpoint/v3/EndpointLoadMetricStats'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Long } from '@grpc/proto-loader'; + +/** + * [#next-free-field: 8] + */ +export interface UpstreamEndpointStats { + /** + * Upstream host address. + */ + 'address'?: (_envoy_config_core_v3_Address); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. These include non-5xx responses for HTTP, where errors + * originate at the client and the endpoint responded successfully. For gRPC, + * the grpc-status values are those not covered by total_error_requests below. + */ + 'total_successful_requests'?: (number | string | Long); + /** + * The total number of unfinished requests for this endpoint. + */ + 'total_requests_in_progress'?: (number | string | Long); + /** + * The total number of requests that failed due to errors at the endpoint. + * For HTTP these are responses with 5xx status codes and for gRPC the + * grpc-status values: + * + * - DeadlineExceeded + * - Unimplemented + * - Internal + * - Unavailable + * - Unknown + * - DataLoss + */ + 'total_error_requests'?: (number | string | Long); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats'?: (_envoy_config_endpoint_v3_EndpointLoadMetricStats)[]; + /** + * Opaque and implementation dependent metadata of the + * endpoint. Envoy will pass this directly to the management server. + */ + 'metadata'?: (_google_protobuf_Struct); + /** + * The total number of requests that were issued to this endpoint + * since the last report. A single TCP connection, HTTP or gRPC + * request or stream is counted as one request. + */ + 'total_issued_requests'?: (number | string | Long); +} + +/** + * [#next-free-field: 8] + */ +export interface UpstreamEndpointStats__Output { + /** + * Upstream host address. + */ + 'address'?: (_envoy_config_core_v3_Address__Output); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. These include non-5xx responses for HTTP, where errors + * originate at the client and the endpoint responded successfully. For gRPC, + * the grpc-status values are those not covered by total_error_requests below. + */ + 'total_successful_requests': (string); + /** + * The total number of unfinished requests for this endpoint. + */ + 'total_requests_in_progress': (string); + /** + * The total number of requests that failed due to errors at the endpoint. + * For HTTP these are responses with 5xx status codes and for gRPC the + * grpc-status values: + * + * - DeadlineExceeded + * - Unimplemented + * - Internal + * - Unavailable + * - Unknown + * - DataLoss + */ + 'total_error_requests': (string); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats': (_envoy_config_endpoint_v3_EndpointLoadMetricStats__Output)[]; + /** + * Opaque and implementation dependent metadata of the + * endpoint. Envoy will pass this directly to the management server. + */ + 'metadata'?: (_google_protobuf_Struct__Output); + /** + * The total number of requests that were issued to this endpoint + * since the last report. A single TCP connection, HTTP or gRPC + * request or stream is counted as one request. + */ + 'total_issued_requests': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts new file mode 100644 index 000000000..3eb8cc4eb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts @@ -0,0 +1,104 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/load_report.proto + +import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _envoy_config_core_v3_Locality__Output } from '../../../../envoy/config/core/v3/Locality'; +import type { EndpointLoadMetricStats as _envoy_config_endpoint_v3_EndpointLoadMetricStats, EndpointLoadMetricStats__Output as _envoy_config_endpoint_v3_EndpointLoadMetricStats__Output } from '../../../../envoy/config/endpoint/v3/EndpointLoadMetricStats'; +import type { UpstreamEndpointStats as _envoy_config_endpoint_v3_UpstreamEndpointStats, UpstreamEndpointStats__Output as _envoy_config_endpoint_v3_UpstreamEndpointStats__Output } from '../../../../envoy/config/endpoint/v3/UpstreamEndpointStats'; +import type { Long } from '@grpc/proto-loader'; + +/** + * These are stats Envoy reports to the management server at a frequency defined by + * :ref:`LoadStatsResponse.load_reporting_interval`. + * Stats per upstream region/zone and optionally per subzone. + * [#next-free-field: 9] + */ +export interface UpstreamLocalityStats { + /** + * Name of zone, region and optionally endpoint group these metrics were + * collected from. Zone and region names could be empty if unknown. + */ + 'locality'?: (_envoy_config_core_v3_Locality); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. + */ + 'total_successful_requests'?: (number | string | Long); + /** + * The total number of unfinished requests + */ + 'total_requests_in_progress'?: (number | string | Long); + /** + * The total number of requests that failed due to errors at the endpoint, + * aggregated over all endpoints in the locality. + */ + 'total_error_requests'?: (number | string | Long); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats'?: (_envoy_config_endpoint_v3_EndpointLoadMetricStats)[]; + /** + * [#not-implemented-hide:] The priority of the endpoint group these metrics + * were collected from. + */ + 'priority'?: (number); + /** + * Endpoint granularity stats information for this locality. This information + * is populated if the Server requests it by setting + * :ref:`LoadStatsResponse.report_endpoint_granularity`. + */ + 'upstream_endpoint_stats'?: (_envoy_config_endpoint_v3_UpstreamEndpointStats)[]; + /** + * The total number of requests that were issued by this Envoy since + * the last report. This information is aggregated over all the + * upstream endpoints in the locality. + */ + 'total_issued_requests'?: (number | string | Long); +} + +/** + * These are stats Envoy reports to the management server at a frequency defined by + * :ref:`LoadStatsResponse.load_reporting_interval`. + * Stats per upstream region/zone and optionally per subzone. + * [#next-free-field: 9] + */ +export interface UpstreamLocalityStats__Output { + /** + * Name of zone, region and optionally endpoint group these metrics were + * collected from. Zone and region names could be empty if unknown. + */ + 'locality'?: (_envoy_config_core_v3_Locality__Output); + /** + * The total number of requests successfully completed by the endpoints in the + * locality. + */ + 'total_successful_requests': (string); + /** + * The total number of unfinished requests + */ + 'total_requests_in_progress': (string); + /** + * The total number of requests that failed due to errors at the endpoint, + * aggregated over all endpoints in the locality. + */ + 'total_error_requests': (string); + /** + * Stats for multi-dimensional load balancing. + */ + 'load_metric_stats': (_envoy_config_endpoint_v3_EndpointLoadMetricStats__Output)[]; + /** + * [#not-implemented-hide:] The priority of the endpoint group these metrics + * were collected from. + */ + 'priority': (number); + /** + * Endpoint granularity stats information for this locality. This information + * is populated if the Server requests it by setting + * :ref:`LoadStatsResponse.report_endpoint_granularity`. + */ + 'upstream_endpoint_stats': (_envoy_config_endpoint_v3_UpstreamEndpointStats__Output)[]; + /** + * The total number of requests that were issued by this Envoy since + * the last report. This information is aggregated over all the + * upstream endpoints in the locality. + */ + 'total_issued_requests': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AccessLogFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AccessLogFilter.ts deleted file mode 100644 index d75c9676c..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/AccessLogFilter.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto - -import type { StatusCodeFilter as _envoy_config_filter_accesslog_v2_StatusCodeFilter, StatusCodeFilter__Output as _envoy_config_filter_accesslog_v2_StatusCodeFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/StatusCodeFilter'; -import type { DurationFilter as _envoy_config_filter_accesslog_v2_DurationFilter, DurationFilter__Output as _envoy_config_filter_accesslog_v2_DurationFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/DurationFilter'; -import type { NotHealthCheckFilter as _envoy_config_filter_accesslog_v2_NotHealthCheckFilter, NotHealthCheckFilter__Output as _envoy_config_filter_accesslog_v2_NotHealthCheckFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/NotHealthCheckFilter'; -import type { TraceableFilter as _envoy_config_filter_accesslog_v2_TraceableFilter, TraceableFilter__Output as _envoy_config_filter_accesslog_v2_TraceableFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/TraceableFilter'; -import type { RuntimeFilter as _envoy_config_filter_accesslog_v2_RuntimeFilter, RuntimeFilter__Output as _envoy_config_filter_accesslog_v2_RuntimeFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/RuntimeFilter'; -import type { AndFilter as _envoy_config_filter_accesslog_v2_AndFilter, AndFilter__Output as _envoy_config_filter_accesslog_v2_AndFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/AndFilter'; -import type { OrFilter as _envoy_config_filter_accesslog_v2_OrFilter, OrFilter__Output as _envoy_config_filter_accesslog_v2_OrFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/OrFilter'; -import type { HeaderFilter as _envoy_config_filter_accesslog_v2_HeaderFilter, HeaderFilter__Output as _envoy_config_filter_accesslog_v2_HeaderFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/HeaderFilter'; -import type { ResponseFlagFilter as _envoy_config_filter_accesslog_v2_ResponseFlagFilter, ResponseFlagFilter__Output as _envoy_config_filter_accesslog_v2_ResponseFlagFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/ResponseFlagFilter'; -import type { GrpcStatusFilter as _envoy_config_filter_accesslog_v2_GrpcStatusFilter, GrpcStatusFilter__Output as _envoy_config_filter_accesslog_v2_GrpcStatusFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/GrpcStatusFilter'; -import type { ExtensionFilter as _envoy_config_filter_accesslog_v2_ExtensionFilter, ExtensionFilter__Output as _envoy_config_filter_accesslog_v2_ExtensionFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/ExtensionFilter'; - -/** - * [#next-free-field: 12] - */ -export interface AccessLogFilter { - /** - * Status code filter. - */ - 'status_code_filter'?: (_envoy_config_filter_accesslog_v2_StatusCodeFilter); - /** - * Duration filter. - */ - 'duration_filter'?: (_envoy_config_filter_accesslog_v2_DurationFilter); - /** - * Not health check filter. - */ - 'not_health_check_filter'?: (_envoy_config_filter_accesslog_v2_NotHealthCheckFilter); - /** - * Traceable filter. - */ - 'traceable_filter'?: (_envoy_config_filter_accesslog_v2_TraceableFilter); - /** - * Runtime filter. - */ - 'runtime_filter'?: (_envoy_config_filter_accesslog_v2_RuntimeFilter); - /** - * And filter. - */ - 'and_filter'?: (_envoy_config_filter_accesslog_v2_AndFilter); - /** - * Or filter. - */ - 'or_filter'?: (_envoy_config_filter_accesslog_v2_OrFilter); - /** - * Header filter. - */ - 'header_filter'?: (_envoy_config_filter_accesslog_v2_HeaderFilter); - /** - * Response flag filter. - */ - 'response_flag_filter'?: (_envoy_config_filter_accesslog_v2_ResponseFlagFilter); - /** - * gRPC status filter. - */ - 'grpc_status_filter'?: (_envoy_config_filter_accesslog_v2_GrpcStatusFilter); - /** - * Extension filter. - */ - 'extension_filter'?: (_envoy_config_filter_accesslog_v2_ExtensionFilter); - 'filter_specifier'?: "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"; -} - -/** - * [#next-free-field: 12] - */ -export interface AccessLogFilter__Output { - /** - * Status code filter. - */ - 'status_code_filter'?: (_envoy_config_filter_accesslog_v2_StatusCodeFilter__Output); - /** - * Duration filter. - */ - 'duration_filter'?: (_envoy_config_filter_accesslog_v2_DurationFilter__Output); - /** - * Not health check filter. - */ - 'not_health_check_filter'?: (_envoy_config_filter_accesslog_v2_NotHealthCheckFilter__Output); - /** - * Traceable filter. - */ - 'traceable_filter'?: (_envoy_config_filter_accesslog_v2_TraceableFilter__Output); - /** - * Runtime filter. - */ - 'runtime_filter'?: (_envoy_config_filter_accesslog_v2_RuntimeFilter__Output); - /** - * And filter. - */ - 'and_filter'?: (_envoy_config_filter_accesslog_v2_AndFilter__Output); - /** - * Or filter. - */ - 'or_filter'?: (_envoy_config_filter_accesslog_v2_OrFilter__Output); - /** - * Header filter. - */ - 'header_filter'?: (_envoy_config_filter_accesslog_v2_HeaderFilter__Output); - /** - * Response flag filter. - */ - 'response_flag_filter'?: (_envoy_config_filter_accesslog_v2_ResponseFlagFilter__Output); - /** - * gRPC status filter. - */ - 'grpc_status_filter'?: (_envoy_config_filter_accesslog_v2_GrpcStatusFilter__Output); - /** - * Extension filter. - */ - 'extension_filter'?: (_envoy_config_filter_accesslog_v2_ExtensionFilter__Output); - 'filter_specifier': "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ComparisonFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ComparisonFilter.ts deleted file mode 100644 index 8989ba603..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/ComparisonFilter.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto - -import type { RuntimeUInt32 as _envoy_api_v2_core_RuntimeUInt32, RuntimeUInt32__Output as _envoy_api_v2_core_RuntimeUInt32__Output } from '../../../../../envoy/api/v2/core/RuntimeUInt32'; - -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto - -export enum _envoy_config_filter_accesslog_v2_ComparisonFilter_Op { - /** - * = - */ - EQ = 0, - /** - * >= - */ - GE = 1, - /** - * <= - */ - LE = 2, -} - -/** - * Filter on an integer comparison. - */ -export interface ComparisonFilter { - /** - * Comparison operator. - */ - 'op'?: (_envoy_config_filter_accesslog_v2_ComparisonFilter_Op | keyof typeof _envoy_config_filter_accesslog_v2_ComparisonFilter_Op); - /** - * Value to compare against. - */ - 'value'?: (_envoy_api_v2_core_RuntimeUInt32); -} - -/** - * Filter on an integer comparison. - */ -export interface ComparisonFilter__Output { - /** - * Comparison operator. - */ - 'op': (keyof typeof _envoy_config_filter_accesslog_v2_ComparisonFilter_Op); - /** - * Value to compare against. - */ - 'value'?: (_envoy_api_v2_core_RuntimeUInt32__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/DurationFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/DurationFilter.ts deleted file mode 100644 index 52a37cd95..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/DurationFilter.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto - -import type { ComparisonFilter as _envoy_config_filter_accesslog_v2_ComparisonFilter, ComparisonFilter__Output as _envoy_config_filter_accesslog_v2_ComparisonFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/ComparisonFilter'; - -/** - * Filters on total request duration in milliseconds. - */ -export interface DurationFilter { - /** - * Comparison. - */ - 'comparison'?: (_envoy_config_filter_accesslog_v2_ComparisonFilter); -} - -/** - * Filters on total request duration in milliseconds. - */ -export interface DurationFilter__Output { - /** - * Comparison. - */ - 'comparison'?: (_envoy_config_filter_accesslog_v2_ComparisonFilter__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/HeaderFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/HeaderFilter.ts deleted file mode 100644 index d40610617..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/HeaderFilter.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto - -import type { HeaderMatcher as _envoy_api_v2_route_HeaderMatcher, HeaderMatcher__Output as _envoy_api_v2_route_HeaderMatcher__Output } from '../../../../../envoy/api/v2/route/HeaderMatcher'; - -/** - * Filters requests based on the presence or value of a request header. - */ -export interface HeaderFilter { - /** - * Only requests with a header which matches the specified HeaderMatcher will pass the filter - * check. - */ - 'header'?: (_envoy_api_v2_route_HeaderMatcher); -} - -/** - * Filters requests based on the presence or value of a request header. - */ -export interface HeaderFilter__Output { - /** - * Only requests with a header which matches the specified HeaderMatcher will pass the filter - * check. - */ - 'header'?: (_envoy_api_v2_route_HeaderMatcher__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/RuntimeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/RuntimeFilter.ts deleted file mode 100644 index 100ce050b..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/RuntimeFilter.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto - -import type { FractionalPercent as _envoy_type_FractionalPercent, FractionalPercent__Output as _envoy_type_FractionalPercent__Output } from '../../../../../envoy/type/FractionalPercent'; - -/** - * Filters for random sampling of requests. - */ -export interface RuntimeFilter { - /** - * Runtime key to get an optional overridden numerator for use in the *percent_sampled* field. - * If found in runtime, this value will replace the default numerator. - */ - 'runtime_key'?: (string); - /** - * The default sampling percentage. If not specified, defaults to 0% with denominator of 100. - */ - 'percent_sampled'?: (_envoy_type_FractionalPercent); - /** - * By default, sampling pivots on the header - * :ref:`x-request-id` being present. If - * :ref:`x-request-id` is present, the filter will - * consistently sample across multiple hosts based on the runtime key value and the value - * extracted from :ref:`x-request-id`. If it is - * missing, or *use_independent_randomness* is set to true, the filter will randomly sample based - * on the runtime key value alone. *use_independent_randomness* can be used for logging kill - * switches within complex nested :ref:`AndFilter - * ` and :ref:`OrFilter - * ` blocks that are easier to reason about - * from a probability perspective (i.e., setting to true will cause the filter to behave like - * an independent random variable when composed within logical operator filters). - */ - 'use_independent_randomness'?: (boolean); -} - -/** - * Filters for random sampling of requests. - */ -export interface RuntimeFilter__Output { - /** - * Runtime key to get an optional overridden numerator for use in the *percent_sampled* field. - * If found in runtime, this value will replace the default numerator. - */ - 'runtime_key': (string); - /** - * The default sampling percentage. If not specified, defaults to 0% with denominator of 100. - */ - 'percent_sampled'?: (_envoy_type_FractionalPercent__Output); - /** - * By default, sampling pivots on the header - * :ref:`x-request-id` being present. If - * :ref:`x-request-id` is present, the filter will - * consistently sample across multiple hosts based on the runtime key value and the value - * extracted from :ref:`x-request-id`. If it is - * missing, or *use_independent_randomness* is set to true, the filter will randomly sample based - * on the runtime key value alone. *use_independent_randomness* can be used for logging kill - * switches within complex nested :ref:`AndFilter - * ` and :ref:`OrFilter - * ` blocks that are easier to reason about - * from a probability perspective (i.e., setting to true will cause the filter to behave like - * an independent random variable when composed within logical operator filters). - */ - 'use_independent_randomness': (boolean); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/StatusCodeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/StatusCodeFilter.ts deleted file mode 100644 index d60a80a14..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/accesslog/v2/StatusCodeFilter.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto - -import type { ComparisonFilter as _envoy_config_filter_accesslog_v2_ComparisonFilter, ComparisonFilter__Output as _envoy_config_filter_accesslog_v2_ComparisonFilter__Output } from '../../../../../envoy/config/filter/accesslog/v2/ComparisonFilter'; - -/** - * Filters on HTTP response/status code. - */ -export interface StatusCodeFilter { - /** - * Comparison. - */ - 'comparison'?: (_envoy_config_filter_accesslog_v2_ComparisonFilter); -} - -/** - * Filters on HTTP response/status code. - */ -export interface StatusCodeFilter__Output { - /** - * Comparison. - */ - 'comparison'?: (_envoy_config_filter_accesslog_v2_ComparisonFilter__Output); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/HttpFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/HttpFilter.ts deleted file mode 100644 index 84e4292fc..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/HttpFilter.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../../../google/protobuf/Struct'; -import type { 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 `. - */ - '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 `. - */ - '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"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRds.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRds.ts deleted file mode 100644 index b3d89dffb..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRds.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto - -import type { 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); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRouteConfigurationsList.ts b/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRouteConfigurationsList.ts deleted file mode 100644 index a57f1725e..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRouteConfigurationsList.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto - -import type { 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)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ActiveRawUdpListenerConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ActiveRawUdpListenerConfig.ts similarity index 56% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ActiveRawUdpListenerConfig.ts rename to packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ActiveRawUdpListenerConfig.ts index 7bb47c26c..3cc895aa9 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ActiveRawUdpListenerConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ActiveRawUdpListenerConfig.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener/udp_listener_config.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/udp_listener_config.proto export interface ActiveRawUdpListenerConfig { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v2/ApiListener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts similarity index 96% rename from packages/grpc-js-xds/src/generated/envoy/config/listener/v2/ApiListener.ts rename to packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts index f683d3e82..508236641 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v2/ApiListener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/config/listener/v2/api_listener.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/api_listener.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts new file mode 100644 index 000000000..a3d26c904 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts @@ -0,0 +1,52 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { ExtensionConfigSource as _envoy_config_core_v3_ExtensionConfigSource, ExtensionConfigSource__Output as _envoy_config_core_v3_ExtensionConfigSource__Output } from '../../../../envoy/config/core/v3/ExtensionConfigSource'; + +/** + * [#next-free-field: 6] + */ +export interface Filter { + /** + * The name of the filter to instantiate. The name must match a + * :ref:`supported filter `. + */ + 'name'?: (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + */ + 'typed_config'?: (_google_protobuf_Any); + /** + * Configuration source specifier for an extension configuration discovery + * service. In case of a failure and without the default configuration, the + * listener closes the connections. + * [#not-implemented-hide:] + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource); + 'config_type'?: "typed_config"|"config_discovery"; +} + +/** + * [#next-free-field: 6] + */ +export interface Filter__Output { + /** + * The name of the filter to instantiate. The name must match a + * :ref:`supported filter `. + */ + 'name': (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + */ + 'typed_config'?: (_google_protobuf_Any__Output); + /** + * Configuration source specifier for an extension configuration discovery + * service. In case of a failure and without the default configuration, the + * listener closes the connections. + * [#not-implemented-hide:] + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output); + 'config_type': "typed_config"|"config_discovery"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts new file mode 100644 index 000000000..e7f0adb7c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts @@ -0,0 +1,170 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto + +import type { FilterChainMatch as _envoy_config_listener_v3_FilterChainMatch, FilterChainMatch__Output as _envoy_config_listener_v3_FilterChainMatch__Output } from '../../../../envoy/config/listener/v3/FilterChainMatch'; +import type { Filter as _envoy_config_listener_v3_Filter, Filter__Output as _envoy_config_listener_v3_Filter__Output } from '../../../../envoy/config/listener/v3/Filter'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { TransportSocket as _envoy_config_core_v3_TransportSocket, TransportSocket__Output as _envoy_config_core_v3_TransportSocket__Output } from '../../../../envoy/config/core/v3/TransportSocket'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * The configuration for on-demand filter chain. If this field is not empty in FilterChain message, + * a filter chain will be built on-demand. + * On-demand filter chains help speedup the warming up of listeners since the building and initialization of + * an on-demand filter chain will be postponed to the arrival of new connection requests that require this filter chain. + * Filter chains that are not often used can be set as on-demand. + */ +export interface _envoy_config_listener_v3_FilterChain_OnDemandConfiguration { + /** + * The timeout to wait for filter chain placeholders to complete rebuilding. + * 1. If this field is set to 0, timeout is disabled. + * 2. If not specified, a default timeout of 15s is used. + * Rebuilding will wait until dependencies are ready, have failed, or this timeout is reached. + * Upon failure or timeout, all connections related to this filter chain will be closed. + * Rebuilding will start again on the next new connection. + */ + 'rebuild_timeout'?: (_google_protobuf_Duration); +} + +/** + * The configuration for on-demand filter chain. If this field is not empty in FilterChain message, + * a filter chain will be built on-demand. + * On-demand filter chains help speedup the warming up of listeners since the building and initialization of + * an on-demand filter chain will be postponed to the arrival of new connection requests that require this filter chain. + * Filter chains that are not often used can be set as on-demand. + */ +export interface _envoy_config_listener_v3_FilterChain_OnDemandConfiguration__Output { + /** + * The timeout to wait for filter chain placeholders to complete rebuilding. + * 1. If this field is set to 0, timeout is disabled. + * 2. If not specified, a default timeout of 15s is used. + * Rebuilding will wait until dependencies are ready, have failed, or this timeout is reached. + * Upon failure or timeout, all connections related to this filter chain will be closed. + * Rebuilding will start again on the next new connection. + */ + 'rebuild_timeout'?: (_google_protobuf_Duration__Output); +} + +/** + * A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and + * various other parameters. + * [#next-free-field: 10] + */ +export interface FilterChain { + /** + * The criteria to use when matching a connection to this filter chain. + */ + 'filter_chain_match'?: (_envoy_config_listener_v3_FilterChainMatch); + /** + * A list of individual network filters that make up the filter chain for + * connections established with the listener. Order matters as the filters are + * processed sequentially as connection events happen. Note: If the filter + * list is empty, the connection will close by default. + */ + 'filters'?: (_envoy_config_listener_v3_Filter)[]; + /** + * Whether the listener should expect a PROXY protocol V1 header on new + * connections. If this option is enabled, the listener will assume that that + * remote address of the connection is the one specified in the header. Some + * load balancers including the AWS ELB support this option. If the option is + * absent or set to false, Envoy will use the physical peer address of the + * connection as the remote address. + * + * This field is deprecated. Add a + * :ref:`PROXY protocol listener filter ` + * explicitly instead. + */ + 'use_proxy_proto'?: (_google_protobuf_BoolValue); + /** + * [#not-implemented-hide:] filter chain metadata. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata); + /** + * Optional custom transport socket implementation to use for downstream connections. + * To setup TLS, set a transport socket with name `tls` and + * :ref:`DownstreamTlsContext ` in the `typed_config`. + * If no transport socket configuration is specified, new connections + * will be set up with plaintext. + */ + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket); + /** + * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no + * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter + * chain is to be dynamically updated or removed via FCDS a unique name must be provided. + */ + 'name'?: (string); + /** + * [#not-implemented-hide:] The configuration to specify whether the filter chain will be built on-demand. + * If this field is not empty, the filter chain will be built on-demand. + * Otherwise, the filter chain will be built normally and block listener warming. + */ + 'on_demand_configuration'?: (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration); + /** + * If present and nonzero, the amount of time to allow incoming connections to complete any + * transport socket negotiations. If this expires before the transport reports connection + * establishment, the connection is summarily closed. + */ + 'transport_socket_connect_timeout'?: (_google_protobuf_Duration); +} + +/** + * A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and + * various other parameters. + * [#next-free-field: 10] + */ +export interface FilterChain__Output { + /** + * The criteria to use when matching a connection to this filter chain. + */ + 'filter_chain_match'?: (_envoy_config_listener_v3_FilterChainMatch__Output); + /** + * A list of individual network filters that make up the filter chain for + * connections established with the listener. Order matters as the filters are + * processed sequentially as connection events happen. Note: If the filter + * list is empty, the connection will close by default. + */ + 'filters': (_envoy_config_listener_v3_Filter__Output)[]; + /** + * Whether the listener should expect a PROXY protocol V1 header on new + * connections. If this option is enabled, the listener will assume that that + * remote address of the connection is the one specified in the header. Some + * load balancers including the AWS ELB support this option. If the option is + * absent or set to false, Envoy will use the physical peer address of the + * connection as the remote address. + * + * This field is deprecated. Add a + * :ref:`PROXY protocol listener filter ` + * explicitly instead. + */ + 'use_proxy_proto'?: (_google_protobuf_BoolValue__Output); + /** + * [#not-implemented-hide:] filter chain metadata. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata__Output); + /** + * Optional custom transport socket implementation to use for downstream connections. + * To setup TLS, set a transport socket with name `tls` and + * :ref:`DownstreamTlsContext ` in the `typed_config`. + * If no transport socket configuration is specified, new connections + * will be set up with plaintext. + */ + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket__Output); + /** + * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no + * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter + * chain is to be dynamically updated or removed via FCDS a unique name must be provided. + */ + 'name': (string); + /** + * [#not-implemented-hide:] The configuration to specify whether the filter chain will be built on-demand. + * If this field is not empty, the filter chain will be built on-demand. + * Otherwise, the filter chain will be built normally and block listener warming. + */ + 'on_demand_configuration'?: (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration__Output); + /** + * If present and nonzero, the amount of time to allow incoming connections to complete any + * transport socket negotiations. If this expires before the transport reports connection + * establishment, the connection is summarily closed. + */ + 'transport_socket_connect_timeout'?: (_google_protobuf_Duration__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/FilterChainMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts similarity index 81% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/listener/FilterChainMatch.ts rename to packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts index 881a5a961..1170232ae 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/FilterChainMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts @@ -1,11 +1,11 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener/listener_components.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto -import type { CidrRange as _envoy_api_v2_core_CidrRange, CidrRange__Output as _envoy_api_v2_core_CidrRange__Output } from '../../../../envoy/api/v2/core/CidrRange'; +import type { CidrRange as _envoy_config_core_v3_CidrRange, CidrRange__Output as _envoy_config_core_v3_CidrRange__Output } from '../../../../envoy/config/core/v3/CidrRange'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -// Original file: deps/envoy-api/envoy/api/v2/listener/listener_components.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto -export enum _envoy_api_v2_listener_FilterChainMatch_ConnectionSourceType { +export enum _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType { /** * Any connection source matches. */ @@ -13,7 +13,7 @@ export enum _envoy_api_v2_listener_FilterChainMatch_ConnectionSourceType { /** * Match a connection originating from the same host. */ - LOCAL = 1, + SAME_IP_OR_LOOPBACK = 1, /** * Match a connection originating from a different host. */ @@ -45,6 +45,18 @@ export enum _envoy_api_v2_listener_FilterChainMatch_ConnectionSourceType { * ``www.example.com``, then ``*.example.com``, then ``*.com``, then any filter * chain without ``server_names`` requirements). * + * A different way to reason about the filter chain matches: + * Suppose there exists N filter chains. Prune the filter chain set using the above 8 steps. + * In each step, filter chains which most specifically matches the attributes continue to the next step. + * The listener guarantees at most 1 filter chain is left after all of the steps. + * + * Example: + * + * For destination port, filter chains specifying the destination port of incoming traffic are the + * most specific match. If none of the filter chains specifies the exact destination port, the filter + * chains which do not specify ports are the most specific match. Filter chains specifying the + * wrong port can never be the most specific match. + * * [#comment: Implemented rules are kept in the preference order, with deprecated fields * listed at the end, because that's how we want to list them in the docs. * @@ -56,7 +68,7 @@ export interface FilterChainMatch { * If non-empty, an IP address and prefix length to match addresses when the * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. */ - 'prefix_ranges'?: (_envoy_api_v2_core_CidrRange)[]; + 'prefix_ranges'?: (_envoy_config_core_v3_CidrRange)[]; /** * If non-empty, an IP address and suffix length to match addresses when the * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. @@ -73,7 +85,7 @@ export interface FilterChainMatch { * parameter is not specified or the list is empty, the source IP address is * ignored. */ - 'source_prefix_ranges'?: (_envoy_api_v2_core_CidrRange)[]; + 'source_prefix_ranges'?: (_envoy_config_core_v3_CidrRange)[]; /** * The criteria is satisfied if the source port of the downstream connection * is contained in at least one of the specified ports. If the parameter is @@ -138,7 +150,7 @@ export interface FilterChainMatch { /** * Specifies the connection source IP match type. Can be any, local or external network. */ - 'source_type'?: (_envoy_api_v2_listener_FilterChainMatch_ConnectionSourceType | keyof typeof _envoy_api_v2_listener_FilterChainMatch_ConnectionSourceType); + 'source_type'?: (_envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType | keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); } /** @@ -166,6 +178,18 @@ export interface FilterChainMatch { * ``www.example.com``, then ``*.example.com``, then ``*.com``, then any filter * chain without ``server_names`` requirements). * + * A different way to reason about the filter chain matches: + * Suppose there exists N filter chains. Prune the filter chain set using the above 8 steps. + * In each step, filter chains which most specifically matches the attributes continue to the next step. + * The listener guarantees at most 1 filter chain is left after all of the steps. + * + * Example: + * + * For destination port, filter chains specifying the destination port of incoming traffic are the + * most specific match. If none of the filter chains specifies the exact destination port, the filter + * chains which do not specify ports are the most specific match. Filter chains specifying the + * wrong port can never be the most specific match. + * * [#comment: Implemented rules are kept in the preference order, with deprecated fields * listed at the end, because that's how we want to list them in the docs. * @@ -177,7 +201,7 @@ export interface FilterChainMatch__Output { * If non-empty, an IP address and prefix length to match addresses when the * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. */ - 'prefix_ranges': (_envoy_api_v2_core_CidrRange__Output)[]; + 'prefix_ranges': (_envoy_config_core_v3_CidrRange__Output)[]; /** * If non-empty, an IP address and suffix length to match addresses when the * listener is bound to 0.0.0.0/:: or when use_original_dst is specified. @@ -194,7 +218,7 @@ export interface FilterChainMatch__Output { * parameter is not specified or the list is empty, the source IP address is * ignored. */ - 'source_prefix_ranges': (_envoy_api_v2_core_CidrRange__Output)[]; + 'source_prefix_ranges': (_envoy_config_core_v3_CidrRange__Output)[]; /** * The criteria is satisfied if the source port of the downstream connection * is contained in at least one of the specified ports. If the parameter is @@ -259,5 +283,5 @@ export interface FilterChainMatch__Output { /** * Specifies the connection source IP match type. Can be any, local or external network. */ - 'source_type': (keyof typeof _envoy_api_v2_listener_FilterChainMatch_ConnectionSourceType); + 'source_type': (keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/Listener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts similarity index 66% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/Listener.ts rename to packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts index 8aed6564d..ebcac4381 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/Listener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts @@ -1,55 +1,52 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto -import type { Address as _envoy_api_v2_core_Address, Address__Output as _envoy_api_v2_core_Address__Output } from '../../../envoy/api/v2/core/Address'; -import type { FilterChain as _envoy_api_v2_listener_FilterChain, FilterChain__Output as _envoy_api_v2_listener_FilterChain__Output } from '../../../envoy/api/v2/listener/FilterChain'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../google/protobuf/BoolValue'; -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../google/protobuf/UInt32Value'; -import type { Metadata as _envoy_api_v2_core_Metadata, Metadata__Output as _envoy_api_v2_core_Metadata__Output } from '../../../envoy/api/v2/core/Metadata'; -import type { ListenerFilter as _envoy_api_v2_listener_ListenerFilter, ListenerFilter__Output as _envoy_api_v2_listener_ListenerFilter__Output } from '../../../envoy/api/v2/listener/ListenerFilter'; -import type { SocketOption as _envoy_api_v2_core_SocketOption, SocketOption__Output as _envoy_api_v2_core_SocketOption__Output } from '../../../envoy/api/v2/core/SocketOption'; -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; -import type { TrafficDirection as _envoy_api_v2_core_TrafficDirection } from '../../../envoy/api/v2/core/TrafficDirection'; -import type { UdpListenerConfig as _envoy_api_v2_listener_UdpListenerConfig, UdpListenerConfig__Output as _envoy_api_v2_listener_UdpListenerConfig__Output } from '../../../envoy/api/v2/listener/UdpListenerConfig'; -import type { ApiListener as _envoy_config_listener_v2_ApiListener, ApiListener__Output as _envoy_config_listener_v2_ApiListener__Output } from '../../../envoy/config/listener/v2/ApiListener'; -import type { AccessLog as _envoy_config_filter_accesslog_v2_AccessLog, AccessLog__Output as _envoy_config_filter_accesslog_v2_AccessLog__Output } from '../../../envoy/config/filter/accesslog/v2/AccessLog'; +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { FilterChain as _envoy_config_listener_v3_FilterChain, FilterChain__Output as _envoy_config_listener_v3_FilterChain__Output } from '../../../../envoy/config/listener/v3/FilterChain'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { ListenerFilter as _envoy_config_listener_v3_ListenerFilter, ListenerFilter__Output as _envoy_config_listener_v3_ListenerFilter__Output } from '../../../../envoy/config/listener/v3/ListenerFilter'; +import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { TrafficDirection as _envoy_config_core_v3_TrafficDirection } from '../../../../envoy/config/core/v3/TrafficDirection'; +import type { UdpListenerConfig as _envoy_config_listener_v3_UdpListenerConfig, UdpListenerConfig__Output as _envoy_config_listener_v3_UdpListenerConfig__Output } from '../../../../envoy/config/listener/v3/UdpListenerConfig'; +import type { ApiListener as _envoy_config_listener_v3_ApiListener, ApiListener__Output as _envoy_config_listener_v3_ApiListener__Output } from '../../../../envoy/config/listener/v3/ApiListener'; +import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; /** * Configuration for listener connection balancing. */ -export interface _envoy_api_v2_Listener_ConnectionBalanceConfig { +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig { /** * If specified, the listener will use the exact connection balancer. */ - 'exact_balance'?: (_envoy_api_v2_Listener_ConnectionBalanceConfig_ExactBalance); + 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance); 'balance_type'?: "exact_balance"; } /** * Configuration for listener connection balancing. */ -export interface _envoy_api_v2_Listener_ConnectionBalanceConfig__Output { +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output { /** * If specified, the listener will use the exact connection balancer. */ - 'exact_balance'?: (_envoy_api_v2_Listener_ConnectionBalanceConfig_ExactBalance__Output); + 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance__Output); 'balance_type': "exact_balance"; } /** * [#not-implemented-hide:] */ -export interface _envoy_api_v2_Listener_DeprecatedV1 { +export interface _envoy_config_listener_v3_Listener_DeprecatedV1 { /** * Whether the listener should bind to the port. A listener that doesn't * bind can only receive connections redirected from other listeners that * set use_original_dst parameter to true. Default is true. * - * This is deprecated in v2, all Listeners will bind to their port. An - * additional filter chain must be created for every original destination - * port this listener may redirect to in v2, with the original port - * specified in the FilterChainMatch destination_port field. - * - * [#comment:TODO(PiotrSikora): Remove this once verified that we no longer need it.] + * This is deprecated. Use :ref:`Listener.bind_to_port + * ` */ 'bind_to_port'?: (_google_protobuf_BoolValue); } @@ -57,25 +54,21 @@ export interface _envoy_api_v2_Listener_DeprecatedV1 { /** * [#not-implemented-hide:] */ -export interface _envoy_api_v2_Listener_DeprecatedV1__Output { +export interface _envoy_config_listener_v3_Listener_DeprecatedV1__Output { /** * Whether the listener should bind to the port. A listener that doesn't * bind can only receive connections redirected from other listeners that * set use_original_dst parameter to true. Default is true. * - * This is deprecated in v2, all Listeners will bind to their port. An - * additional filter chain must be created for every original destination - * port this listener may redirect to in v2, with the original port - * specified in the FilterChainMatch destination_port field. - * - * [#comment:TODO(PiotrSikora): Remove this once verified that we no longer need it.] + * This is deprecated. Use :ref:`Listener.bind_to_port + * ` */ 'bind_to_port'?: (_google_protobuf_BoolValue__Output); } -// Original file: deps/envoy-api/envoy/api/v2/listener.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto -export enum _envoy_api_v2_Listener_DrainType { +export enum _envoy_config_listener_v3_Listener_DrainType { /** * Drain in response to calling /healthcheck/fail admin endpoint (along with the health check * filter), listener removal/modification, and hot restart. @@ -97,7 +90,7 @@ export enum _envoy_api_v2_Listener_DrainType { * sacrifices accept throughput for accuracy and should be used when there are a small number of * connections that rarely cycle (e.g., service mesh gRPC egress). */ -export interface _envoy_api_v2_Listener_ConnectionBalanceConfig_ExactBalance { +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance { } /** @@ -108,11 +101,11 @@ export interface _envoy_api_v2_Listener_ConnectionBalanceConfig_ExactBalance { * sacrifices accept throughput for accuracy and should be used when there are a small number of * connections that rarely cycle (e.g., service mesh gRPC egress). */ -export interface _envoy_api_v2_Listener_ConnectionBalanceConfig_ExactBalance__Output { +export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance__Output { } /** - * [#next-free-field: 23] + * [#next-free-field: 27] */ export interface Listener { /** @@ -126,33 +119,23 @@ export interface Listener { * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on * Linux as the actual port will be allocated by the OS. */ - 'address'?: (_envoy_api_v2_core_Address); + 'address'?: (_envoy_config_core_v3_Address); /** * A list of filter chains to consider for this listener. The - * :ref:`FilterChain ` with the most specific - * :ref:`FilterChainMatch ` criteria is used on a + * :ref:`FilterChain ` with the most specific + * :ref:`FilterChainMatch ` criteria is used on a * connection. * * Example using SNI for filter chain selection can be found in the * :ref:`FAQ entry `. */ - 'filter_chains'?: (_envoy_api_v2_listener_FilterChain)[]; + 'filter_chains'?: (_envoy_config_listener_v3_FilterChain)[]; /** * If a connection is redirected using *iptables*, the port on which the proxy * receives it might be different from the original destination address. When this flag is set to * true, the listener hands off redirected connections to the listener associated with the * original destination address. If there is no listener associated with the original destination * address, the connection is handled by the listener that receives it. Defaults to false. - * - * .. attention:: - * - * This field is deprecated. Use :ref:`an original_dst ` - * :ref:`listener filter ` instead. - * - * Note that hand off to another listener is *NOT* performed without this flag. Once - * :ref:`FilterChainMatch ` is implemented this flag - * will be removed, as filter chain matching can be used to select a filter chain based on the - * restored destination address. */ 'use_original_dst'?: (_google_protobuf_BoolValue); /** @@ -163,34 +146,34 @@ export interface Listener { /** * Listener metadata. */ - 'metadata'?: (_envoy_api_v2_core_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata); /** * [#not-implemented-hide:] */ - 'deprecated_v1'?: (_envoy_api_v2_Listener_DeprecatedV1); + 'deprecated_v1'?: (_envoy_config_listener_v3_Listener_DeprecatedV1); /** * The type of draining to perform at a listener-wide level. */ - 'drain_type'?: (_envoy_api_v2_Listener_DrainType | keyof typeof _envoy_api_v2_Listener_DrainType); + 'drain_type'?: (_envoy_config_listener_v3_Listener_DrainType | keyof typeof _envoy_config_listener_v3_Listener_DrainType); /** * Listener filters have the opportunity to manipulate and augment the connection metadata that * is used in connection filter chain matching, for example. These filters are run before any in - * :ref:`filter_chains `. Order matters as the + * :ref:`filter_chains `. Order matters as the * filters are processed sequentially right after a socket has been accepted by the listener, and * before a connection is created. * UDP Listener filters can be specified when the protocol in the listener socket address in - * :ref:`protocol ` is :ref:`UDP - * `. + * :ref:`protocol ` is :ref:`UDP + * `. * UDP listeners currently support a single filter. */ - 'listener_filters'?: (_envoy_api_v2_listener_ListenerFilter)[]; + 'listener_filters'?: (_envoy_config_listener_v3_ListenerFilter)[]; /** * Whether the listener should be set as a transparent socket. * When this flag is set to true, connections can be redirected to the listener using an * *iptables* *TPROXY* target, in which case the original source and destination addresses and * ports are preserved on accepted connections. This flag should be used in combination with * :ref:`an original_dst ` :ref:`listener filter - * ` to mark the connections' local addresses as + * ` to mark the connections' local addresses as * "restored." This can be used to hand off each redirected connection to another listener * associated with the connection's destination address. Direct connections to the socket without * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -231,7 +214,7 @@ export interface Listener { * Additional socket options that may not be present in Envoy source code or * precompiled binaries. */ - 'socket_options'?: (_envoy_api_v2_core_SocketOption)[]; + 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; /** * The timeout to wait for all listener filters to complete operation. If the timeout is reached, * the accepted socket is closed without a connection being created unless @@ -242,7 +225,7 @@ export interface Listener { /** * Specifies the intended direction of the traffic relative to the local Envoy. */ - 'traffic_direction'?: (_envoy_api_v2_core_TrafficDirection | keyof typeof _envoy_api_v2_core_TrafficDirection); + 'traffic_direction'?: (_envoy_config_core_v3_TrafficDirection | keyof typeof _envoy_config_core_v3_TrafficDirection); /** * Whether a connection should be created when listener filters timeout. Default is false. * @@ -255,17 +238,17 @@ export interface Listener { 'continue_on_listener_filters_timeout'?: (boolean); /** * If the protocol in the listener socket address in :ref:`protocol - * ` is :ref:`UDP - * `, this field specifies the actual udp + * ` is :ref:`UDP + * `, this field specifies the actual udp * listener to create, i.e. :ref:`udp_listener_name - * ` = "raw_udp_listener" for + * ` = "raw_udp_listener" for * creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". */ - 'udp_listener_config'?: (_envoy_api_v2_listener_UdpListenerConfig); + 'udp_listener_config'?: (_envoy_config_listener_v3_UdpListenerConfig); /** * Used to represent an API listener, which is used in non-proxy clients. The type of API * exposed to the non-proxy application depends on the type of API listener. - * When this field is set, no other field except for :ref:`name` + * When this field is set, no other field except for :ref:`name` * should be set. * * .. note:: @@ -280,13 +263,13 @@ export interface Listener { * socket listener and the various types of API listener. That way, a given Listener message * can structurally only contain the fields of the relevant type.] */ - 'api_listener'?: (_envoy_config_listener_v2_ApiListener); + 'api_listener'?: (_envoy_config_listener_v3_ApiListener); /** * The listener's connection balancer configuration, currently only applicable to TCP listeners. * If no configuration is specified, Envoy will not attempt to balance active connections between * worker threads. */ - 'connection_balance_config'?: (_envoy_api_v2_Listener_ConnectionBalanceConfig); + 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig); /** * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and * create one socket for each worker thread. This makes inbound connections @@ -304,11 +287,39 @@ export interface Listener { * Configuration for :ref:`access logs ` * emitted by this listener. */ - 'access_log'?: (_envoy_config_filter_accesslog_v2_AccessLog)[]; + 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; + /** + * If the protocol in the listener socket address in :ref:`protocol + * ` is :ref:`UDP + * `, this field specifies the actual udp + * writer to create, i.e. :ref:`name ` + * = "udp_default_writer" for creating a udp writer with writing in passthrough mode, + * = "udp_gso_batch_writer" for creating a udp writer with writing in batch mode. + * If not present, treat it as "udp_default_writer". + * [#not-implemented-hide:] + */ + 'udp_writer_config'?: (_envoy_config_core_v3_TypedExtensionConfig); + /** + * The maximum length a tcp listener's pending connections queue can grow to. If no value is + * provided net.core.somaxconn will be used on Linux and 128 otherwise. + */ + 'tcp_backlog_size'?: (_google_protobuf_UInt32Value); + /** + * The default filter chain if none of the filter chain matches. If no default filter chain is supplied, + * the connection will be closed. The filter chain match is ignored in this field. + */ + 'default_filter_chain'?: (_envoy_config_listener_v3_FilterChain); + /** + * Whether the listener should bind to the port. A listener that doesn't + * bind can only receive connections redirected from other listeners that set + * :ref:`use_original_dst ` + * to true. Default is true. + */ + 'bind_to_port'?: (_google_protobuf_BoolValue); } /** - * [#next-free-field: 23] + * [#next-free-field: 27] */ export interface Listener__Output { /** @@ -322,33 +333,23 @@ export interface Listener__Output { * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on * Linux as the actual port will be allocated by the OS. */ - 'address'?: (_envoy_api_v2_core_Address__Output); + 'address'?: (_envoy_config_core_v3_Address__Output); /** * A list of filter chains to consider for this listener. The - * :ref:`FilterChain ` with the most specific - * :ref:`FilterChainMatch ` criteria is used on a + * :ref:`FilterChain ` with the most specific + * :ref:`FilterChainMatch ` criteria is used on a * connection. * * Example using SNI for filter chain selection can be found in the * :ref:`FAQ entry `. */ - 'filter_chains': (_envoy_api_v2_listener_FilterChain__Output)[]; + 'filter_chains': (_envoy_config_listener_v3_FilterChain__Output)[]; /** * If a connection is redirected using *iptables*, the port on which the proxy * receives it might be different from the original destination address. When this flag is set to * true, the listener hands off redirected connections to the listener associated with the * original destination address. If there is no listener associated with the original destination * address, the connection is handled by the listener that receives it. Defaults to false. - * - * .. attention:: - * - * This field is deprecated. Use :ref:`an original_dst ` - * :ref:`listener filter ` instead. - * - * Note that hand off to another listener is *NOT* performed without this flag. Once - * :ref:`FilterChainMatch ` is implemented this flag - * will be removed, as filter chain matching can be used to select a filter chain based on the - * restored destination address. */ 'use_original_dst'?: (_google_protobuf_BoolValue__Output); /** @@ -359,34 +360,34 @@ export interface Listener__Output { /** * Listener metadata. */ - 'metadata'?: (_envoy_api_v2_core_Metadata__Output); + 'metadata'?: (_envoy_config_core_v3_Metadata__Output); /** * [#not-implemented-hide:] */ - 'deprecated_v1'?: (_envoy_api_v2_Listener_DeprecatedV1__Output); + 'deprecated_v1'?: (_envoy_config_listener_v3_Listener_DeprecatedV1__Output); /** * The type of draining to perform at a listener-wide level. */ - 'drain_type': (keyof typeof _envoy_api_v2_Listener_DrainType); + 'drain_type': (keyof typeof _envoy_config_listener_v3_Listener_DrainType); /** * Listener filters have the opportunity to manipulate and augment the connection metadata that * is used in connection filter chain matching, for example. These filters are run before any in - * :ref:`filter_chains `. Order matters as the + * :ref:`filter_chains `. Order matters as the * filters are processed sequentially right after a socket has been accepted by the listener, and * before a connection is created. * UDP Listener filters can be specified when the protocol in the listener socket address in - * :ref:`protocol ` is :ref:`UDP - * `. + * :ref:`protocol ` is :ref:`UDP + * `. * UDP listeners currently support a single filter. */ - 'listener_filters': (_envoy_api_v2_listener_ListenerFilter__Output)[]; + 'listener_filters': (_envoy_config_listener_v3_ListenerFilter__Output)[]; /** * Whether the listener should be set as a transparent socket. * When this flag is set to true, connections can be redirected to the listener using an * *iptables* *TPROXY* target, in which case the original source and destination addresses and * ports are preserved on accepted connections. This flag should be used in combination with * :ref:`an original_dst ` :ref:`listener filter - * ` to mark the connections' local addresses as + * ` to mark the connections' local addresses as * "restored." This can be used to hand off each redirected connection to another listener * associated with the connection's destination address. Direct connections to the socket without * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -427,7 +428,7 @@ export interface Listener__Output { * Additional socket options that may not be present in Envoy source code or * precompiled binaries. */ - 'socket_options': (_envoy_api_v2_core_SocketOption__Output)[]; + 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; /** * The timeout to wait for all listener filters to complete operation. If the timeout is reached, * the accepted socket is closed without a connection being created unless @@ -438,7 +439,7 @@ export interface Listener__Output { /** * Specifies the intended direction of the traffic relative to the local Envoy. */ - 'traffic_direction': (keyof typeof _envoy_api_v2_core_TrafficDirection); + 'traffic_direction': (keyof typeof _envoy_config_core_v3_TrafficDirection); /** * Whether a connection should be created when listener filters timeout. Default is false. * @@ -451,17 +452,17 @@ export interface Listener__Output { 'continue_on_listener_filters_timeout': (boolean); /** * If the protocol in the listener socket address in :ref:`protocol - * ` is :ref:`UDP - * `, this field specifies the actual udp + * ` is :ref:`UDP + * `, this field specifies the actual udp * listener to create, i.e. :ref:`udp_listener_name - * ` = "raw_udp_listener" for + * ` = "raw_udp_listener" for * creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". */ - 'udp_listener_config'?: (_envoy_api_v2_listener_UdpListenerConfig__Output); + 'udp_listener_config'?: (_envoy_config_listener_v3_UdpListenerConfig__Output); /** * Used to represent an API listener, which is used in non-proxy clients. The type of API * exposed to the non-proxy application depends on the type of API listener. - * When this field is set, no other field except for :ref:`name` + * When this field is set, no other field except for :ref:`name` * should be set. * * .. note:: @@ -476,13 +477,13 @@ export interface Listener__Output { * socket listener and the various types of API listener. That way, a given Listener message * can structurally only contain the fields of the relevant type.] */ - 'api_listener'?: (_envoy_config_listener_v2_ApiListener__Output); + 'api_listener'?: (_envoy_config_listener_v3_ApiListener__Output); /** * The listener's connection balancer configuration, currently only applicable to TCP listeners. * If no configuration is specified, Envoy will not attempt to balance active connections between * worker threads. */ - 'connection_balance_config'?: (_envoy_api_v2_Listener_ConnectionBalanceConfig__Output); + 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output); /** * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and * create one socket for each worker thread. This makes inbound connections @@ -500,5 +501,33 @@ export interface Listener__Output { * Configuration for :ref:`access logs ` * emitted by this listener. */ - 'access_log': (_envoy_config_filter_accesslog_v2_AccessLog__Output)[]; + 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; + /** + * If the protocol in the listener socket address in :ref:`protocol + * ` is :ref:`UDP + * `, this field specifies the actual udp + * writer to create, i.e. :ref:`name ` + * = "udp_default_writer" for creating a udp writer with writing in passthrough mode, + * = "udp_gso_batch_writer" for creating a udp writer with writing in batch mode. + * If not present, treat it as "udp_default_writer". + * [#not-implemented-hide:] + */ + 'udp_writer_config'?: (_envoy_config_core_v3_TypedExtensionConfig__Output); + /** + * The maximum length a tcp listener's pending connections queue can grow to. If no value is + * provided net.core.somaxconn will be used on Linux and 128 otherwise. + */ + 'tcp_backlog_size'?: (_google_protobuf_UInt32Value__Output); + /** + * The default filter chain if none of the filter chain matches. If no default filter chain is supplied, + * the connection will be closed. The filter chain match is ignored in this field. + */ + 'default_filter_chain'?: (_envoy_config_listener_v3_FilterChain__Output); + /** + * Whether the listener should bind to the port. A listener that doesn't + * bind can only receive connections redirected from other listeners that set + * :ref:`use_original_dst ` + * to true. Default is true. + */ + 'bind_to_port'?: (_google_protobuf_BoolValue__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts new file mode 100644 index 000000000..590ca8ed3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts @@ -0,0 +1,19 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + +import type { CollectionEntry as _xds_core_v3_CollectionEntry, CollectionEntry__Output as _xds_core_v3_CollectionEntry__Output } from '../../../../xds/core/v3/CollectionEntry'; + +/** + * Listener list collections. Entries are *Listener* resources or references. + * [#not-implemented-hide:] + */ +export interface ListenerCollection { + 'entries'?: (_xds_core_v3_CollectionEntry)[]; +} + +/** + * Listener list collections. Entries are *Listener* resources or references. + * [#not-implemented-hide:] + */ +export interface ListenerCollection__Output { + 'entries': (_xds_core_v3_CollectionEntry__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ListenerFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts similarity index 58% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ListenerFilter.ts rename to packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts index 080d922b1..e8a827618 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ListenerFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts @@ -1,8 +1,7 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener/listener_components.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -import type { ListenerFilterChainMatchPredicate as _envoy_api_v2_listener_ListenerFilterChainMatchPredicate, ListenerFilterChainMatchPredicate__Output as _envoy_api_v2_listener_ListenerFilterChainMatchPredicate__Output } from '../../../../envoy/api/v2/listener/ListenerFilterChainMatchPredicate'; +import type { ListenerFilterChainMatchPredicate as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate, ListenerFilterChainMatchPredicate__Output as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output } from '../../../../envoy/config/listener/v3/ListenerFilterChainMatchPredicate'; export interface ListenerFilter { /** @@ -10,19 +9,18 @@ export interface ListenerFilter { * :ref:`supported filter `. */ 'name'?: (string); - 'config'?: (_google_protobuf_Struct); 'typed_config'?: (_google_protobuf_Any); /** * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - * See :ref:`ListenerFilterChainMatchPredicate ` + * See :ref:`ListenerFilterChainMatchPredicate ` * for further examples. */ - 'filter_disabled'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate); + 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate); /** * Filter specific configuration which depends on the filter being instantiated. * See the supported filters for further documentation. */ - 'config_type'?: "config"|"typed_config"; + 'config_type'?: "typed_config"; } export interface ListenerFilter__Output { @@ -31,17 +29,16 @@ export interface ListenerFilter__Output { * :ref:`supported filter `. */ 'name': (string); - 'config'?: (_google_protobuf_Struct__Output); 'typed_config'?: (_google_protobuf_Any__Output); /** * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - * See :ref:`ListenerFilterChainMatchPredicate ` + * See :ref:`ListenerFilterChainMatchPredicate ` * for further examples. */ - 'filter_disabled'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate__Output); + 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output); /** * Filter specific configuration which depends on the filter being instantiated. * See the supported filters for further documentation. */ - 'config_type': "config"|"typed_config"; + 'config_type': "typed_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ListenerFilterChainMatchPredicate.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts similarity index 66% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ListenerFilterChainMatchPredicate.ts rename to packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts index ac3ddfd2a..e0d9ccc3e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/ListenerFilterChainMatchPredicate.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts @@ -1,26 +1,26 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener/listener_components.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto -import type { ListenerFilterChainMatchPredicate as _envoy_api_v2_listener_ListenerFilterChainMatchPredicate, ListenerFilterChainMatchPredicate__Output as _envoy_api_v2_listener_ListenerFilterChainMatchPredicate__Output } from '../../../../envoy/api/v2/listener/ListenerFilterChainMatchPredicate'; -import type { Int32Range as _envoy_type_Int32Range, Int32Range__Output as _envoy_type_Int32Range__Output } from '../../../../envoy/type/Int32Range'; +import type { ListenerFilterChainMatchPredicate as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate, ListenerFilterChainMatchPredicate__Output as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output } from '../../../../envoy/config/listener/v3/ListenerFilterChainMatchPredicate'; +import type { Int32Range as _envoy_type_v3_Int32Range, Int32Range__Output as _envoy_type_v3_Int32Range__Output } from '../../../../envoy/type/v3/Int32Range'; /** * A set of match configurations used for logical operations. */ -export interface _envoy_api_v2_listener_ListenerFilterChainMatchPredicate_MatchSet { +export interface _envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet { /** * The list of rules that make up the set. */ - 'rules'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate)[]; + 'rules'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate)[]; } /** * A set of match configurations used for logical operations. */ -export interface _envoy_api_v2_listener_ListenerFilterChainMatchPredicate_MatchSet__Output { +export interface _envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output { /** * The list of rules that make up the set. */ - 'rules': (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate__Output)[]; + 'rules': (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output)[]; } /** @@ -57,16 +57,16 @@ export interface ListenerFilterChainMatchPredicate { * A set that describes a logical OR. If any member of the set matches, the match configuration * matches. */ - 'or_match'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate_MatchSet); + 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet); /** * A set that describes a logical AND. If all members of the set match, the match configuration * matches. */ - 'and_match'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate_MatchSet); + 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet); /** * A negation match. The match configuration will match if the negated match condition matches. */ - 'not_match'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate); + 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate); /** * The match configuration will always match. */ @@ -75,7 +75,7 @@ export interface ListenerFilterChainMatchPredicate { * Match destination port. Particularly, the match evaluation must use the recovered local port if * the owning listener filter is after :ref:`an original_dst listener filter `. */ - 'destination_port_range'?: (_envoy_type_Int32Range); + 'destination_port_range'?: (_envoy_type_v3_Int32Range); 'rule'?: "or_match"|"and_match"|"not_match"|"any_match"|"destination_port_range"; } @@ -113,16 +113,16 @@ export interface ListenerFilterChainMatchPredicate__Output { * A set that describes a logical OR. If any member of the set matches, the match configuration * matches. */ - 'or_match'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate_MatchSet__Output); + 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output); /** * A set that describes a logical AND. If all members of the set match, the match configuration * matches. */ - 'and_match'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate_MatchSet__Output); + 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output); /** * A negation match. The match configuration will match if the negated match condition matches. */ - 'not_match'?: (_envoy_api_v2_listener_ListenerFilterChainMatchPredicate__Output); + 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output); /** * The match configuration will always match. */ @@ -131,6 +131,6 @@ export interface ListenerFilterChainMatchPredicate__Output { * Match destination port. Particularly, the match evaluation must use the recovered local port if * the owning listener filter is after :ref:`an original_dst listener filter `. */ - 'destination_port_range'?: (_envoy_type_Int32Range__Output); + 'destination_port_range'?: (_envoy_type_v3_Int32Range__Output); 'rule': "or_match"|"and_match"|"not_match"|"any_match"|"destination_port_range"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/UdpListenerConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts similarity index 72% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/listener/UdpListenerConfig.ts rename to packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts index 2299c5719..c7475c42b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/listener/UdpListenerConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts @@ -1,6 +1,5 @@ -// Original file: deps/envoy-api/envoy/api/v2/listener/udp_listener_config.proto +// Original file: deps/envoy-api/envoy/config/listener/v3/udp_listener_config.proto -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; export interface UdpListenerConfig { @@ -10,13 +9,12 @@ export interface UdpListenerConfig { * If not specified, treat as "raw_udp_listener". */ 'udp_listener_name'?: (string); - 'config'?: (_google_protobuf_Struct); 'typed_config'?: (_google_protobuf_Any); /** * Used to create a specific listener factory. To some factory, e.g. * "raw_udp_listener", config is not needed. */ - 'config_type'?: "config"|"typed_config"; + 'config_type'?: "typed_config"; } export interface UdpListenerConfig__Output { @@ -26,11 +24,10 @@ export interface UdpListenerConfig__Output { * If not specified, treat as "raw_udp_listener". */ 'udp_listener_name': (string); - 'config'?: (_google_protobuf_Struct__Output); 'typed_config'?: (_google_protobuf_Any__Output); /** * Used to create a specific listener factory. To some factory, e.g. * "raw_udp_listener", config is not needed. */ - 'config_type': "config"|"typed_config"; + 'config_type': "typed_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/CorsPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts similarity index 50% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/CorsPolicy.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts index 7b76b9d85..c292a199a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/CorsPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts @@ -1,22 +1,13 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { RuntimeFractionalPercent as _envoy_api_v2_core_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_api_v2_core_RuntimeFractionalPercent__Output } from '../../../../envoy/api/v2/core/RuntimeFractionalPercent'; -import type { StringMatcher as _envoy_type_matcher_StringMatcher, StringMatcher__Output as _envoy_type_matcher_StringMatcher__Output } from '../../../../envoy/type/matcher/StringMatcher'; +import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; /** * [#next-free-field: 12] */ export interface CorsPolicy { - /** - * Specifies the origins that will be allowed to do CORS requests. - * - * An origin is allowed if either allow_origin or allow_origin_regex match. - * - * .. attention:: - * This field has been deprecated in favor of `allow_origin_string_match`. - */ - 'allow_origin'?: (string)[]; /** * Specifies the content for the *access-control-allow-methods* header. */ @@ -37,35 +28,16 @@ export interface CorsPolicy { * Specifies whether the resource allows credentials. */ 'allow_credentials'?: (_google_protobuf_BoolValue); - /** - * Specifies if the CORS filter is enabled. Defaults to true. Only effective on route. - * - * .. attention:: - * - * **This field is deprecated**. Set the - * :ref:`filter_enabled` field instead. - */ - 'enabled'?: (_google_protobuf_BoolValue); - /** - * Specifies regex patterns that match allowed origins. - * - * An origin is allowed if either allow_origin or allow_origin_regex match. - * - * .. attention:: - * This field has been deprecated in favor of `allow_origin_string_match` as it is not safe for - * use with untrusted input in all cases. - */ - 'allow_origin_regex'?: (string)[]; /** * Specifies the % of requests for which the CORS filter is enabled. * * If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS * filter will be enabled for 100% of the requests. * - * If :ref:`runtime_key ` is + * If :ref:`runtime_key ` is * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. */ - 'filter_enabled'?: (_envoy_api_v2_core_RuntimeFractionalPercent); + 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent); /** * Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not * enforced. @@ -73,32 +45,23 @@ export interface CorsPolicy { * This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those * fields have to explicitly disable the filter in order for this setting to take effect. * - * If :ref:`runtime_key ` is specified, + * If :ref:`runtime_key ` is specified, * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate * and track the request's *Origin* to determine if it's valid but will not enforce any policies. */ - 'shadow_enabled'?: (_envoy_api_v2_core_RuntimeFractionalPercent); + 'shadow_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent); /** * Specifies string patterns that match allowed origins. An origin is allowed if any of the * string matchers match. */ - 'allow_origin_string_match'?: (_envoy_type_matcher_StringMatcher)[]; - 'enabled_specifier'?: "enabled"|"filter_enabled"; + 'allow_origin_string_match'?: (_envoy_type_matcher_v3_StringMatcher)[]; + 'enabled_specifier'?: "filter_enabled"; } /** * [#next-free-field: 12] */ export interface CorsPolicy__Output { - /** - * Specifies the origins that will be allowed to do CORS requests. - * - * An origin is allowed if either allow_origin or allow_origin_regex match. - * - * .. attention:: - * This field has been deprecated in favor of `allow_origin_string_match`. - */ - 'allow_origin': (string)[]; /** * Specifies the content for the *access-control-allow-methods* header. */ @@ -119,35 +82,16 @@ export interface CorsPolicy__Output { * Specifies whether the resource allows credentials. */ 'allow_credentials'?: (_google_protobuf_BoolValue__Output); - /** - * Specifies if the CORS filter is enabled. Defaults to true. Only effective on route. - * - * .. attention:: - * - * **This field is deprecated**. Set the - * :ref:`filter_enabled` field instead. - */ - 'enabled'?: (_google_protobuf_BoolValue__Output); - /** - * Specifies regex patterns that match allowed origins. - * - * An origin is allowed if either allow_origin or allow_origin_regex match. - * - * .. attention:: - * This field has been deprecated in favor of `allow_origin_string_match` as it is not safe for - * use with untrusted input in all cases. - */ - 'allow_origin_regex': (string)[]; /** * Specifies the % of requests for which the CORS filter is enabled. * * If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS * filter will be enabled for 100% of the requests. * - * If :ref:`runtime_key ` is + * If :ref:`runtime_key ` is * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. */ - 'filter_enabled'?: (_envoy_api_v2_core_RuntimeFractionalPercent__Output); + 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); /** * Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not * enforced. @@ -155,15 +99,15 @@ export interface CorsPolicy__Output { * This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those * fields have to explicitly disable the filter in order for this setting to take effect. * - * If :ref:`runtime_key ` is specified, + * If :ref:`runtime_key ` is specified, * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate * and track the request's *Origin* to determine if it's valid but will not enforce any policies. */ - 'shadow_enabled'?: (_envoy_api_v2_core_RuntimeFractionalPercent__Output); + 'shadow_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); /** * Specifies string patterns that match allowed origins. An origin is allowed if any of the * string matchers match. */ - 'allow_origin_string_match': (_envoy_type_matcher_StringMatcher__Output)[]; - 'enabled_specifier': "enabled"|"filter_enabled"; + 'allow_origin_string_match': (_envoy_type_matcher_v3_StringMatcher__Output)[]; + 'enabled_specifier': "filter_enabled"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/Decorator.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts similarity index 94% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/Decorator.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts index 68c91327d..00e37d98f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/Decorator.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/DirectResponseAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts similarity index 52% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/DirectResponseAction.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts index 83777f9e3..9088fe712 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/DirectResponseAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts @@ -1,6 +1,6 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -import type { DataSource as _envoy_api_v2_core_DataSource, DataSource__Output as _envoy_api_v2_core_DataSource__Output } from '../../../../envoy/api/v2/core/DataSource'; +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; export interface DirectResponseAction { /** @@ -14,10 +14,10 @@ export interface DirectResponseAction { * .. note:: * * Headers can be specified using *response_headers_to_add* in the enclosing - * :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_RouteConfiguration` or - * :ref:`envoy_api_msg_route.VirtualHost`. + * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or + * :ref:`envoy_api_msg_config.route.v3.VirtualHost`. */ - 'body'?: (_envoy_api_v2_core_DataSource); + 'body'?: (_envoy_config_core_v3_DataSource); } export interface DirectResponseAction__Output { @@ -32,8 +32,8 @@ export interface DirectResponseAction__Output { * .. note:: * * Headers can be specified using *response_headers_to_add* in the enclosing - * :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_RouteConfiguration` or - * :ref:`envoy_api_msg_route.VirtualHost`. + * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or + * :ref:`envoy_api_msg_config.route.v3.VirtualHost`. */ - 'body'?: (_envoy_api_v2_core_DataSource__Output); + 'body'?: (_envoy_config_core_v3_DataSource__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/FilterAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts similarity index 82% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/FilterAction.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts index a41f3f417..78221b2c7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/FilterAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts new file mode 100644 index 000000000..ff5976e0b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts @@ -0,0 +1,47 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the + * map value in + * :ref:`VirtualHost.typed_per_filter_config`, + * :ref:`Route.typed_per_filter_config`, + * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * to add additional flags to the filter. + * [#not-implemented-hide:] + */ +export interface FilterConfig { + /** + * The filter config. + */ + 'config'?: (_google_protobuf_Any); + /** + * If true, the filter is optional, meaning that if the client does + * not support the specified filter, it may ignore the map entry rather + * than rejecting the config. + */ + 'is_optional'?: (boolean); +} + +/** + * A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the + * map value in + * :ref:`VirtualHost.typed_per_filter_config`, + * :ref:`Route.typed_per_filter_config`, + * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * to add additional flags to the filter. + * [#not-implemented-hide:] + */ +export interface FilterConfig__Output { + /** + * The filter config. + */ + 'config'?: (_google_protobuf_Any__Output); + /** + * If true, the filter is optional, meaning that if the client does + * not support the specified filter, it may ignore the map entry rather + * than rejecting the config. + */ + 'is_optional': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/HeaderMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts similarity index 69% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/HeaderMatcher.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts index 347849901..8d20c5c63 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/HeaderMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts @@ -1,7 +1,7 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -import type { Int64Range as _envoy_type_Int64Range, Int64Range__Output as _envoy_type_Int64Range__Output } from '../../../../envoy/type/Int64Range'; -import type { RegexMatcher as _envoy_type_matcher_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_RegexMatcher__Output } from '../../../../envoy/type/matcher/RegexMatcher'; +import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; import type { Long } from '@grpc/proto-loader'; /** @@ -24,12 +24,12 @@ import type { Long } from '@grpc/proto-loader'; * * .. attention:: * In the absence of any header match specifier, match will default to :ref:`present_match - * `. i.e, a request that has the :ref:`name - * ` header will match, regardless of the header's + * `. i.e, a request that has the :ref:`name + * ` header will match, regardless of the header's * value. * * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface HeaderMatcher { /** @@ -40,23 +40,6 @@ export interface HeaderMatcher { * If specified, header match will be performed based on the value of the header. */ 'exact_match'?: (string); - /** - * If specified, this regex string is a regular expression rule which implies the entire request - * header value must match the regex. The rule will not match if only a subsequence of the - * request header value matches the regex. The regex grammar used in the value field is defined - * `here `_. - * - * Examples: - * - * * The regex ``\d{3}`` matches the value *123* - * * The regex ``\d{3}`` does not match the value *1234* - * * The regex ``\d{3}`` does not match the value *123.456* - * - * .. attention:: - * This field has been deprecated in favor of `safe_regex_match` as it is not safe for use - * with untrusted input in all cases. - */ - 'regex_match'?: (string); /** * If specified, header match will be performed based on range. * The rule will match if the request header value is within this range. @@ -70,7 +53,7 @@ export interface HeaderMatcher { * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, * "-1somestring" */ - 'range_match'?: (_envoy_type_Int64Range); + 'range_match'?: (_envoy_type_v3_Int64Range); /** * If specified, header match will be performed based on whether the header is in the * request. @@ -108,11 +91,21 @@ export interface HeaderMatcher { * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. */ - 'safe_regex_match'?: (_envoy_type_matcher_RegexMatcher); + 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher); + /** + * If specified, header match will be performed based on whether the header value contains + * the given value or not. + * Note: empty contains match is not allowed, please use present_match instead. + * + * Examples: + * + * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. + */ + 'contains_match'?: (string); /** * Specifies how the header match will be performed to route the request. */ - 'header_match_specifier'?: "exact_match"|"regex_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"; + 'header_match_specifier'?: "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"; } /** @@ -135,12 +128,12 @@ export interface HeaderMatcher { * * .. attention:: * In the absence of any header match specifier, match will default to :ref:`present_match - * `. i.e, a request that has the :ref:`name - * ` header will match, regardless of the header's + * `. i.e, a request that has the :ref:`name + * ` header will match, regardless of the header's * value. * * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface HeaderMatcher__Output { /** @@ -151,23 +144,6 @@ export interface HeaderMatcher__Output { * If specified, header match will be performed based on the value of the header. */ 'exact_match'?: (string); - /** - * If specified, this regex string is a regular expression rule which implies the entire request - * header value must match the regex. The rule will not match if only a subsequence of the - * request header value matches the regex. The regex grammar used in the value field is defined - * `here `_. - * - * Examples: - * - * * The regex ``\d{3}`` matches the value *123* - * * The regex ``\d{3}`` does not match the value *1234* - * * The regex ``\d{3}`` does not match the value *123.456* - * - * .. attention:: - * This field has been deprecated in favor of `safe_regex_match` as it is not safe for use - * with untrusted input in all cases. - */ - 'regex_match'?: (string); /** * If specified, header match will be performed based on range. * The rule will match if the request header value is within this range. @@ -181,7 +157,7 @@ export interface HeaderMatcher__Output { * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, * "-1somestring" */ - 'range_match'?: (_envoy_type_Int64Range__Output); + 'range_match'?: (_envoy_type_v3_Int64Range__Output); /** * If specified, header match will be performed based on whether the header is in the * request. @@ -219,9 +195,19 @@ export interface HeaderMatcher__Output { * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. */ - 'safe_regex_match'?: (_envoy_type_matcher_RegexMatcher__Output); + 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher__Output); + /** + * If specified, header match will be performed based on whether the header value contains + * the given value or not. + * Note: empty contains match is not allowed, please use present_match instead. + * + * Examples: + * + * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. + */ + 'contains_match'?: (string); /** * Specifies how the header match will be performed to route the request. */ - 'header_match_specifier': "exact_match"|"regex_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"; + 'header_match_specifier': "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts new file mode 100644 index 000000000..344d825e7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts @@ -0,0 +1,76 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; + +/** + * HTTP request hedging :ref:`architecture overview `. + */ +export interface HedgePolicy { + /** + * Specifies the number of initial requests that should be sent upstream. + * Must be at least 1. + * Defaults to 1. + * [#not-implemented-hide:] + */ + 'initial_requests'?: (_google_protobuf_UInt32Value); + /** + * Specifies a probability that an additional upstream request should be sent + * on top of what is specified by initial_requests. + * Defaults to 0. + * [#not-implemented-hide:] + */ + 'additional_request_chance'?: (_envoy_type_v3_FractionalPercent); + /** + * Indicates that a hedged request should be sent when the per-try timeout is hit. + * This means that a retry will be issued without resetting the original request, leaving multiple upstream requests in flight. + * The first request to complete successfully will be the one returned to the caller. + * + * * At any time, a successful response (i.e. not triggering any of the retry-on conditions) would be returned to the client. + * * Before per-try timeout, an error response (per retry-on conditions) would be retried immediately or returned ot the client + * if there are no more retries left. + * * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. + * + * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + * one error code and specifies a maximum number of retries. + * + * Defaults to false. + */ + 'hedge_on_per_try_timeout'?: (boolean); +} + +/** + * HTTP request hedging :ref:`architecture overview `. + */ +export interface HedgePolicy__Output { + /** + * Specifies the number of initial requests that should be sent upstream. + * Must be at least 1. + * Defaults to 1. + * [#not-implemented-hide:] + */ + 'initial_requests'?: (_google_protobuf_UInt32Value__Output); + /** + * Specifies a probability that an additional upstream request should be sent + * on top of what is specified by initial_requests. + * Defaults to 0. + * [#not-implemented-hide:] + */ + 'additional_request_chance'?: (_envoy_type_v3_FractionalPercent__Output); + /** + * Indicates that a hedged request should be sent when the per-try timeout is hit. + * This means that a retry will be issued without resetting the original request, leaving multiple upstream requests in flight. + * The first request to complete successfully will be the one returned to the caller. + * + * * At any time, a successful response (i.e. not triggering any of the retry-on conditions) would be returned to the client. + * * Before per-try timeout, an error response (per retry-on conditions) would be retried immediately or returned ot the client + * if there are no more retries left. + * * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. + * + * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + * one error code and specifies a maximum number of retries. + * + * Defaults to false. + */ + 'hedge_on_per_try_timeout': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts new file mode 100644 index 000000000..73a2bb32e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts @@ -0,0 +1,72 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * HTTP Internal Redirect :ref:`architecture overview `. + */ +export interface InternalRedirectPolicy { + /** + * An internal redirect is not handled, unless the number of previous internal redirects that a + * downstream request has encountered is lower than this value. + * In the case where a downstream request is bounced among multiple routes by internal redirect, + * the first route that hits this threshold, or does not set :ref:`internal_redirect_policy + * ` + * will pass the redirect back to downstream. + * + * If not specified, at most one redirect will be followed. + */ + 'max_internal_redirects'?: (_google_protobuf_UInt32Value); + /** + * Defines what upstream response codes are allowed to trigger internal redirect. If unspecified, + * only 302 will be treated as internal redirect. + * Only 301, 302, 303, 307 and 308 are valid values. Any other codes will be ignored. + */ + 'redirect_response_codes'?: (number)[]; + /** + * Specifies a list of predicates that are queried when an upstream response is deemed + * to trigger an internal redirect by all other criteria. Any predicate in the list can reject + * the redirect, causing the response to be proxied to downstream. + */ + 'predicates'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Allow internal redirect to follow a target URI with a different scheme than the value of + * x-forwarded-proto. The default is false. + */ + 'allow_cross_scheme_redirect'?: (boolean); +} + +/** + * HTTP Internal Redirect :ref:`architecture overview `. + */ +export interface InternalRedirectPolicy__Output { + /** + * An internal redirect is not handled, unless the number of previous internal redirects that a + * downstream request has encountered is lower than this value. + * In the case where a downstream request is bounced among multiple routes by internal redirect, + * the first route that hits this threshold, or does not set :ref:`internal_redirect_policy + * ` + * will pass the redirect back to downstream. + * + * If not specified, at most one redirect will be followed. + */ + 'max_internal_redirects'?: (_google_protobuf_UInt32Value__Output); + /** + * Defines what upstream response codes are allowed to trigger internal redirect. If unspecified, + * only 302 will be treated as internal redirect. + * Only 301, 302, 303, 307 and 308 are valid values. Any other codes will be ignored. + */ + 'redirect_response_codes': (number)[]; + /** + * Specifies a list of predicates that are queried when an upstream response is deemed + * to trigger an internal redirect by all other criteria. Any predicate in the list can reject + * the redirect, causing the response to be proxied to downstream. + */ + 'predicates': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Allow internal redirect to follow a target URI with a different scheme than the value of + * x-forwarded-proto. The default is false. + */ + 'allow_cross_scheme_redirect': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts new file mode 100644 index 000000000..c88fa8ff7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts @@ -0,0 +1,47 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; + +/** + * Query parameter matching treats the query string of a request's :path header + * as an ampersand-separated list of keys and/or key=value elements. + * [#next-free-field: 7] + */ +export interface QueryParameterMatcher { + /** + * Specifies the name of a key that must be present in the requested + * *path*'s query string. + */ + 'name'?: (string); + /** + * Specifies whether a query parameter value should match against a string. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher); + /** + * Specifies whether a query parameter should be present. + */ + 'present_match'?: (boolean); + 'query_parameter_match_specifier'?: "string_match"|"present_match"; +} + +/** + * Query parameter matching treats the query string of a request's :path header + * as an ampersand-separated list of keys and/or key=value elements. + * [#next-free-field: 7] + */ +export interface QueryParameterMatcher__Output { + /** + * Specifies the name of a key that must be present in the requested + * *path*'s query string. + */ + 'name': (string); + /** + * Specifies whether a query parameter value should match against a string. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output); + /** + * Specifies whether a query parameter should be present. + */ + 'present_match'?: (boolean); + 'query_parameter_match_specifier': "string_match"|"present_match"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts new file mode 100644 index 000000000..33d93af7d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts @@ -0,0 +1,578 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; +import type { MetadataKey as _envoy_type_metadata_v3_MetadataKey, MetadataKey__Output as _envoy_type_metadata_v3_MetadataKey__Output } from '../../../../envoy/type/metadata/v3/MetadataKey'; + +/** + * [#next-free-field: 10] + */ +export interface _envoy_config_route_v3_RateLimit_Action { + /** + * Rate limit on source cluster. + */ + 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster); + /** + * Rate limit on destination cluster. + */ + 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster); + /** + * Rate limit on request headers. + */ + 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders); + /** + * Rate limit on remote address. + */ + 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress); + /** + * Rate limit on a generic key. + */ + 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey); + /** + * Rate limit on the existence of request headers. + */ + 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch); + /** + * Rate limit on dynamic metadata. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`metadata ` field + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData); + /** + * Rate limit on metadata. + */ + 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData); + /** + * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + */ + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig); + 'action_specifier'?: "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; +} + +/** + * [#next-free-field: 10] + */ +export interface _envoy_config_route_v3_RateLimit_Action__Output { + /** + * Rate limit on source cluster. + */ + 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster__Output); + /** + * Rate limit on destination cluster. + */ + 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster__Output); + /** + * Rate limit on request headers. + */ + 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders__Output); + /** + * Rate limit on remote address. + */ + 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress__Output); + /** + * Rate limit on a generic key. + */ + 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey__Output); + /** + * Rate limit on the existence of request headers. + */ + 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Output); + /** + * Rate limit on dynamic metadata. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`metadata ` field + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output); + /** + * Rate limit on metadata. + */ + 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData__Output); + /** + * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + */ + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig__Output); + 'action_specifier': "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("destination_cluster", "") + * + * Once a request matches against a route table rule, a routed cluster is determined by one of + * the following :ref:`route table configuration ` + * settings: + * + * * :ref:`cluster ` indicates the upstream cluster + * to route to. + * * :ref:`weighted_clusters ` + * chooses a cluster randomly from a set of clusters with attributed weight. + * * :ref:`cluster_header ` indicates which + * header in the request contains the target cluster. + */ +export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster { +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("destination_cluster", "") + * + * Once a request matches against a route table rule, a routed cluster is determined by one of + * the following :ref:`route table configuration ` + * settings: + * + * * :ref:`cluster ` indicates the upstream cluster + * to route to. + * * :ref:`weighted_clusters ` + * chooses a cluster randomly from a set of clusters with attributed weight. + * * :ref:`cluster_header ` indicates which + * header in the request contains the target cluster. + */ +export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster__Output { +} + +/** + * The following descriptor entry is appended when the + * :ref:`dynamic metadata ` contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + * + * .. attention:: + * This action has been deprecated in favor of the :ref:`metadata ` action + */ +export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key'?: (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the dynamic metadata is of type string. + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value'?: (string); +} + +/** + * The following descriptor entry is appended when the + * :ref:`dynamic metadata ` contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + * + * .. attention:: + * This action has been deprecated in favor of the :ref:`metadata ` action + */ +export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key': (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the dynamic metadata is of type string. + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value': (string); +} + +/** + * Fetches the override from the dynamic metadata. + */ +export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata { + /** + * Metadata struct that defines the key and path to retrieve the struct value. + * The value must be a struct containing an integer "requests_per_unit" property + * and a "unit" property with a value parseable to :ref:`RateLimitUnit + * enum ` + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); +} + +/** + * Fetches the override from the dynamic metadata. + */ +export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Output { + /** + * Metadata struct that defines the key and path to retrieve the struct value. + * The value must be a struct containing an integer "requests_per_unit" property + * and a "unit" property with a value parseable to :ref:`RateLimitUnit + * enum ` + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("generic_key", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_GenericKey { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value'?: (string); + /** + * An optional key to use in the descriptor entry. If not set it defaults + * to 'generic_key' as the descriptor key. + */ + 'descriptor_key'?: (string); +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("generic_key", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_GenericKey__Output { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value': (string); + /** + * An optional key to use in the descriptor entry. If not set it defaults + * to 'generic_key' as the descriptor key. + */ + 'descriptor_key': (string); +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("header_match", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value'?: (string); + /** + * If set to true, the action will append a descriptor entry when the + * request matches the headers. If set to false, the action will append a + * descriptor entry when the request does not match the headers. The + * default value is true. + */ + 'expect_match'?: (_google_protobuf_BoolValue); + /** + * Specifies a set of headers that the rate limit action should match + * on. The action will check the request’s headers against all the + * specified headers in the config. A match will happen if all the + * headers in the config are present in the request with the same values + * (or based on presence if the value field is not in the config). + */ + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("header_match", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Output { + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value': (string); + /** + * If set to true, the action will append a descriptor entry when the + * request matches the headers. If set to false, the action will append a + * descriptor entry when the request does not match the headers. The + * default value is true. + */ + 'expect_match'?: (_google_protobuf_BoolValue__Output); + /** + * Specifies a set of headers that the rate limit action should match + * on. The action will check the request’s headers against all the + * specified headers in the config. A match will happen if all the + * headers in the config are present in the request with the same values + * (or based on presence if the value field is not in the config). + */ + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; +} + +/** + * The following descriptor entry is appended when the metadata contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_MetaData { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key'?: (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the metadata is of type string. + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value'?: (string); + /** + * Source of metadata + */ + 'source'?: (_envoy_config_route_v3_RateLimit_Action_MetaData_Source | keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); +} + +/** + * The following descriptor entry is appended when the metadata contains a key value: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_MetaData__Output { + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key': (string); + /** + * Metadata struct that defines the key and path to retrieve the string value. A match will + * only happen if the value in the metadata is of type string. + */ + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); + /** + * An optional value to use if *metadata_key* is empty. If not set and + * no value is present under the metadata_key then no descriptor is generated. + */ + 'default_value': (string); + /** + * Source of metadata + */ + 'source': (keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); +} + +export interface _envoy_config_route_v3_RateLimit_Override { + /** + * Limit override from dynamic metadata. + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata); + 'override_specifier'?: "dynamic_metadata"; +} + +export interface _envoy_config_route_v3_RateLimit_Override__Output { + /** + * Limit override from dynamic metadata. + */ + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Output); + 'override_specifier': "dynamic_metadata"; +} + +/** + * The following descriptor entry is appended to the descriptor and is populated using the + * trusted address from :ref:`x-forwarded-for `: + * + * .. code-block:: cpp + * + * ("remote_address", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RemoteAddress { +} + +/** + * The following descriptor entry is appended to the descriptor and is populated using the + * trusted address from :ref:`x-forwarded-for `: + * + * .. code-block:: cpp + * + * ("remote_address", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RemoteAddress__Output { +} + +/** + * The following descriptor entry is appended when a header contains a key that matches the + * *header_name*: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RequestHeaders { + /** + * The header name to be queried from the request headers. The header’s + * value is used to populate the value of the descriptor entry for the + * descriptor_key. + */ + 'header_name'?: (string); + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key'?: (string); + /** + * If set to true, Envoy skips the descriptor while calling rate limiting service + * when header is not present in the request. By default it skips calling the + * rate limiting service if this header is not present in the request. + */ + 'skip_if_absent'?: (boolean); +} + +/** + * The following descriptor entry is appended when a header contains a key that matches the + * *header_name*: + * + * .. code-block:: cpp + * + * ("", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_RequestHeaders__Output { + /** + * The header name to be queried from the request headers. The header’s + * value is used to populate the value of the descriptor entry for the + * descriptor_key. + */ + 'header_name': (string); + /** + * The key to use in the descriptor entry. + */ + 'descriptor_key': (string); + /** + * If set to true, Envoy skips the descriptor while calling rate limiting service + * when header is not present in the request. By default it skips calling the + * rate limiting service if this header is not present in the request. + */ + 'skip_if_absent': (boolean); +} + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_RateLimit_Action_MetaData_Source { + /** + * Query :ref:`dynamic metadata ` + */ + DYNAMIC = 0, + /** + * Query :ref:`route entry metadata ` + */ + ROUTE_ENTRY = 1, +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("source_cluster", "") + * + * is derived from the :option:`--service-cluster` option. + */ +export interface _envoy_config_route_v3_RateLimit_Action_SourceCluster { +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("source_cluster", "") + * + * is derived from the :option:`--service-cluster` option. + */ +export interface _envoy_config_route_v3_RateLimit_Action_SourceCluster__Output { +} + +/** + * Global rate limiting :ref:`architecture overview `. + * Also applies to Local rate limiting :ref:`using descriptors `. + */ +export interface RateLimit { + /** + * Refers to the stage set in the filter. The rate limit configuration only + * applies to filters with the same stage number. The default stage number is + * 0. + * + * .. note:: + * + * The filter supports a range of 0 - 10 inclusively for stage numbers. + */ + 'stage'?: (_google_protobuf_UInt32Value); + /** + * The key to be set in runtime to disable this rate limit configuration. + */ + 'disable_key'?: (string); + /** + * A list of actions that are to be applied for this rate limit configuration. + * Order matters as the actions are processed sequentially and the descriptor + * is composed by appending descriptor entries in that sequence. If an action + * cannot append a descriptor entry, no descriptor is generated for the + * configuration. See :ref:`composing actions + * ` for additional documentation. + */ + 'actions'?: (_envoy_config_route_v3_RateLimit_Action)[]; + /** + * An optional limit override to be appended to the descriptor produced by this + * rate limit configuration. If the override value is invalid or cannot be resolved + * from metadata, no override is provided. See :ref:`rate limit override + * ` for more information. + */ + 'limit'?: (_envoy_config_route_v3_RateLimit_Override); +} + +/** + * Global rate limiting :ref:`architecture overview `. + * Also applies to Local rate limiting :ref:`using descriptors `. + */ +export interface RateLimit__Output { + /** + * Refers to the stage set in the filter. The rate limit configuration only + * applies to filters with the same stage number. The default stage number is + * 0. + * + * .. note:: + * + * The filter supports a range of 0 - 10 inclusively for stage numbers. + */ + 'stage'?: (_google_protobuf_UInt32Value__Output); + /** + * The key to be set in runtime to disable this rate limit configuration. + */ + 'disable_key': (string); + /** + * A list of actions that are to be applied for this rate limit configuration. + * Order matters as the actions are processed sequentially and the descriptor + * is composed by appending descriptor entries in that sequence. If an action + * cannot append a descriptor entry, no descriptor is generated for the + * configuration. See :ref:`composing actions + * ` for additional documentation. + */ + 'actions': (_envoy_config_route_v3_RateLimit_Action__Output)[]; + /** + * An optional limit override to be appended to the descriptor produced by this + * rate limit configuration. If the override value is invalid or cannot be resolved + * from metadata, no override is provided. See :ref:`rate limit override + * ` for more information. + */ + 'limit'?: (_envoy_config_route_v3_RateLimit_Override__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts new file mode 100644 index 000000000..baea6bc4e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts @@ -0,0 +1,222 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { RegexMatchAndSubstitute as _envoy_type_matcher_v3_RegexMatchAndSubstitute, RegexMatchAndSubstitute__Output as _envoy_type_matcher_v3_RegexMatchAndSubstitute__Output } from '../../../../envoy/type/matcher/v3/RegexMatchAndSubstitute'; + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_RedirectAction_RedirectResponseCode { + /** + * Moved Permanently HTTP Status Code - 301. + */ + MOVED_PERMANENTLY = 0, + /** + * Found HTTP Status Code - 302. + */ + FOUND = 1, + /** + * See Other HTTP Status Code - 303. + */ + SEE_OTHER = 2, + /** + * Temporary Redirect HTTP Status Code - 307. + */ + TEMPORARY_REDIRECT = 3, + /** + * Permanent Redirect HTTP Status Code - 308. + */ + PERMANENT_REDIRECT = 4, +} + +/** + * [#next-free-field: 10] + */ +export interface RedirectAction { + /** + * The host portion of the URL will be swapped with this value. + */ + 'host_redirect'?: (string); + /** + * The path portion of the URL will be swapped with this value. + * Please note that query string in path_redirect will override the + * request's query string and will not be stripped. + * + * For example, let's say we have the following routes: + * + * - match: { path: "/old-path-1" } + * redirect: { path_redirect: "/new-path-1" } + * - match: { path: "/old-path-2" } + * redirect: { path_redirect: "/new-path-2", strip-query: "true" } + * - match: { path: "/old-path-3" } + * redirect: { path_redirect: "/new-path-3?foo=1", strip_query: "true" } + * + * 1. if request uri is "/old-path-1?bar=1", users will be redirected to "/new-path-1?bar=1" + * 2. if request uri is "/old-path-2?bar=1", users will be redirected to "/new-path-2" + * 3. if request uri is "/old-path-3?bar=1", users will be redirected to "/new-path-3?foo=1" + */ + 'path_redirect'?: (string); + /** + * The HTTP status code to use in the redirect response. The default response + * code is MOVED_PERMANENTLY (301). + */ + 'response_code'?: (_envoy_config_route_v3_RedirectAction_RedirectResponseCode | keyof typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode); + /** + * The scheme portion of the URL will be swapped with "https". + */ + 'https_redirect'?: (boolean); + /** + * Indicates that during redirection, the matched prefix (or path) + * should be swapped with this value. This option allows redirect URLs be dynamically created + * based on the request. + * + * .. attention:: + * + * Pay attention to the use of trailing slashes as mentioned in + * :ref:`RouteAction's prefix_rewrite `. + */ + 'prefix_rewrite'?: (string); + /** + * Indicates that during redirection, the query portion of the URL will + * be removed. Default value is false. + */ + 'strip_query'?: (boolean); + /** + * The scheme portion of the URL will be swapped with this value. + */ + 'scheme_redirect'?: (string); + /** + * The port value of the URL will be swapped with this value. + */ + 'port_redirect'?: (number); + /** + * Indicates that during redirect, portions of the path that match the + * pattern should be rewritten, even allowing the substitution of capture + * groups from the pattern into the new path as specified by the rewrite + * substitution string. This is useful to allow application paths to be + * rewritten in a way that is aware of segments with variable content like + * identifiers. + * + * Examples using Google's `RE2 `_ engine: + * + * * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution + * string of ``\2/instance/\1`` would transform ``/service/foo/v1/api`` + * into ``/v1/api/instance/foo``. + * + * * The pattern ``one`` paired with a substitution string of ``two`` would + * transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``. + * + * * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of + * ``\1two\2`` would replace only the first occurrence of ``one``, + * transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``. + * + * * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/`` + * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to + * ``/aaa/yyy/bbb``. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); + /** + * When the scheme redirection take place, the following rules apply: + * 1. If the source URI scheme is `http` and the port is explicitly + * set to `:80`, the port will be removed after the redirection + * 2. If the source URI scheme is `https` and the port is explicitly + * set to `:443`, the port will be removed after the redirection + */ + 'scheme_rewrite_specifier'?: "https_redirect"|"scheme_redirect"; + 'path_rewrite_specifier'?: "path_redirect"|"prefix_rewrite"|"regex_rewrite"; +} + +/** + * [#next-free-field: 10] + */ +export interface RedirectAction__Output { + /** + * The host portion of the URL will be swapped with this value. + */ + 'host_redirect': (string); + /** + * The path portion of the URL will be swapped with this value. + * Please note that query string in path_redirect will override the + * request's query string and will not be stripped. + * + * For example, let's say we have the following routes: + * + * - match: { path: "/old-path-1" } + * redirect: { path_redirect: "/new-path-1" } + * - match: { path: "/old-path-2" } + * redirect: { path_redirect: "/new-path-2", strip-query: "true" } + * - match: { path: "/old-path-3" } + * redirect: { path_redirect: "/new-path-3?foo=1", strip_query: "true" } + * + * 1. if request uri is "/old-path-1?bar=1", users will be redirected to "/new-path-1?bar=1" + * 2. if request uri is "/old-path-2?bar=1", users will be redirected to "/new-path-2" + * 3. if request uri is "/old-path-3?bar=1", users will be redirected to "/new-path-3?foo=1" + */ + 'path_redirect'?: (string); + /** + * The HTTP status code to use in the redirect response. The default response + * code is MOVED_PERMANENTLY (301). + */ + 'response_code': (keyof typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode); + /** + * The scheme portion of the URL will be swapped with "https". + */ + 'https_redirect'?: (boolean); + /** + * Indicates that during redirection, the matched prefix (or path) + * should be swapped with this value. This option allows redirect URLs be dynamically created + * based on the request. + * + * .. attention:: + * + * Pay attention to the use of trailing slashes as mentioned in + * :ref:`RouteAction's prefix_rewrite `. + */ + 'prefix_rewrite'?: (string); + /** + * Indicates that during redirection, the query portion of the URL will + * be removed. Default value is false. + */ + 'strip_query': (boolean); + /** + * The scheme portion of the URL will be swapped with this value. + */ + 'scheme_redirect'?: (string); + /** + * The port value of the URL will be swapped with this value. + */ + 'port_redirect': (number); + /** + * Indicates that during redirect, portions of the path that match the + * pattern should be rewritten, even allowing the substitution of capture + * groups from the pattern into the new path as specified by the rewrite + * substitution string. This is useful to allow application paths to be + * rewritten in a way that is aware of segments with variable content like + * identifiers. + * + * Examples using Google's `RE2 `_ engine: + * + * * The path pattern ``^/service/([^/]+)(/.*)$`` paired with a substitution + * string of ``\2/instance/\1`` would transform ``/service/foo/v1/api`` + * into ``/v1/api/instance/foo``. + * + * * The pattern ``one`` paired with a substitution string of ``two`` would + * transform ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/two/zzz``. + * + * * The pattern ``^(.*?)one(.*)$`` paired with a substitution string of + * ``\1two\2`` would replace only the first occurrence of ``one``, + * transforming path ``/xxx/one/yyy/one/zzz`` into ``/xxx/two/yyy/one/zzz``. + * + * * The pattern ``(?i)/xxx/`` paired with a substitution string of ``/yyy/`` + * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to + * ``/aaa/yyy/bbb``. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); + /** + * When the scheme redirection take place, the following rules apply: + * 1. If the source URI scheme is `http` and the port is explicitly + * set to `:80`, the port will be removed after the redirection + * 2. If the source URI scheme is `https` and the port is explicitly + * set to `:443`, the port will be removed after the redirection + */ + 'scheme_rewrite_specifier': "https_redirect"|"scheme_redirect"; + 'path_rewrite_specifier': "path_redirect"|"prefix_rewrite"|"regex_rewrite"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts new file mode 100644 index 000000000..52f391dd9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts @@ -0,0 +1,392 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Long } from '@grpc/proto-loader'; + +/** + * A retry back-off strategy that applies when the upstream server rate limits + * the request. + * + * Given this configuration: + * + * .. code-block:: yaml + * + * rate_limited_retry_back_off: + * reset_headers: + * - name: Retry-After + * format: SECONDS + * - name: X-RateLimit-Reset + * format: UNIX_TIMESTAMP + * max_interval: "300s" + * + * The following algorithm will apply: + * + * 1. If the response contains the header ``Retry-After`` its value must be on + * the form ``120`` (an integer that represents the number of seconds to + * wait before retrying). If so, this value is used as the back-off interval. + * 2. Otherwise, if the response contains the header ``X-RateLimit-Reset`` its + * value must be on the form ``1595320702`` (an integer that represents the + * point in time at which to retry, as a Unix timestamp in seconds). If so, + * the current time is subtracted from this value and the result is used as + * the back-off interval. + * 3. Otherwise, Envoy will use the default + * :ref:`exponential back-off ` + * strategy. + * + * No matter which format is used, if the resulting back-off interval exceeds + * ``max_interval`` it is discarded and the next header in ``reset_headers`` + * is tried. If a request timeout is configured for the route it will further + * limit how long the request will be allowed to run. + * + * To prevent many clients retrying at the same point in time jitter is added + * to the back-off interval, so the resulting interval is decided by taking: + * ``random(interval, interval * 1.5)``. + * + * .. attention:: + * + * Configuring ``rate_limited_retry_back_off`` will not by itself cause a request + * to be retried. You will still need to configure the right retry policy to match + * the responses from the upstream server. + */ +export interface _envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff { + /** + * Specifies the reset headers (like ``Retry-After`` or ``X-RateLimit-Reset``) + * to match against the response. Headers are tried in order, and matched case + * insensitive. The first header to be parsed successfully is used. If no headers + * match the default exponential back-off is used instead. + */ + 'reset_headers'?: (_envoy_config_route_v3_RetryPolicy_ResetHeader)[]; + /** + * Specifies the maximum back off interval that Envoy will allow. If a reset + * header contains an interval longer than this then it will be discarded and + * the next header will be tried. Defaults to 300 seconds. + */ + 'max_interval'?: (_google_protobuf_Duration); +} + +/** + * A retry back-off strategy that applies when the upstream server rate limits + * the request. + * + * Given this configuration: + * + * .. code-block:: yaml + * + * rate_limited_retry_back_off: + * reset_headers: + * - name: Retry-After + * format: SECONDS + * - name: X-RateLimit-Reset + * format: UNIX_TIMESTAMP + * max_interval: "300s" + * + * The following algorithm will apply: + * + * 1. If the response contains the header ``Retry-After`` its value must be on + * the form ``120`` (an integer that represents the number of seconds to + * wait before retrying). If so, this value is used as the back-off interval. + * 2. Otherwise, if the response contains the header ``X-RateLimit-Reset`` its + * value must be on the form ``1595320702`` (an integer that represents the + * point in time at which to retry, as a Unix timestamp in seconds). If so, + * the current time is subtracted from this value and the result is used as + * the back-off interval. + * 3. Otherwise, Envoy will use the default + * :ref:`exponential back-off ` + * strategy. + * + * No matter which format is used, if the resulting back-off interval exceeds + * ``max_interval`` it is discarded and the next header in ``reset_headers`` + * is tried. If a request timeout is configured for the route it will further + * limit how long the request will be allowed to run. + * + * To prevent many clients retrying at the same point in time jitter is added + * to the back-off interval, so the resulting interval is decided by taking: + * ``random(interval, interval * 1.5)``. + * + * .. attention:: + * + * Configuring ``rate_limited_retry_back_off`` will not by itself cause a request + * to be retried. You will still need to configure the right retry policy to match + * the responses from the upstream server. + */ +export interface _envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output { + /** + * Specifies the reset headers (like ``Retry-After`` or ``X-RateLimit-Reset``) + * to match against the response. Headers are tried in order, and matched case + * insensitive. The first header to be parsed successfully is used. If no headers + * match the default exponential back-off is used instead. + */ + 'reset_headers': (_envoy_config_route_v3_RetryPolicy_ResetHeader__Output)[]; + /** + * Specifies the maximum back off interval that Envoy will allow. If a reset + * header contains an interval longer than this then it will be discarded and + * the next header will be tried. Defaults to 300 seconds. + */ + 'max_interval'?: (_google_protobuf_Duration__Output); +} + +export interface _envoy_config_route_v3_RetryPolicy_ResetHeader { + /** + * The name of the reset header. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'name'?: (string); + /** + * The format of the reset header. + */ + 'format'?: (_envoy_config_route_v3_RetryPolicy_ResetHeaderFormat | keyof typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat); +} + +export interface _envoy_config_route_v3_RetryPolicy_ResetHeader__Output { + /** + * The name of the reset header. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'name': (string); + /** + * The format of the reset header. + */ + 'format': (keyof typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat); +} + +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +export enum _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat { + SECONDS = 0, + UNIX_TIMESTAMP = 1, +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff { + /** + * Specifies the base interval between retries. This parameter is required and must be greater + * than zero. Values less than 1 ms are rounded up to 1 ms. + * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's + * back-off algorithm. + */ + 'base_interval'?: (_google_protobuf_Duration); + /** + * Specifies the maximum interval between retries. This parameter is optional, but must be + * greater than or equal to the `base_interval` if set. The default is 10 times the + * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion + * of Envoy's back-off algorithm. + */ + 'max_interval'?: (_google_protobuf_Duration); +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff__Output { + /** + * Specifies the base interval between retries. This parameter is required and must be greater + * than zero. Values less than 1 ms are rounded up to 1 ms. + * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's + * back-off algorithm. + */ + 'base_interval'?: (_google_protobuf_Duration__Output); + /** + * Specifies the maximum interval between retries. This parameter is optional, but must be + * greater than or equal to the `base_interval` if set. The default is 10 times the + * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion + * of Envoy's back-off algorithm. + */ + 'max_interval'?: (_google_protobuf_Duration__Output); +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate { + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any); + 'config_type'?: "typed_config"; +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate__Output { + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output); + 'config_type': "typed_config"; +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryPriority { + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any); + 'config_type'?: "typed_config"; +} + +export interface _envoy_config_route_v3_RetryPolicy_RetryPriority__Output { + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output); + 'config_type': "typed_config"; +} + +/** + * HTTP retry :ref:`architecture overview `. + * [#next-free-field: 12] + */ +export interface RetryPolicy { + /** + * Specifies the conditions under which retry takes place. These are the same + * conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and + * :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. + */ + 'retry_on'?: (string); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. These are the same conditions documented for + * :ref:`config_http_filters_router_x-envoy-max-retries`. + */ + 'num_retries'?: (_google_protobuf_UInt32Value); + /** + * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The + * same conditions documented for + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. + * + * .. note:: + * + * If left unspecified, Envoy will use the global + * :ref:`route timeout ` for the request. + * Consequently, when using a :ref:`5xx ` based + * retry policy, a request that times out will not be retried as the total timeout budget + * would have been exhausted. + */ + 'per_try_timeout'?: (_google_protobuf_Duration); + /** + * Specifies an implementation of a RetryPriority which is used to determine the + * distribution of load across priorities used for retries. Refer to + * :ref:`retry plugin configuration ` for more details. + */ + 'retry_priority'?: (_envoy_config_route_v3_RetryPolicy_RetryPriority); + /** + * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host + * for retries. If any of the predicates reject the host, host selection will be reattempted. + * Refer to :ref:`retry plugin configuration ` for more + * details. + */ + 'retry_host_predicate'?: (_envoy_config_route_v3_RetryPolicy_RetryHostPredicate)[]; + /** + * The maximum number of times host selection will be reattempted before giving up, at which + * point the host that was last selected will be routed to. If unspecified, this will default to + * retrying once. + */ + 'host_selection_retry_max_attempts'?: (number | string | Long); + /** + * HTTP status codes that should trigger a retry in addition to those specified by retry_on. + */ + 'retriable_status_codes'?: (number)[]; + /** + * Specifies parameters that control exponential retry back off. This parameter is optional, in which case the + * default base interval is 25 milliseconds or, if set, the current value of the + * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times + * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` + * describes Envoy's back-off algorithm. + */ + 'retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RetryBackOff); + /** + * HTTP response headers that trigger a retry if present in the response. A retry will be + * triggered if any of the header matches match the upstream response headers. + * The field is only consulted if 'retriable-headers' retry policy is active. + */ + 'retriable_headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; + /** + * HTTP headers which must be present in the request for retries to be attempted. + */ + 'retriable_request_headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; + /** + * Specifies parameters that control a retry back-off strategy that is used + * when the request is rate limited by the upstream server. The server may + * return a response header like ``Retry-After`` or ``X-RateLimit-Reset`` to + * provide feedback to the client on how long to wait before retrying. If + * configured, this back-off strategy will be used instead of the + * default exponential back off strategy (configured using `retry_back_off`) + * whenever a response includes the matching headers. + */ + 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff); +} + +/** + * HTTP retry :ref:`architecture overview `. + * [#next-free-field: 12] + */ +export interface RetryPolicy__Output { + /** + * Specifies the conditions under which retry takes place. These are the same + * conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and + * :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. + */ + 'retry_on': (string); + /** + * Specifies the allowed number of retries. This parameter is optional and + * defaults to 1. These are the same conditions documented for + * :ref:`config_http_filters_router_x-envoy-max-retries`. + */ + 'num_retries'?: (_google_protobuf_UInt32Value__Output); + /** + * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The + * same conditions documented for + * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. + * + * .. note:: + * + * If left unspecified, Envoy will use the global + * :ref:`route timeout ` for the request. + * Consequently, when using a :ref:`5xx ` based + * retry policy, a request that times out will not be retried as the total timeout budget + * would have been exhausted. + */ + 'per_try_timeout'?: (_google_protobuf_Duration__Output); + /** + * Specifies an implementation of a RetryPriority which is used to determine the + * distribution of load across priorities used for retries. Refer to + * :ref:`retry plugin configuration ` for more details. + */ + 'retry_priority'?: (_envoy_config_route_v3_RetryPolicy_RetryPriority__Output); + /** + * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host + * for retries. If any of the predicates reject the host, host selection will be reattempted. + * Refer to :ref:`retry plugin configuration ` for more + * details. + */ + 'retry_host_predicate': (_envoy_config_route_v3_RetryPolicy_RetryHostPredicate__Output)[]; + /** + * The maximum number of times host selection will be reattempted before giving up, at which + * point the host that was last selected will be routed to. If unspecified, this will default to + * retrying once. + */ + 'host_selection_retry_max_attempts': (string); + /** + * HTTP status codes that should trigger a retry in addition to those specified by retry_on. + */ + 'retriable_status_codes': (number)[]; + /** + * Specifies parameters that control exponential retry back off. This parameter is optional, in which case the + * default base interval is 25 milliseconds or, if set, the current value of the + * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times + * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` + * describes Envoy's back-off algorithm. + */ + 'retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RetryBackOff__Output); + /** + * HTTP response headers that trigger a retry if present in the response. A retry will be + * triggered if any of the header matches match the upstream response headers. + * The field is only consulted if 'retriable-headers' retry policy is active. + */ + 'retriable_headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; + /** + * HTTP headers which must be present in the request for retries to be attempted. + */ + 'retriable_request_headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; + /** + * Specifies parameters that control a retry back-off strategy that is used + * when the request is rate limited by the upstream server. The server may + * return a response header like ``Retry-After`` or ``X-RateLimit-Reset`` to + * provide feedback to the client on how long to wait before retrying. If + * configured, this back-off strategy will be used instead of the + * default exponential back off strategy (configured using `retry_back_off`) + * whenever a response includes the matching headers. + */ + 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/Route.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts similarity index 58% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/Route.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts index 86cb4ac3d..ea00564f8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/Route.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts @@ -1,17 +1,16 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -import type { RouteMatch as _envoy_api_v2_route_RouteMatch, RouteMatch__Output as _envoy_api_v2_route_RouteMatch__Output } from '../../../../envoy/api/v2/route/RouteMatch'; -import type { RouteAction as _envoy_api_v2_route_RouteAction, RouteAction__Output as _envoy_api_v2_route_RouteAction__Output } from '../../../../envoy/api/v2/route/RouteAction'; -import type { RedirectAction as _envoy_api_v2_route_RedirectAction, RedirectAction__Output as _envoy_api_v2_route_RedirectAction__Output } from '../../../../envoy/api/v2/route/RedirectAction'; -import type { Metadata as _envoy_api_v2_core_Metadata, Metadata__Output as _envoy_api_v2_core_Metadata__Output } from '../../../../envoy/api/v2/core/Metadata'; -import type { Decorator as _envoy_api_v2_route_Decorator, Decorator__Output as _envoy_api_v2_route_Decorator__Output } from '../../../../envoy/api/v2/route/Decorator'; -import type { DirectResponseAction as _envoy_api_v2_route_DirectResponseAction, DirectResponseAction__Output as _envoy_api_v2_route_DirectResponseAction__Output } from '../../../../envoy/api/v2/route/DirectResponseAction'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { HeaderValueOption as _envoy_api_v2_core_HeaderValueOption, HeaderValueOption__Output as _envoy_api_v2_core_HeaderValueOption__Output } from '../../../../envoy/api/v2/core/HeaderValueOption'; +import type { RouteMatch as _envoy_config_route_v3_RouteMatch, RouteMatch__Output as _envoy_config_route_v3_RouteMatch__Output } from '../../../../envoy/config/route/v3/RouteMatch'; +import type { RouteAction as _envoy_config_route_v3_RouteAction, RouteAction__Output as _envoy_config_route_v3_RouteAction__Output } from '../../../../envoy/config/route/v3/RouteAction'; +import type { RedirectAction as _envoy_config_route_v3_RedirectAction, RedirectAction__Output as _envoy_config_route_v3_RedirectAction__Output } from '../../../../envoy/config/route/v3/RedirectAction'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { Decorator as _envoy_config_route_v3_Decorator, Decorator__Output as _envoy_config_route_v3_Decorator__Output } from '../../../../envoy/config/route/v3/Decorator'; +import type { DirectResponseAction as _envoy_config_route_v3_DirectResponseAction, DirectResponseAction__Output as _envoy_config_route_v3_DirectResponseAction__Output } from '../../../../envoy/config/route/v3/DirectResponseAction'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -import type { Tracing as _envoy_api_v2_route_Tracing, Tracing__Output as _envoy_api_v2_route_Tracing__Output } from '../../../../envoy/api/v2/route/Tracing'; +import type { Tracing as _envoy_config_route_v3_Tracing, Tracing__Output as _envoy_config_route_v3_Tracing__Output } from '../../../../envoy/config/route/v3/Tracing'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -import type { FilterAction as _envoy_api_v2_route_FilterAction, FilterAction__Output as _envoy_api_v2_route_FilterAction__Output } from '../../../../envoy/api/v2/route/FilterAction'; +import type { FilterAction as _envoy_config_route_v3_FilterAction, FilterAction__Output as _envoy_config_route_v3_FilterAction__Output } from '../../../../envoy/config/route/v3/FilterAction'; /** * A route is both a specification of how to match a request as well as an indication of what to do @@ -20,22 +19,22 @@ import type { FilterAction as _envoy_api_v2_route_FilterAction, FilterAction__Ou * .. attention:: * * Envoy supports routing on HTTP method via :ref:`header matching - * `. + * `. * [#next-free-field: 18] */ export interface Route { /** * Route matching parameters. */ - 'match'?: (_envoy_api_v2_route_RouteMatch); + 'match'?: (_envoy_config_route_v3_RouteMatch); /** * Route request to some upstream cluster. */ - 'route'?: (_envoy_api_v2_route_RouteAction); + 'route'?: (_envoy_config_route_v3_RouteAction); /** * Return a redirect. */ - 'redirect'?: (_envoy_api_v2_route_RedirectAction); + 'redirect'?: (_envoy_config_route_v3_RedirectAction); /** * The Metadata field can be used to provide additional information * about the route. It can be used for configuration, stats, and logging. @@ -43,41 +42,33 @@ export interface Route { * For instance, if the metadata is intended for the Router filter, * the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_api_v2_core_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata); /** * Decorator for the matched route. */ - 'decorator'?: (_envoy_api_v2_route_Decorator); + 'decorator'?: (_envoy_config_route_v3_Decorator); /** * Return an arbitrary HTTP response directly, without proxying. */ - 'direct_response'?: (_envoy_api_v2_route_DirectResponseAction); - /** - * The per_filter_config field can be used to provide route-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` for - * if and how it is utilized. - */ - 'per_filter_config'?: ({[key: string]: _google_protobuf_Struct}); + 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction); /** * Specifies a set of headers that will be added to requests matching this * route. Headers specified at this level are applied before headers from the - * enclosing :ref:`envoy_api_msg_route.VirtualHost` and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + * enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a set of headers that will be added to responses to requests * matching this route. Headers specified at this level are applied before - * headers from the enclosing :ref:`envoy_api_msg_route.VirtualHost` and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + * headers from the enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on * :ref:`custom request headers `. */ - 'response_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of HTTP headers that should be removed from each response * to requests matching this route. @@ -94,6 +85,9 @@ export interface Route { * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter * specific; see the :ref:`HTTP filter documentation ` for * if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); /** @@ -104,7 +98,7 @@ export interface Route { * Presence of the object defines whether the connection manager's tracing configuration * is overridden by this route specific instance. */ - 'tracing'?: (_envoy_api_v2_route_Tracing); + 'tracing'?: (_envoy_config_route_v3_Tracing); /** * The maximum bytes which will be buffered for retries and shadowing. * If set, the bytes actually buffered will be the minimum value of this and the @@ -115,8 +109,10 @@ export interface Route { * [#not-implemented-hide:] * If true, a filter will define the action (e.g., it could dynamically generate the * RouteAction). + * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when + * implemented] */ - 'filter_action'?: (_envoy_api_v2_route_FilterAction); + 'filter_action'?: (_envoy_config_route_v3_FilterAction); 'action'?: "route"|"redirect"|"direct_response"|"filter_action"; } @@ -127,22 +123,22 @@ export interface Route { * .. attention:: * * Envoy supports routing on HTTP method via :ref:`header matching - * `. + * `. * [#next-free-field: 18] */ export interface Route__Output { /** * Route matching parameters. */ - 'match'?: (_envoy_api_v2_route_RouteMatch__Output); + 'match'?: (_envoy_config_route_v3_RouteMatch__Output); /** * Route request to some upstream cluster. */ - 'route'?: (_envoy_api_v2_route_RouteAction__Output); + 'route'?: (_envoy_config_route_v3_RouteAction__Output); /** * Return a redirect. */ - 'redirect'?: (_envoy_api_v2_route_RedirectAction__Output); + 'redirect'?: (_envoy_config_route_v3_RedirectAction__Output); /** * The Metadata field can be used to provide additional information * about the route. It can be used for configuration, stats, and logging. @@ -150,41 +146,33 @@ export interface Route__Output { * For instance, if the metadata is intended for the Router filter, * the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_api_v2_core_Metadata__Output); + 'metadata'?: (_envoy_config_core_v3_Metadata__Output); /** * Decorator for the matched route. */ - 'decorator'?: (_envoy_api_v2_route_Decorator__Output); + 'decorator'?: (_envoy_config_route_v3_Decorator__Output); /** * Return an arbitrary HTTP response directly, without proxying. */ - 'direct_response'?: (_envoy_api_v2_route_DirectResponseAction__Output); - /** - * The per_filter_config field can be used to provide route-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` for - * if and how it is utilized. - */ - 'per_filter_config'?: ({[key: string]: _google_protobuf_Struct__Output}); + 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction__Output); /** * Specifies a set of headers that will be added to requests matching this * route. Headers specified at this level are applied before headers from the - * enclosing :ref:`envoy_api_msg_route.VirtualHost` and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + * enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a set of headers that will be added to responses to requests * matching this route. Headers specified at this level are applied before - * headers from the enclosing :ref:`envoy_api_msg_route.VirtualHost` and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + * headers from the enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on * :ref:`custom request headers `. */ - 'response_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of HTTP headers that should be removed from each response * to requests matching this route. @@ -201,6 +189,9 @@ export interface Route__Output { * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter * specific; see the :ref:`HTTP filter documentation ` for * if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any__Output}); /** @@ -211,7 +202,7 @@ export interface Route__Output { * Presence of the object defines whether the connection manager's tracing configuration * is overridden by this route specific instance. */ - 'tracing'?: (_envoy_api_v2_route_Tracing__Output); + 'tracing'?: (_envoy_config_route_v3_Tracing__Output); /** * The maximum bytes which will be buffered for retries and shadowing. * If set, the bytes actually buffered will be the minimum value of this and the @@ -222,7 +213,9 @@ export interface Route__Output { * [#not-implemented-hide:] * If true, a filter will define the action (e.g., it could dynamically generate the * RouteAction). + * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when + * implemented] */ - 'filter_action'?: (_envoy_api_v2_route_FilterAction__Output); + 'filter_action'?: (_envoy_config_route_v3_FilterAction__Output); 'action': "route"|"redirect"|"direct_response"|"filter_action"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RouteAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts similarity index 62% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/RouteAction.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts index 5f07bae9b..797d6eda6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RouteAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts @@ -1,22 +1,24 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -import type { WeightedCluster as _envoy_api_v2_route_WeightedCluster, WeightedCluster__Output as _envoy_api_v2_route_WeightedCluster__Output } from '../../../../envoy/api/v2/route/WeightedCluster'; -import type { Metadata as _envoy_api_v2_core_Metadata, Metadata__Output as _envoy_api_v2_core_Metadata__Output } from '../../../../envoy/api/v2/core/Metadata'; +import type { WeightedCluster as _envoy_config_route_v3_WeightedCluster, WeightedCluster__Output as _envoy_config_route_v3_WeightedCluster__Output } from '../../../../envoy/config/route/v3/WeightedCluster'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { RetryPolicy as _envoy_api_v2_route_RetryPolicy, RetryPolicy__Output as _envoy_api_v2_route_RetryPolicy__Output } from '../../../../envoy/api/v2/route/RetryPolicy'; -import type { RoutingPriority as _envoy_api_v2_core_RoutingPriority } from '../../../../envoy/api/v2/core/RoutingPriority'; -import type { RateLimit as _envoy_api_v2_route_RateLimit, RateLimit__Output as _envoy_api_v2_route_RateLimit__Output } from '../../../../envoy/api/v2/route/RateLimit'; -import type { CorsPolicy as _envoy_api_v2_route_CorsPolicy, CorsPolicy__Output as _envoy_api_v2_route_CorsPolicy__Output } from '../../../../envoy/api/v2/route/CorsPolicy'; -import type { HedgePolicy as _envoy_api_v2_route_HedgePolicy, HedgePolicy__Output as _envoy_api_v2_route_HedgePolicy__Output } from '../../../../envoy/api/v2/route/HedgePolicy'; +import type { RetryPolicy as _envoy_config_route_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_route_v3_RetryPolicy__Output } from '../../../../envoy/config/route/v3/RetryPolicy'; +import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority } from '../../../../envoy/config/core/v3/RoutingPriority'; +import type { RateLimit as _envoy_config_route_v3_RateLimit, RateLimit__Output as _envoy_config_route_v3_RateLimit__Output } from '../../../../envoy/config/route/v3/RateLimit'; +import type { CorsPolicy as _envoy_config_route_v3_CorsPolicy, CorsPolicy__Output as _envoy_config_route_v3_CorsPolicy__Output } from '../../../../envoy/config/route/v3/CorsPolicy'; +import type { HedgePolicy as _envoy_config_route_v3_HedgePolicy, HedgePolicy__Output as _envoy_config_route_v3_HedgePolicy__Output } from '../../../../envoy/config/route/v3/HedgePolicy'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -import type { RegexMatchAndSubstitute as _envoy_type_matcher_RegexMatchAndSubstitute, RegexMatchAndSubstitute__Output as _envoy_type_matcher_RegexMatchAndSubstitute__Output } from '../../../../envoy/type/matcher/RegexMatchAndSubstitute'; +import type { RegexMatchAndSubstitute as _envoy_type_matcher_v3_RegexMatchAndSubstitute, RegexMatchAndSubstitute__Output as _envoy_type_matcher_v3_RegexMatchAndSubstitute__Output } from '../../../../envoy/type/matcher/v3/RegexMatchAndSubstitute'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -import type { RuntimeFractionalPercent as _envoy_api_v2_core_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_api_v2_core_RuntimeFractionalPercent__Output } from '../../../../envoy/api/v2/core/RuntimeFractionalPercent'; +import type { InternalRedirectPolicy as _envoy_config_route_v3_InternalRedirectPolicy, InternalRedirectPolicy__Output as _envoy_config_route_v3_InternalRedirectPolicy__Output } from '../../../../envoy/config/route/v3/InternalRedirectPolicy'; +import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; +import type { ProxyProtocolConfig as _envoy_config_core_v3_ProxyProtocolConfig, ProxyProtocolConfig__Output as _envoy_config_core_v3_ProxyProtocolConfig__Output } from '../../../../envoy/config/core/v3/ProxyProtocolConfig'; -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -export enum _envoy_api_v2_route_RouteAction_ClusterNotFoundResponseCode { +export enum _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode { /** * HTTP status code - 503 Service Unavailable. */ @@ -27,14 +29,44 @@ export enum _envoy_api_v2_route_RouteAction_ClusterNotFoundResponseCode { NOT_FOUND = 1, } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_ConnectionProperties { +/** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT or POST requests, when forwarding request payload as raw TCP. + */ +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig { + /** + * If present, the proxy protocol header will be prepended to the CONNECT payload sent upstream. + */ + 'proxy_protocol_config'?: (_envoy_config_core_v3_ProxyProtocolConfig); + /** + * If set, the route will also allow forwarding POST payload as raw TCP. + */ + 'allow_post'?: (boolean); +} + +/** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT or POST requests, when forwarding request payload as raw TCP. + */ +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig__Output { + /** + * If present, the proxy protocol header will be prepended to the CONNECT payload sent upstream. + */ + 'proxy_protocol_config'?: (_envoy_config_core_v3_ProxyProtocolConfig__Output); + /** + * If set, the route will also allow forwarding POST payload as raw TCP. + */ + 'allow_post': (boolean); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties { /** * Hash on source IP address. */ 'source_ip'?: (boolean); } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_ConnectionProperties__Output { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties__Output { /** * Hash on source IP address. */ @@ -57,7 +89,7 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy_ConnectionProperties * streams on the same connection will independently receive the same * cookie, even if they arrive at the Envoy simultaneously. */ -export interface _envoy_api_v2_route_RouteAction_HashPolicy_Cookie { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Cookie { /** * The name of the cookie that will be used to obtain the hash key. If the * cookie is not present and ttl below is not set, no hash will be @@ -93,7 +125,7 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy_Cookie { * streams on the same connection will independently receive the same * cookie, even if they arrive at the Envoy simultaneously. */ -export interface _envoy_api_v2_route_RouteAction_HashPolicy_Cookie__Output { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output { /** * The name of the cookie that will be used to obtain the hash key. If the * cookie is not present and ttl below is not set, no hash will be @@ -113,7 +145,7 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy_Cookie__Output { 'path': (string); } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_FilterState { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_FilterState { /** * The name of the Object in the per-request filterState, which is an * Envoy::Http::Hashable object. If there is no data associated with the key, @@ -122,7 +154,7 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy_FilterState { 'key'?: (string); } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_FilterState__Output { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_FilterState__Output { /** * The name of the Object in the per-request filterState, which is an * Envoy::Http::Hashable object. If there is no data associated with the key, @@ -136,27 +168,27 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy_FilterState__Output * `. * [#next-free-field: 7] */ -export interface _envoy_api_v2_route_RouteAction_HashPolicy { +export interface _envoy_config_route_v3_RouteAction_HashPolicy { /** * Header hash policy. */ - 'header'?: (_envoy_api_v2_route_RouteAction_HashPolicy_Header); + 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header); /** * Cookie hash policy. */ - 'cookie'?: (_envoy_api_v2_route_RouteAction_HashPolicy_Cookie); + 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie); /** * Connection properties hash policy. */ - 'connection_properties'?: (_envoy_api_v2_route_RouteAction_HashPolicy_ConnectionProperties); + 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties); /** * Query parameter hash policy. */ - 'query_parameter'?: (_envoy_api_v2_route_RouteAction_HashPolicy_QueryParameter); + 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter); /** * Filter state hash policy. */ - 'filter_state'?: (_envoy_api_v2_route_RouteAction_HashPolicy_FilterState); + 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState); /** * The flag that short-circuits the hash computing. This field provides a * 'fallback' style of configuration: "if a terminal policy doesn't work, @@ -187,27 +219,27 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy { * `. * [#next-free-field: 7] */ -export interface _envoy_api_v2_route_RouteAction_HashPolicy__Output { +export interface _envoy_config_route_v3_RouteAction_HashPolicy__Output { /** * Header hash policy. */ - 'header'?: (_envoy_api_v2_route_RouteAction_HashPolicy_Header__Output); + 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header__Output); /** * Cookie hash policy. */ - 'cookie'?: (_envoy_api_v2_route_RouteAction_HashPolicy_Cookie__Output); + 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output); /** * Connection properties hash policy. */ - 'connection_properties'?: (_envoy_api_v2_route_RouteAction_HashPolicy_ConnectionProperties__Output); + 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties__Output); /** * Query parameter hash policy. */ - 'query_parameter'?: (_envoy_api_v2_route_RouteAction_HashPolicy_QueryParameter__Output); + 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter__Output); /** * Filter state hash policy. */ - 'filter_state'?: (_envoy_api_v2_route_RouteAction_HashPolicy_FilterState__Output); + 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState__Output); /** * The flag that short-circuits the hash computing. This field provides a * 'fallback' style of configuration: "if a terminal policy doesn't work, @@ -233,33 +265,104 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy__Output { 'policy_specifier': "header"|"cookie"|"connection_properties"|"query_parameter"|"filter_state"; } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_Header { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header { /** * The name of the request header that will be used to obtain the hash * key. If the request header is not present, no hash will be produced. */ 'header_name'?: (string); + /** + * If specified, the request header value will be rewritten and used + * to produce the hash key. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_Header__Output { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header__Output { /** * The name of the request header that will be used to obtain the hash * key. If the request header is not present, no hash will be produced. */ 'header_name': (string); + /** + * If specified, the request header value will be rewritten and used + * to produce the hash key. + */ + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); } -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto /** * Configures :ref:`internal redirect ` behavior. + * [#next-major-version: remove this definition - it's defined in the InternalRedirectPolicy message.] */ -export enum _envoy_api_v2_route_RouteAction_InternalRedirectAction { +export enum _envoy_config_route_v3_RouteAction_InternalRedirectAction { PASS_THROUGH_INTERNAL_REDIRECT = 0, HANDLE_INTERNAL_REDIRECT = 1, } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_QueryParameter { +export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration { + /** + * Specifies the maximum duration allowed for streams on the route. If not specified, the value + * from the :ref:`max_stream_duration + * ` field in + * :ref:`HttpConnectionManager.common_http_protocol_options + * ` + * is used. If this field is set explicitly to zero, any + * HttpConnectionManager max_stream_duration timeout will be disabled for + * this route. + */ + 'max_stream_duration'?: (_google_protobuf_Duration); + /** + * If present, and the request contains a `grpc-timeout header + * `_, use that value as the + * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. + * If set to 0, the `grpc-timeout` header is used without modification. + */ + 'grpc_timeout_header_max'?: (_google_protobuf_Duration); + /** + * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by + * subtracting the provided duration from the header. This is useful for allowing Envoy to set + * its global timeout to be less than that of the deadline imposed by the calling client, which + * makes it more likely that Envoy will handle the timeout instead of having the call canceled + * by the client. If, after applying the offset, the resulting timeout is zero or negative, + * the stream will timeout immediately. + */ + 'grpc_timeout_header_offset'?: (_google_protobuf_Duration); +} + +export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration__Output { + /** + * Specifies the maximum duration allowed for streams on the route. If not specified, the value + * from the :ref:`max_stream_duration + * ` field in + * :ref:`HttpConnectionManager.common_http_protocol_options + * ` + * is used. If this field is set explicitly to zero, any + * HttpConnectionManager max_stream_duration timeout will be disabled for + * this route. + */ + 'max_stream_duration'?: (_google_protobuf_Duration__Output); + /** + * If present, and the request contains a `grpc-timeout header + * `_, use that value as the + * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. + * If set to 0, the `grpc-timeout` header is used without modification. + */ + 'grpc_timeout_header_max'?: (_google_protobuf_Duration__Output); + /** + * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by + * subtracting the provided duration from the header. This is useful for allowing Envoy to set + * its global timeout to be less than that of the deadline imposed by the calling client, which + * makes it more likely that Envoy will handle the timeout instead of having the call canceled + * by the client. If, after applying the offset, the resulting timeout is zero or negative, + * the stream will timeout immediately. + */ + 'grpc_timeout_header_offset'?: (_google_protobuf_Duration__Output); +} + +export interface _envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter { /** * The name of the URL query parameter that will be used to obtain the hash * key. If the parameter is not present, no hash will be produced. Query @@ -268,7 +371,7 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy_QueryParameter { 'name'?: (string); } -export interface _envoy_api_v2_route_RouteAction_HashPolicy_QueryParameter__Output { +export interface _envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter__Output { /** * The name of the URL query parameter that will be used to obtain the hash * key. If the parameter is not present, no hash will be produced. Query @@ -290,30 +393,12 @@ export interface _envoy_api_v2_route_RouteAction_HashPolicy_QueryParameter__Outp * * Shadowing will not be triggered if the primary cluster does not exist. */ -export interface _envoy_api_v2_route_RouteAction_RequestMirrorPolicy { +export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy { /** * Specifies the cluster that requests will be mirrored to. The cluster must * exist in the cluster manager configuration. */ 'cluster'?: (string); - /** - * If not specified, all requests to the target cluster will be mirrored. If - * specified, Envoy will lookup the runtime key to get the % of requests to - * mirror. Valid values are from 0 to 10000, allowing for increments of - * 0.01% of requests to be mirrored. If the runtime key is specified in the - * configuration but not present in runtime, 0 is the default and thus 0% of - * requests will be mirrored. - * - * .. attention:: - * - * **This field is deprecated**. Set the - * :ref:`runtime_fraction - * ` - * field instead. Mirroring occurs if both this and - * ` - * are not set. - */ - 'runtime_key'?: (string); /** * If not specified, all requests to the target cluster will be mirrored. * @@ -324,7 +409,7 @@ export interface _envoy_api_v2_route_RouteAction_RequestMirrorPolicy { * number is <= the value of the numerator N, or if the key is not present, the default * value, the request will be mirrored. */ - 'runtime_fraction'?: (_envoy_api_v2_core_RuntimeFractionalPercent); + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent); /** * Determines if the trace span should be sampled. Defaults to true. */ @@ -344,30 +429,12 @@ export interface _envoy_api_v2_route_RouteAction_RequestMirrorPolicy { * * Shadowing will not be triggered if the primary cluster does not exist. */ -export interface _envoy_api_v2_route_RouteAction_RequestMirrorPolicy__Output { +export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output { /** * Specifies the cluster that requests will be mirrored to. The cluster must * exist in the cluster manager configuration. */ 'cluster': (string); - /** - * If not specified, all requests to the target cluster will be mirrored. If - * specified, Envoy will lookup the runtime key to get the % of requests to - * mirror. Valid values are from 0 to 10000, allowing for increments of - * 0.01% of requests to be mirrored. If the runtime key is specified in the - * configuration but not present in runtime, 0 is the default and thus 0% of - * requests will be mirrored. - * - * .. attention:: - * - * **This field is deprecated**. Set the - * :ref:`runtime_fraction - * ` - * field instead. Mirroring occurs if both this and - * ` - * are not set. - */ - 'runtime_key': (string); /** * If not specified, all requests to the target cluster will be mirrored. * @@ -378,7 +445,7 @@ export interface _envoy_api_v2_route_RouteAction_RequestMirrorPolicy__Output { * number is <= the value of the numerator N, or if the key is not present, the default * value, the request will be mirrored. */ - 'runtime_fraction'?: (_envoy_api_v2_core_RuntimeFractionalPercent__Output); + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); /** * Determines if the trace span should be sampled. Defaults to true. */ @@ -390,10 +457,10 @@ export interface _envoy_api_v2_route_RouteAction_RequestMirrorPolicy__Output { * This overrides any enabled/disabled upgrade filter chain specified in the * HttpConnectionManager * :ref:`upgrade_configs - * ` + * ` * but does not affect any custom filter chain specified there. */ -export interface _envoy_api_v2_route_RouteAction_UpgradeConfig { +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig { /** * The case-insensitive name of this upgrade, e.g. "websocket". * For each upgrade type present in upgrade_configs, requests with @@ -404,6 +471,13 @@ export interface _envoy_api_v2_route_RouteAction_UpgradeConfig { * Determines if upgrades are available on this route. Defaults to true. */ 'enabled'?: (_google_protobuf_BoolValue); + /** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT requests, when forwarding CONNECT payload as raw TCP. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + */ + 'connect_config'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig); } /** @@ -411,10 +485,10 @@ export interface _envoy_api_v2_route_RouteAction_UpgradeConfig { * This overrides any enabled/disabled upgrade filter chain specified in the * HttpConnectionManager * :ref:`upgrade_configs - * ` + * ` * but does not affect any custom filter chain specified there. */ -export interface _envoy_api_v2_route_RouteAction_UpgradeConfig__Output { +export interface _envoy_config_route_v3_RouteAction_UpgradeConfig__Output { /** * The case-insensitive name of this upgrade, e.g. "websocket". * For each upgrade type present in upgrade_configs, requests with @@ -425,10 +499,17 @@ export interface _envoy_api_v2_route_RouteAction_UpgradeConfig__Output { * Determines if upgrades are available on this route. Defaults to true. */ 'enabled'?: (_google_protobuf_BoolValue__Output); + /** + * Configuration for sending data upstream as a raw data payload. This is used for + * CONNECT requests, when forwarding CONNECT payload as raw TCP. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + */ + 'connect_config'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig__Output); } /** - * [#next-free-field: 34] + * [#next-free-field: 37] */ export interface RouteAction { /** @@ -446,6 +527,10 @@ export interface RouteAction { * * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ 'cluster_header'?: (string); /** @@ -455,15 +540,15 @@ export interface RouteAction { * :ref:`traffic splitting ` * for additional documentation. */ - 'weighted_clusters'?: (_envoy_api_v2_route_WeightedCluster); + 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster); /** * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints * in the upstream cluster with metadata matching what's set in this field will be considered * for load balancing. If using :ref:`weighted_clusters - * `, metadata will be merged, with values + * `, metadata will be merged, with values * provided there taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_api_v2_core_Metadata); + 'metadata_match'?: (_envoy_config_core_v3_Metadata); /** * Indicates that during forwarding, the matched prefix (or path) should be * swapped with this value. This option allows application URLs to be rooted @@ -472,16 +557,16 @@ export interface RouteAction { * ` header. * * Only one of *prefix_rewrite* or - * :ref:`regex_rewrite ` + * :ref:`regex_rewrite ` * may be specified. * * .. attention:: * * Pay careful attention to the use of trailing slashes in the - * :ref:`route's match ` prefix value. + * :ref:`route's match ` prefix value. * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single - * :ref:`Route `, as shown by the below config entries: + * :ref:`Route `, as shown by the below config entries: * * .. code-block:: yaml * @@ -502,7 +587,7 @@ export interface RouteAction { * Indicates that during forwarding, the host header will be swapped with * this value. */ - 'host_rewrite'?: (string); + 'host_rewrite_literal'?: (string); /** * Indicates that during forwarding, the host header will be swapped with * the hostname of the upstream host chosen by the cluster manager. This @@ -530,29 +615,23 @@ export interface RouteAction { * it'll take precedence over the virtual host level retry policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'retry_policy'?: (_envoy_api_v2_route_RetryPolicy); - /** - * Indicates that the route has a request mirroring policy. - * - * .. attention:: - * This field has been deprecated in favor of `request_mirror_policies` which supports one or - * more mirroring policies. - */ - 'request_mirror_policy'?: (_envoy_api_v2_route_RouteAction_RequestMirrorPolicy); + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy); /** * Optionally specifies the :ref:`routing priority `. */ - 'priority'?: (_envoy_api_v2_core_RoutingPriority | keyof typeof _envoy_api_v2_core_RoutingPriority); + 'priority'?: (_envoy_config_core_v3_RoutingPriority | keyof typeof _envoy_config_core_v3_RoutingPriority); /** * Specifies a set of rate limit configurations that could be applied to the * route. */ - 'rate_limits'?: (_envoy_api_v2_route_RateLimit)[]; + 'rate_limits'?: (_envoy_config_route_v3_RateLimit)[]; /** * Specifies if the rate limit filter should include the virtual host rate * limits. By default, if the route configured rate limits, the virtual host - * :ref:`rate_limits ` are not applied to the + * :ref:`rate_limits ` are not applied to the * request. + * + * This field is deprecated. Please use :ref:`vh_rate_limits ` */ 'include_vh_rate_limits'?: (_google_protobuf_BoolValue); /** @@ -569,25 +648,26 @@ export interface RouteAction { * there is already a hash generated, the hash is returned immediately, * ignoring the rest of the hash policy list. */ - 'hash_policy'?: (_envoy_api_v2_route_RouteAction_HashPolicy)[]; + 'hash_policy'?: (_envoy_config_route_v3_RouteAction_HashPolicy)[]; /** * Indicates that the route has a CORS policy. */ - 'cors'?: (_envoy_api_v2_route_CorsPolicy); + 'cors'?: (_envoy_config_route_v3_CorsPolicy); /** * The HTTP status code to use when configured cluster is not found. * The default response code is 503 Service Unavailable. */ - 'cluster_not_found_response_code'?: (_envoy_api_v2_route_RouteAction_ClusterNotFoundResponseCode | keyof typeof _envoy_api_v2_route_RouteAction_ClusterNotFoundResponseCode); + 'cluster_not_found_response_code'?: (_envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode | keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); /** + * Deprecated by :ref:`grpc_timeout_header_max ` * If present, and the request is a gRPC request, use the * `grpc-timeout header `_, * or its default value (infinity) instead of - * :ref:`timeout `, but limit the applied timeout + * :ref:`timeout `, but limit the applied timeout * to the maximum value specified here. If configured as 0, the maximum allowed timeout for * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used * and gRPC requests time out like any other requests using - * :ref:`timeout ` or its default. + * :ref:`timeout ` or its default. * This can be used to prevent unexpected upstream request timeouts due to potentially long * time gaps between gRPC request and response in gRPC streaming mode. * @@ -604,14 +684,14 @@ export interface RouteAction { /** * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, * although the connection manager wide :ref:`stream_idle_timeout - * ` + * ` * will still apply. A value of 0 will completely disable the route's idle timeout, even if a * connection manager stream idle timeout is configured. * * The idle timeout is distinct to :ref:`timeout - * `, which provides an upper bound + * `, which provides an upper bound * on the upstream response time; :ref:`idle_timeout - * ` instead bounds the amount + * ` instead bounds the amount * of time the request's stream may be idle. * * After header decoding, the idle timeout will apply on downstream and @@ -620,17 +700,22 @@ export interface RouteAction { * fires, the stream is terminated with a 408 Request Timeout error code if no * upstream response header has been received, otherwise a stream reset * occurs. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. */ 'idle_timeout'?: (_google_protobuf_Duration); - 'upgrade_configs'?: (_envoy_api_v2_route_RouteAction_UpgradeConfig)[]; - 'internal_redirect_action'?: (_envoy_api_v2_route_RouteAction_InternalRedirectAction | keyof typeof _envoy_api_v2_route_RouteAction_InternalRedirectAction); + 'upgrade_configs'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig)[]; + 'internal_redirect_action'?: (_envoy_config_route_v3_RouteAction_InternalRedirectAction | keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); /** * Indicates that the route has a hedge policy. Note that if this is set, * it'll take precedence over the virtual host level hedge policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'hedge_policy'?: (_envoy_api_v2_route_HedgePolicy); + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy); /** + * Deprecated by :ref:`grpc_timeout_header_offset `. * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting * the provided duration from the header. This is useful in allowing Envoy to set its global * timeout to be less than that of the deadline imposed by the calling client, which makes it more @@ -649,24 +734,28 @@ export interface RouteAction { * * Pay attention to the potential security implications of using this option. Provided header * must come from trusted source. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ - 'auto_host_rewrite_header'?: (string); + 'host_rewrite_header'?: (string); /** * Indicates that the route has request mirroring policies. */ - 'request_mirror_policies'?: (_envoy_api_v2_route_RouteAction_RequestMirrorPolicy)[]; + 'request_mirror_policies'?: (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy)[]; /** * An internal redirect is handled, iff the number of previous internal redirects that a * downstream request has encountered is lower than this value, and - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * is set to :ref:`HANDLE_INTERNAL_REDIRECT - * ` + * ` * In the case where a downstream request is bounced among multiple routes by internal redirect, * the first route that hits this threshold, or has - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * set to * :ref:`PASS_THROUGH_INTERNAL_REDIRECT - * ` + * ` * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. @@ -682,7 +771,7 @@ export interface RouteAction { * before the rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of :ref:`prefix_rewrite ` + * Only one of :ref:`prefix_rewrite ` * or *regex_rewrite* may be specified. * * Examples using Google's `RE2 `_ engine: @@ -702,21 +791,50 @@ export interface RouteAction { * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to * ``/aaa/yyy/bbb``. */ - 'regex_rewrite'?: (_envoy_type_matcher_RegexMatchAndSubstitute); + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that if this is set, it'll take * precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - * most internal one becomes the enforced policy). :ref:`Retry policy ` + * most internal one becomes the enforced policy). :ref:`Retry policy ` * should not be set if this field is used. */ 'retry_policy_typed_config'?: (_google_protobuf_Any); + /** + * If present, Envoy will try to follow an upstream redirect response instead of proxying the + * response back to the downstream. An upstream redirect response is defined + * by :ref:`redirect_response_codes + * `. + */ + 'internal_redirect_policy'?: (_envoy_config_route_v3_InternalRedirectPolicy); + /** + * Indicates that during forwarding, the host header will be swapped with + * the result of the regex substitution executed on path value with query and fragment removed. + * This is useful for transitioning variable content between path segment and subdomain. + * + * For example with the following config: + * + * .. code-block:: yaml + * + * host_rewrite_path_regex: + * pattern: + * google_re2: {} + * regex: "^/(.+)/.+$" + * substitution: \1 + * + * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. + */ + 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); + /** + * Specifies the maximum stream duration for this route. + */ + 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration); 'cluster_specifier'?: "cluster"|"cluster_header"|"weighted_clusters"; - 'host_rewrite_specifier'?: "host_rewrite"|"auto_host_rewrite"|"auto_host_rewrite_header"; + 'host_rewrite_specifier'?: "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } /** - * [#next-free-field: 34] + * [#next-free-field: 37] */ export interface RouteAction__Output { /** @@ -734,6 +852,10 @@ export interface RouteAction__Output { * * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ 'cluster_header'?: (string); /** @@ -743,15 +865,15 @@ export interface RouteAction__Output { * :ref:`traffic splitting ` * for additional documentation. */ - 'weighted_clusters'?: (_envoy_api_v2_route_WeightedCluster__Output); + 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster__Output); /** * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints * in the upstream cluster with metadata matching what's set in this field will be considered * for load balancing. If using :ref:`weighted_clusters - * `, metadata will be merged, with values + * `, metadata will be merged, with values * provided there taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_api_v2_core_Metadata__Output); + 'metadata_match'?: (_envoy_config_core_v3_Metadata__Output); /** * Indicates that during forwarding, the matched prefix (or path) should be * swapped with this value. This option allows application URLs to be rooted @@ -760,16 +882,16 @@ export interface RouteAction__Output { * ` header. * * Only one of *prefix_rewrite* or - * :ref:`regex_rewrite ` + * :ref:`regex_rewrite ` * may be specified. * * .. attention:: * * Pay careful attention to the use of trailing slashes in the - * :ref:`route's match ` prefix value. + * :ref:`route's match ` prefix value. * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single - * :ref:`Route `, as shown by the below config entries: + * :ref:`Route `, as shown by the below config entries: * * .. code-block:: yaml * @@ -790,7 +912,7 @@ export interface RouteAction__Output { * Indicates that during forwarding, the host header will be swapped with * this value. */ - 'host_rewrite'?: (string); + 'host_rewrite_literal'?: (string); /** * Indicates that during forwarding, the host header will be swapped with * the hostname of the upstream host chosen by the cluster manager. This @@ -818,29 +940,23 @@ export interface RouteAction__Output { * it'll take precedence over the virtual host level retry policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'retry_policy'?: (_envoy_api_v2_route_RetryPolicy__Output); - /** - * Indicates that the route has a request mirroring policy. - * - * .. attention:: - * This field has been deprecated in favor of `request_mirror_policies` which supports one or - * more mirroring policies. - */ - 'request_mirror_policy'?: (_envoy_api_v2_route_RouteAction_RequestMirrorPolicy__Output); + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy__Output); /** * Optionally specifies the :ref:`routing priority `. */ - 'priority': (keyof typeof _envoy_api_v2_core_RoutingPriority); + 'priority': (keyof typeof _envoy_config_core_v3_RoutingPriority); /** * Specifies a set of rate limit configurations that could be applied to the * route. */ - 'rate_limits': (_envoy_api_v2_route_RateLimit__Output)[]; + 'rate_limits': (_envoy_config_route_v3_RateLimit__Output)[]; /** * Specifies if the rate limit filter should include the virtual host rate * limits. By default, if the route configured rate limits, the virtual host - * :ref:`rate_limits ` are not applied to the + * :ref:`rate_limits ` are not applied to the * request. + * + * This field is deprecated. Please use :ref:`vh_rate_limits ` */ 'include_vh_rate_limits'?: (_google_protobuf_BoolValue__Output); /** @@ -857,25 +973,26 @@ export interface RouteAction__Output { * there is already a hash generated, the hash is returned immediately, * ignoring the rest of the hash policy list. */ - 'hash_policy': (_envoy_api_v2_route_RouteAction_HashPolicy__Output)[]; + 'hash_policy': (_envoy_config_route_v3_RouteAction_HashPolicy__Output)[]; /** * Indicates that the route has a CORS policy. */ - 'cors'?: (_envoy_api_v2_route_CorsPolicy__Output); + 'cors'?: (_envoy_config_route_v3_CorsPolicy__Output); /** * The HTTP status code to use when configured cluster is not found. * The default response code is 503 Service Unavailable. */ - 'cluster_not_found_response_code': (keyof typeof _envoy_api_v2_route_RouteAction_ClusterNotFoundResponseCode); + 'cluster_not_found_response_code': (keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); /** + * Deprecated by :ref:`grpc_timeout_header_max ` * If present, and the request is a gRPC request, use the * `grpc-timeout header `_, * or its default value (infinity) instead of - * :ref:`timeout `, but limit the applied timeout + * :ref:`timeout `, but limit the applied timeout * to the maximum value specified here. If configured as 0, the maximum allowed timeout for * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used * and gRPC requests time out like any other requests using - * :ref:`timeout ` or its default. + * :ref:`timeout ` or its default. * This can be used to prevent unexpected upstream request timeouts due to potentially long * time gaps between gRPC request and response in gRPC streaming mode. * @@ -892,14 +1009,14 @@ export interface RouteAction__Output { /** * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, * although the connection manager wide :ref:`stream_idle_timeout - * ` + * ` * will still apply. A value of 0 will completely disable the route's idle timeout, even if a * connection manager stream idle timeout is configured. * * The idle timeout is distinct to :ref:`timeout - * `, which provides an upper bound + * `, which provides an upper bound * on the upstream response time; :ref:`idle_timeout - * ` instead bounds the amount + * ` instead bounds the amount * of time the request's stream may be idle. * * After header decoding, the idle timeout will apply on downstream and @@ -908,17 +1025,22 @@ export interface RouteAction__Output { * fires, the stream is terminated with a 408 Request Timeout error code if no * upstream response header has been received, otherwise a stream reset * occurs. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. */ 'idle_timeout'?: (_google_protobuf_Duration__Output); - 'upgrade_configs': (_envoy_api_v2_route_RouteAction_UpgradeConfig__Output)[]; - 'internal_redirect_action': (keyof typeof _envoy_api_v2_route_RouteAction_InternalRedirectAction); + 'upgrade_configs': (_envoy_config_route_v3_RouteAction_UpgradeConfig__Output)[]; + 'internal_redirect_action': (keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); /** * Indicates that the route has a hedge policy. Note that if this is set, * it'll take precedence over the virtual host level hedge policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'hedge_policy'?: (_envoy_api_v2_route_HedgePolicy__Output); + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy__Output); /** + * Deprecated by :ref:`grpc_timeout_header_offset `. * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting * the provided duration from the header. This is useful in allowing Envoy to set its global * timeout to be less than that of the deadline imposed by the calling client, which makes it more @@ -937,24 +1059,28 @@ export interface RouteAction__Output { * * Pay attention to the potential security implications of using this option. Provided header * must come from trusted source. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ - 'auto_host_rewrite_header'?: (string); + 'host_rewrite_header'?: (string); /** * Indicates that the route has request mirroring policies. */ - 'request_mirror_policies': (_envoy_api_v2_route_RouteAction_RequestMirrorPolicy__Output)[]; + 'request_mirror_policies': (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output)[]; /** * An internal redirect is handled, iff the number of previous internal redirects that a * downstream request has encountered is lower than this value, and - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * is set to :ref:`HANDLE_INTERNAL_REDIRECT - * ` + * ` * In the case where a downstream request is bounced among multiple routes by internal redirect, * the first route that hits this threshold, or has - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * set to * :ref:`PASS_THROUGH_INTERNAL_REDIRECT - * ` + * ` * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. @@ -970,7 +1096,7 @@ export interface RouteAction__Output { * before the rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of :ref:`prefix_rewrite ` + * Only one of :ref:`prefix_rewrite ` * or *regex_rewrite* may be specified. * * Examples using Google's `RE2 `_ engine: @@ -990,15 +1116,44 @@ export interface RouteAction__Output { * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to * ``/aaa/yyy/bbb``. */ - 'regex_rewrite'?: (_envoy_type_matcher_RegexMatchAndSubstitute__Output); + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that if this is set, it'll take * precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - * most internal one becomes the enforced policy). :ref:`Retry policy ` + * most internal one becomes the enforced policy). :ref:`Retry policy ` * should not be set if this field is used. */ 'retry_policy_typed_config'?: (_google_protobuf_Any__Output); + /** + * If present, Envoy will try to follow an upstream redirect response instead of proxying the + * response back to the downstream. An upstream redirect response is defined + * by :ref:`redirect_response_codes + * `. + */ + 'internal_redirect_policy'?: (_envoy_config_route_v3_InternalRedirectPolicy__Output); + /** + * Indicates that during forwarding, the host header will be swapped with + * the result of the regex substitution executed on path value with query and fragment removed. + * This is useful for transitioning variable content between path segment and subdomain. + * + * For example with the following config: + * + * .. code-block:: yaml + * + * host_rewrite_path_regex: + * pattern: + * google_re2: {} + * regex: "^/(.+)/.+$" + * substitution: \1 + * + * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. + */ + 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); + /** + * Specifies the maximum stream duration for this route. + */ + 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration__Output); 'cluster_specifier': "cluster"|"cluster_header"|"weighted_clusters"; - 'host_rewrite_specifier': "host_rewrite"|"auto_host_rewrite"|"auto_host_rewrite_header"; + 'host_rewrite_specifier': "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/RouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts similarity index 63% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/RouteConfiguration.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts index de0634c24..e9ad30257 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/RouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts @@ -1,25 +1,26 @@ -// Original file: deps/envoy-api/envoy/api/v2/route.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route.proto -import type { VirtualHost as _envoy_api_v2_route_VirtualHost, VirtualHost__Output as _envoy_api_v2_route_VirtualHost__Output } from '../../../envoy/api/v2/route/VirtualHost'; -import type { HeaderValueOption as _envoy_api_v2_core_HeaderValueOption, HeaderValueOption__Output as _envoy_api_v2_core_HeaderValueOption__Output } from '../../../envoy/api/v2/core/HeaderValueOption'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../google/protobuf/BoolValue'; -import type { Vhds as _envoy_api_v2_Vhds, Vhds__Output as _envoy_api_v2_Vhds__Output } from '../../../envoy/api/v2/Vhds'; +import type { VirtualHost as _envoy_config_route_v3_VirtualHost, VirtualHost__Output as _envoy_config_route_v3_VirtualHost__Output } from '../../../../envoy/config/route/v3/VirtualHost'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Vhds as _envoy_config_route_v3_Vhds, Vhds__Output as _envoy_config_route_v3_Vhds__Output } from '../../../../envoy/config/route/v3/Vhds'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; /** - * [#next-free-field: 11] + * [#next-free-field: 12] */ export interface RouteConfiguration { /** * The name of the route configuration. For example, it might match * :ref:`route_config_name - * ` in - * :ref:`envoy_api_msg_config.filter.network.http_connection_manager.v2.Rds`. + * ` in + * :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. */ 'name'?: (string); /** * An array of virtual hosts that make up the route table. */ - 'virtual_hosts'?: (_envoy_api_v2_route_VirtualHost)[]; + 'virtual_hosts'?: (_envoy_config_route_v3_VirtualHost)[]; /** * Optionally specifies a list of HTTP headers that the connection manager * will consider to be internal only. If they are found on external requests they will be cleaned @@ -30,12 +31,12 @@ export interface RouteConfiguration { /** * Specifies a list of HTTP headers that should be added to each response that * the connection manager encodes. Headers specified at this level are applied - * after headers from any enclosed :ref:`envoy_api_msg_route.VirtualHost` or - * :ref:`envoy_api_msg_route.RouteAction`. For more information, including details on + * after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'response_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of HTTP headers that should be removed from each response * that the connection manager encodes. @@ -44,12 +45,12 @@ export interface RouteConfiguration { /** * Specifies a list of HTTP headers that should be added to each request * routed by the HTTP connection manager. Headers specified at this level are - * applied after headers from any enclosed :ref:`envoy_api_msg_route.VirtualHost` or - * :ref:`envoy_api_msg_route.RouteAction`. For more information, including details on + * applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * An optional boolean that specifies whether the clusters that the route * table refers to will be validated by the cluster manager. If set to true @@ -58,10 +59,10 @@ export interface RouteConfiguration { * route table will load and the router filter will return a 404 if the route * is selected at runtime. This setting defaults to true if the route table * is statically defined via the :ref:`route_config - * ` + * ` * option. This setting default to false if the route table is loaded dynamically via the * :ref:`rds - * ` + * ` * option. Users may wish to override the default behavior in certain cases (for example when * using CDS with a static route table). */ @@ -79,7 +80,7 @@ export interface RouteConfiguration { * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration * taking precedence. */ - 'vhds'?: (_envoy_api_v2_Vhds); + 'vhds'?: (_envoy_config_route_v3_Vhds); /** * By default, headers that should be added/removed are evaluated from most to least specific: * @@ -93,23 +94,36 @@ export interface RouteConfiguration { * [#next-major-version: In the v3 API, this will default to true.] */ 'most_specific_header_mutations_wins'?: (boolean); + /** + * The maximum bytes of the response :ref:`direct response body + * ` size. If not specified the default + * is 4096. + * + * .. warning:: + * + * Envoy currently holds the content of :ref:`direct response body + * ` in memory. Be careful setting + * this to be larger than the default 4KB, since the allocated memory for direct response body + * is not subject to data plane buffering controls. + */ + 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value); } /** - * [#next-free-field: 11] + * [#next-free-field: 12] */ export interface RouteConfiguration__Output { /** * The name of the route configuration. For example, it might match * :ref:`route_config_name - * ` in - * :ref:`envoy_api_msg_config.filter.network.http_connection_manager.v2.Rds`. + * ` in + * :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. */ 'name': (string); /** * An array of virtual hosts that make up the route table. */ - 'virtual_hosts': (_envoy_api_v2_route_VirtualHost__Output)[]; + 'virtual_hosts': (_envoy_config_route_v3_VirtualHost__Output)[]; /** * Optionally specifies a list of HTTP headers that the connection manager * will consider to be internal only. If they are found on external requests they will be cleaned @@ -120,12 +134,12 @@ export interface RouteConfiguration__Output { /** * Specifies a list of HTTP headers that should be added to each response that * the connection manager encodes. Headers specified at this level are applied - * after headers from any enclosed :ref:`envoy_api_msg_route.VirtualHost` or - * :ref:`envoy_api_msg_route.RouteAction`. For more information, including details on + * after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'response_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of HTTP headers that should be removed from each response * that the connection manager encodes. @@ -134,12 +148,12 @@ export interface RouteConfiguration__Output { /** * Specifies a list of HTTP headers that should be added to each request * routed by the HTTP connection manager. Headers specified at this level are - * applied after headers from any enclosed :ref:`envoy_api_msg_route.VirtualHost` or - * :ref:`envoy_api_msg_route.RouteAction`. For more information, including details on + * applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * An optional boolean that specifies whether the clusters that the route * table refers to will be validated by the cluster manager. If set to true @@ -148,10 +162,10 @@ export interface RouteConfiguration__Output { * route table will load and the router filter will return a 404 if the route * is selected at runtime. This setting defaults to true if the route table * is statically defined via the :ref:`route_config - * ` + * ` * option. This setting default to false if the route table is loaded dynamically via the * :ref:`rds - * ` + * ` * option. Users may wish to override the default behavior in certain cases (for example when * using CDS with a static route table). */ @@ -169,7 +183,7 @@ export interface RouteConfiguration__Output { * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration * taking precedence. */ - 'vhds'?: (_envoy_api_v2_Vhds__Output); + 'vhds'?: (_envoy_config_route_v3_Vhds__Output); /** * By default, headers that should be added/removed are evaluated from most to least specific: * @@ -183,4 +197,17 @@ export interface RouteConfiguration__Output { * [#next-major-version: In the v3 API, this will default to true.] */ 'most_specific_header_mutations_wins': (boolean); + /** + * The maximum bytes of the response :ref:`direct response body + * ` size. If not specified the default + * is 4096. + * + * .. warning:: + * + * Envoy currently holds the content of :ref:`direct response body + * ` in memory. Be careful setting + * this to be larger than the default 4KB, since the allocated memory for direct response body + * is not subject to data plane buffering controls. + */ + 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RouteMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts similarity index 67% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/RouteMatch.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts index b055c0506..d941bcfd1 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/RouteMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts @@ -1,18 +1,30 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { HeaderMatcher as _envoy_api_v2_route_HeaderMatcher, HeaderMatcher__Output as _envoy_api_v2_route_HeaderMatcher__Output } from '../../../../envoy/api/v2/route/HeaderMatcher'; -import type { QueryParameterMatcher as _envoy_api_v2_route_QueryParameterMatcher, QueryParameterMatcher__Output as _envoy_api_v2_route_QueryParameterMatcher__Output } from '../../../../envoy/api/v2/route/QueryParameterMatcher'; -import type { RuntimeFractionalPercent as _envoy_api_v2_core_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_api_v2_core_RuntimeFractionalPercent__Output } from '../../../../envoy/api/v2/core/RuntimeFractionalPercent'; -import type { RegexMatcher as _envoy_type_matcher_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_RegexMatcher__Output } from '../../../../envoy/type/matcher/RegexMatcher'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; +import type { QueryParameterMatcher as _envoy_config_route_v3_QueryParameterMatcher, QueryParameterMatcher__Output as _envoy_config_route_v3_QueryParameterMatcher__Output } from '../../../../envoy/config/route/v3/QueryParameterMatcher'; +import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; -export interface _envoy_api_v2_route_RouteMatch_GrpcRouteMatchOptions { +/** + * An extensible message for matching CONNECT requests. + */ +export interface _envoy_config_route_v3_RouteMatch_ConnectMatcher { +} + +/** + * An extensible message for matching CONNECT requests. + */ +export interface _envoy_config_route_v3_RouteMatch_ConnectMatcher__Output { +} + +export interface _envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions { } -export interface _envoy_api_v2_route_RouteMatch_GrpcRouteMatchOptions__Output { +export interface _envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions__Output { } -export interface _envoy_api_v2_route_RouteMatch_TlsContextMatchOptions { +export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions { /** * If specified, the route will match against whether or not a certificate is presented. * If not specified, certificate presentation status (true or false) will not be considered when route matching. @@ -25,7 +37,7 @@ export interface _envoy_api_v2_route_RouteMatch_TlsContextMatchOptions { 'validated'?: (_google_protobuf_BoolValue); } -export interface _envoy_api_v2_route_RouteMatch_TlsContextMatchOptions__Output { +export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Output { /** * If specified, the route will match against whether or not a certificate is presented. * If not specified, certificate presentation status (true or false) will not be considered when route matching. @@ -39,7 +51,7 @@ export interface _envoy_api_v2_route_RouteMatch_TlsContextMatchOptions__Output { } /** - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface RouteMatch { /** @@ -53,26 +65,7 @@ export interface RouteMatch { */ 'path'?: (string); /** - * If specified, the route is a regular expression rule meaning that the - * regex must match the *:path* header once the query string is removed. The entire path - * (without the query string) must match the regex. The rule will not match if only a - * subsequence of the *:path* header matches the regex. The regex grammar is defined `here - * `_. - * - * Examples: - * - * * The regex ``/b[io]t`` matches the path * /bit* - * * The regex ``/b[io]t`` matches the path * /bot* - * * The regex ``/b[io]t`` does not match the path * /bite* - * * The regex ``/b[io]t`` does not match the path * /bit/bot* - * - * .. attention:: - * This field has been deprecated in favor of `safe_regex` as it is not safe for use with - * untrusted input in all cases. - */ - 'regex'?: (string); - /** - * Indicates that prefix/path matching should be case insensitive. The default + * Indicates that prefix/path matching should be case sensitive. The default * is true. */ 'case_sensitive'?: (_google_protobuf_BoolValue); @@ -83,7 +76,7 @@ export interface RouteMatch { * the request with the same values (or based on presence if the value field * is not in the config). */ - 'headers'?: (_envoy_api_v2_route_HeaderMatcher)[]; + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; /** * Specifies a set of URL query parameters on which the route should * match. The router will check the query string from the *path* header @@ -91,13 +84,13 @@ export interface RouteMatch { * query parameters is nonzero, they all must match the *path* header's * query string for a match to occur. */ - 'query_parameters'?: (_envoy_api_v2_route_QueryParameterMatcher)[]; + 'query_parameters'?: (_envoy_config_route_v3_QueryParameterMatcher)[]; /** * If specified, only gRPC requests will be matched. The router will check * that the content-type header has a application/grpc or one of the various * application/grpc+ values. */ - 'grpc'?: (_envoy_api_v2_route_RouteMatch_GrpcRouteMatchOptions); + 'grpc'?: (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions); /** * Indicates that the route should additionally match on a runtime key. Every time the route * is considered for a match, it must also fall under the percentage of matches indicated by @@ -116,7 +109,7 @@ export interface RouteMatch { * instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent * whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. */ - 'runtime_fraction'?: (_envoy_api_v2_core_RuntimeFractionalPercent); + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent); /** * If specified, the route is a regular expression rule meaning that the * regex must match the *:path* header once the query string is removed. The entire path @@ -131,19 +124,31 @@ export interface RouteMatch { * on :path, etc. The issue with that is it is unclear how to generically deal with query string * stripping. This needs more thought.] */ - 'safe_regex'?: (_envoy_type_matcher_RegexMatcher); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher); /** * If specified, the client tls context will be matched against the defined * match options. * * [#next-major-version: unify with RBAC] */ - 'tls_context'?: (_envoy_api_v2_route_RouteMatch_TlsContextMatchOptions); - 'path_specifier'?: "prefix"|"path"|"regex"|"safe_regex"; + 'tls_context'?: (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions); + /** + * If this is used as the matcher, the matcher will only match CONNECT requests. + * Note that this will not match HTTP/2 upgrade-style CONNECT requests + * (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + * upgrades. + * This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + * where Extended CONNECT requests may have a path, the path matchers will work if + * there is a path present. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + */ + 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher); + 'path_specifier'?: "prefix"|"path"|"safe_regex"|"connect_matcher"; } /** - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface RouteMatch__Output { /** @@ -157,26 +162,7 @@ export interface RouteMatch__Output { */ 'path'?: (string); /** - * If specified, the route is a regular expression rule meaning that the - * regex must match the *:path* header once the query string is removed. The entire path - * (without the query string) must match the regex. The rule will not match if only a - * subsequence of the *:path* header matches the regex. The regex grammar is defined `here - * `_. - * - * Examples: - * - * * The regex ``/b[io]t`` matches the path * /bit* - * * The regex ``/b[io]t`` matches the path * /bot* - * * The regex ``/b[io]t`` does not match the path * /bite* - * * The regex ``/b[io]t`` does not match the path * /bit/bot* - * - * .. attention:: - * This field has been deprecated in favor of `safe_regex` as it is not safe for use with - * untrusted input in all cases. - */ - 'regex'?: (string); - /** - * Indicates that prefix/path matching should be case insensitive. The default + * Indicates that prefix/path matching should be case sensitive. The default * is true. */ 'case_sensitive'?: (_google_protobuf_BoolValue__Output); @@ -187,7 +173,7 @@ export interface RouteMatch__Output { * the request with the same values (or based on presence if the value field * is not in the config). */ - 'headers': (_envoy_api_v2_route_HeaderMatcher__Output)[]; + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; /** * Specifies a set of URL query parameters on which the route should * match. The router will check the query string from the *path* header @@ -195,13 +181,13 @@ export interface RouteMatch__Output { * query parameters is nonzero, they all must match the *path* header's * query string for a match to occur. */ - 'query_parameters': (_envoy_api_v2_route_QueryParameterMatcher__Output)[]; + 'query_parameters': (_envoy_config_route_v3_QueryParameterMatcher__Output)[]; /** * If specified, only gRPC requests will be matched. The router will check * that the content-type header has a application/grpc or one of the various * application/grpc+ values. */ - 'grpc'?: (_envoy_api_v2_route_RouteMatch_GrpcRouteMatchOptions__Output); + 'grpc'?: (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions__Output); /** * Indicates that the route should additionally match on a runtime key. Every time the route * is considered for a match, it must also fall under the percentage of matches indicated by @@ -220,7 +206,7 @@ export interface RouteMatch__Output { * instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent * whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. */ - 'runtime_fraction'?: (_envoy_api_v2_core_RuntimeFractionalPercent__Output); + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); /** * If specified, the route is a regular expression rule meaning that the * regex must match the *:path* header once the query string is removed. The entire path @@ -235,13 +221,25 @@ export interface RouteMatch__Output { * on :path, etc. The issue with that is it is unclear how to generically deal with query string * stripping. This needs more thought.] */ - 'safe_regex'?: (_envoy_type_matcher_RegexMatcher__Output); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output); /** * If specified, the client tls context will be matched against the defined * match options. * * [#next-major-version: unify with RBAC] */ - 'tls_context'?: (_envoy_api_v2_route_RouteMatch_TlsContextMatchOptions__Output); - 'path_specifier': "prefix"|"path"|"regex"|"safe_regex"; + 'tls_context'?: (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Output); + /** + * If this is used as the matcher, the matcher will only match CONNECT requests. + * Note that this will not match HTTP/2 upgrade-style CONNECT requests + * (WebSocket and the like) as they are normalized in Envoy as HTTP/1.1 style + * upgrades. + * This is the only way to match CONNECT requests for HTTP/1.1. For HTTP/2, + * where Extended CONNECT requests may have a path, the path matchers will work if + * there is a path present. + * Note that CONNECT support is currently considered alpha in Envoy. + * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + */ + 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher__Output); + 'path_specifier': "prefix"|"path"|"safe_regex"|"connect_matcher"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/ScopedRouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts similarity index 59% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/ScopedRouteConfiguration.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts index 02810bf02..afbb9886b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/ScopedRouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts @@ -1,7 +1,7 @@ -// Original file: deps/envoy-api/envoy/api/v2/scoped_route.proto +// Original file: deps/envoy-api/envoy/config/route/v3/scoped_route.proto -export interface _envoy_api_v2_ScopedRouteConfiguration_Key_Fragment { +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment { /** * A string to match against. */ @@ -9,7 +9,7 @@ export interface _envoy_api_v2_ScopedRouteConfiguration_Key_Fragment { 'type'?: "string_key"; } -export interface _envoy_api_v2_ScopedRouteConfiguration_Key_Fragment__Output { +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment__Output { /** * A string to match against. */ @@ -19,45 +19,45 @@ export interface _envoy_api_v2_ScopedRouteConfiguration_Key_Fragment__Output { /** * Specifies a key which is matched against the output of the - * :ref:`scope_key_builder` + * :ref:`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 { +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key { /** * The ordered set of fragments to match against. The order must match the * fragments in the corresponding - * :ref:`scope_key_builder`. + * :ref:`scope_key_builder`. */ - 'fragments'?: (_envoy_api_v2_ScopedRouteConfiguration_Key_Fragment)[]; + 'fragments'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment)[]; } /** * Specifies a key which is matched against the output of the - * :ref:`scope_key_builder` + * :ref:`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 { +export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key__Output { /** * The ordered set of fragments to match against. The order must match the * fragments in the corresponding - * :ref:`scope_key_builder`. + * :ref:`scope_key_builder`. */ - 'fragments': (_envoy_api_v2_ScopedRouteConfiguration_Key_Fragment__Output)[]; + 'fragments': (_envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment__Output)[]; } /** * Specifies a routing scope, which associates a - * :ref:`Key` to a - * :ref:`envoy_api_msg_RouteConfiguration` (identified by its resource name). + * :ref:`Key` to a + * :ref:`envoy_api_msg_config.route.v3.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` + * :ref:`scope_key_builder` * assigned to the HttpConnectionManager. * * For example, with the following configurations (in YAML): @@ -79,7 +79,7 @@ export interface _envoy_api_v2_ScopedRouteConfiguration_Key__Output { * key: vip * * ScopedRouteConfiguration resources (specified statically via - * :ref:`scoped_route_configurations_list` + * :ref:`scoped_route_configurations_list` * or obtained dynamically via SRDS): * * .. code:: @@ -115,26 +115,30 @@ export interface ScopedRouteConfiguration { */ '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 + * The resource name to use for a :ref:`envoy_api_msg_service.discovery.v3.DiscoveryRequest` to an + * RDS server to fetch the :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` associated * with this scope. */ 'route_configuration_name'?: (string); /** * The key to match against. */ - 'key'?: (_envoy_api_v2_ScopedRouteConfiguration_Key); + 'key'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key); + /** + * Whether the RouteConfiguration should be loaded on demand. + */ + 'on_demand'?: (boolean); } /** * Specifies a routing scope, which associates a - * :ref:`Key` to a - * :ref:`envoy_api_msg_RouteConfiguration` (identified by its resource name). + * :ref:`Key` to a + * :ref:`envoy_api_msg_config.route.v3.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` + * :ref:`scope_key_builder` * assigned to the HttpConnectionManager. * * For example, with the following configurations (in YAML): @@ -156,7 +160,7 @@ export interface ScopedRouteConfiguration { * key: vip * * ScopedRouteConfiguration resources (specified statically via - * :ref:`scoped_route_configurations_list` + * :ref:`scoped_route_configurations_list` * or obtained dynamically via SRDS): * * .. code:: @@ -192,13 +196,17 @@ export interface ScopedRouteConfiguration__Output { */ '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 + * The resource name to use for a :ref:`envoy_api_msg_service.discovery.v3.DiscoveryRequest` to an + * RDS server to fetch the :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` associated * with this scope. */ 'route_configuration_name': (string); /** * The key to match against. */ - 'key'?: (_envoy_api_v2_ScopedRouteConfiguration_Key__Output); + 'key'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key__Output); + /** + * Whether the RouteConfiguration should be loaded on demand. + */ + 'on_demand': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts similarity index 75% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/Tracing.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts index 18b063339..5be619a5f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/Tracing.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts @@ -1,7 +1,7 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -import type { FractionalPercent as _envoy_type_FractionalPercent, FractionalPercent__Output as _envoy_type_FractionalPercent__Output } from '../../../../envoy/type/FractionalPercent'; -import type { CustomTag as _envoy_type_tracing_v2_CustomTag, CustomTag__Output as _envoy_type_tracing_v2_CustomTag__Output } from '../../../../envoy/type/tracing/v2/CustomTag'; +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../envoy/type/v3/FractionalPercent'; +import type { CustomTag as _envoy_type_tracing_v3_CustomTag, CustomTag__Output as _envoy_type_tracing_v3_CustomTag__Output } from '../../../../envoy/type/tracing/v3/CustomTag'; export interface Tracing { /** @@ -12,7 +12,7 @@ export interface Tracing { * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_FractionalPercent); + 'client_sampling'?: (_envoy_type_v3_FractionalPercent); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -20,7 +20,7 @@ export interface Tracing { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_FractionalPercent); + 'random_sampling'?: (_envoy_type_v3_FractionalPercent); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -31,16 +31,16 @@ export interface Tracing { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_FractionalPercent); + 'overall_sampling'?: (_envoy_type_v3_FractionalPercent); /** * A list of custom tags with unique tag name to create tags for the active span. * It will take effect after merging with the :ref:`corresponding configuration - * ` + * ` * configured in the HTTP connection manager. If two tags with the same name are configured * each in the HTTP connection manager and the route level, the one configured here takes * priority. */ - 'custom_tags'?: (_envoy_type_tracing_v2_CustomTag)[]; + 'custom_tags'?: (_envoy_type_tracing_v3_CustomTag)[]; } export interface Tracing__Output { @@ -52,7 +52,7 @@ export interface Tracing__Output { * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_FractionalPercent__Output); + 'client_sampling'?: (_envoy_type_v3_FractionalPercent__Output); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -60,7 +60,7 @@ export interface Tracing__Output { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_FractionalPercent__Output); + 'random_sampling'?: (_envoy_type_v3_FractionalPercent__Output); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -71,14 +71,14 @@ export interface Tracing__Output { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_FractionalPercent__Output); + 'overall_sampling'?: (_envoy_type_v3_FractionalPercent__Output); /** * A list of custom tags with unique tag name to create tags for the active span. * It will take effect after merging with the :ref:`corresponding configuration - * ` + * ` * configured in the HTTP connection manager. If two tags with the same name are configured * each in the HTTP connection manager and the route level, the one configured here takes * priority. */ - 'custom_tags': (_envoy_type_tracing_v2_CustomTag__Output)[]; + 'custom_tags': (_envoy_type_tracing_v3_CustomTag__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts new file mode 100644 index 000000000..2868685b2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; + +export interface Vhds { + /** + * Configuration source specifier for VHDS. + */ + 'config_source'?: (_envoy_config_core_v3_ConfigSource); +} + +export interface Vhds__Output { + /** + * Configuration source specifier for VHDS. + */ + 'config_source'?: (_envoy_config_core_v3_ConfigSource__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/VirtualCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts similarity index 58% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/VirtualCluster.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts index f072710ce..7674da733 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/VirtualCluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts @@ -1,7 +1,6 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -import type { RequestMethod as _envoy_api_v2_core_RequestMethod } from '../../../../envoy/api/v2/core/RequestMethod'; -import type { HeaderMatcher as _envoy_api_v2_route_HeaderMatcher, HeaderMatcher__Output as _envoy_api_v2_route_HeaderMatcher__Output } from '../../../../envoy/api/v2/route/HeaderMatcher'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; /** * A virtual cluster is a way of specifying a regex matching rule against @@ -23,42 +22,18 @@ import type { HeaderMatcher as _envoy_api_v2_route_HeaderMatcher, HeaderMatcher_ * statistics output are not free. */ export interface VirtualCluster { - /** - * Specifies a regex pattern to use for matching requests. The entire path of the request - * must match the regex. The regex grammar used is defined `here - * `_. - * - * Examples: - * - * * The regex ``/rides/\d+`` matches the path * /rides/0* - * * The regex ``/rides/\d+`` matches the path * /rides/123* - * * The regex ``/rides/\d+`` does not match the path * /rides/123/456* - * - * .. attention:: - * This field has been deprecated in favor of `headers` as it is not safe for use with - * untrusted input in all cases. - */ - 'pattern'?: (string); /** * Specifies the name of the virtual cluster. The virtual cluster name as well * as the virtual host name are used when emitting statistics. The statistics are emitted by the * router filter and are documented :ref:`here `. */ 'name'?: (string); - /** - * Optionally specifies the HTTP method to match on. For example GET, PUT, - * etc. - * - * .. attention:: - * This field has been deprecated in favor of `headers`. - */ - 'method'?: (_envoy_api_v2_core_RequestMethod | keyof typeof _envoy_api_v2_core_RequestMethod); /** * Specifies a list of header matchers to use for matching requests. Each specified header must * match. The pseudo-headers `:path` and `:method` can be used to match the request path and * method, respectively. */ - 'headers'?: (_envoy_api_v2_route_HeaderMatcher)[]; + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; } /** @@ -81,40 +56,16 @@ export interface VirtualCluster { * statistics output are not free. */ export interface VirtualCluster__Output { - /** - * Specifies a regex pattern to use for matching requests. The entire path of the request - * must match the regex. The regex grammar used is defined `here - * `_. - * - * Examples: - * - * * The regex ``/rides/\d+`` matches the path * /rides/0* - * * The regex ``/rides/\d+`` matches the path * /rides/123* - * * The regex ``/rides/\d+`` does not match the path * /rides/123/456* - * - * .. attention:: - * This field has been deprecated in favor of `headers` as it is not safe for use with - * untrusted input in all cases. - */ - 'pattern': (string); /** * Specifies the name of the virtual cluster. The virtual cluster name as well * as the virtual host name are used when emitting statistics. The statistics are emitted by the * router filter and are documented :ref:`here `. */ 'name': (string); - /** - * Optionally specifies the HTTP method to match on. For example GET, PUT, - * etc. - * - * .. attention:: - * This field has been deprecated in favor of `headers`. - */ - 'method': (keyof typeof _envoy_api_v2_core_RequestMethod); /** * Specifies a list of header matchers to use for matching requests. Each specified header must * match. The pseudo-headers `:path` and `:method` can be used to match the request path and * method, respectively. */ - 'headers': (_envoy_api_v2_route_HeaderMatcher__Output)[]; + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/VirtualHost.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts similarity index 71% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/VirtualHost.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts index ad806e949..86088ded6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/VirtualHost.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts @@ -1,19 +1,18 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -import type { Route as _envoy_api_v2_route_Route, Route__Output as _envoy_api_v2_route_Route__Output } from '../../../../envoy/api/v2/route/Route'; -import type { VirtualCluster as _envoy_api_v2_route_VirtualCluster, VirtualCluster__Output as _envoy_api_v2_route_VirtualCluster__Output } from '../../../../envoy/api/v2/route/VirtualCluster'; -import type { RateLimit as _envoy_api_v2_route_RateLimit, RateLimit__Output as _envoy_api_v2_route_RateLimit__Output } from '../../../../envoy/api/v2/route/RateLimit'; -import type { HeaderValueOption as _envoy_api_v2_core_HeaderValueOption, HeaderValueOption__Output as _envoy_api_v2_core_HeaderValueOption__Output } from '../../../../envoy/api/v2/core/HeaderValueOption'; -import type { CorsPolicy as _envoy_api_v2_route_CorsPolicy, CorsPolicy__Output as _envoy_api_v2_route_CorsPolicy__Output } from '../../../../envoy/api/v2/route/CorsPolicy'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Route as _envoy_config_route_v3_Route, Route__Output as _envoy_config_route_v3_Route__Output } from '../../../../envoy/config/route/v3/Route'; +import type { VirtualCluster as _envoy_config_route_v3_VirtualCluster, VirtualCluster__Output as _envoy_config_route_v3_VirtualCluster__Output } from '../../../../envoy/config/route/v3/VirtualCluster'; +import type { RateLimit as _envoy_config_route_v3_RateLimit, RateLimit__Output as _envoy_config_route_v3_RateLimit__Output } from '../../../../envoy/config/route/v3/RateLimit'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; +import type { CorsPolicy as _envoy_config_route_v3_CorsPolicy, CorsPolicy__Output as _envoy_config_route_v3_CorsPolicy__Output } from '../../../../envoy/config/route/v3/CorsPolicy'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -import type { RetryPolicy as _envoy_api_v2_route_RetryPolicy, RetryPolicy__Output as _envoy_api_v2_route_RetryPolicy__Output } from '../../../../envoy/api/v2/route/RetryPolicy'; -import type { HedgePolicy as _envoy_api_v2_route_HedgePolicy, HedgePolicy__Output as _envoy_api_v2_route_HedgePolicy__Output } from '../../../../envoy/api/v2/route/HedgePolicy'; +import type { RetryPolicy as _envoy_config_route_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_route_v3_RetryPolicy__Output } from '../../../../envoy/config/route/v3/RetryPolicy'; +import type { HedgePolicy as _envoy_config_route_v3_HedgePolicy, HedgePolicy__Output as _envoy_config_route_v3_HedgePolicy__Output } from '../../../../envoy/config/route/v3/HedgePolicy'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -export enum _envoy_api_v2_route_VirtualHost_TlsRequirementType { +export enum _envoy_config_route_v3_VirtualHost_TlsRequirementType { /** * No TLS requirement for the virtual host. */ @@ -69,57 +68,49 @@ export interface VirtualHost { * The list of routes that will be matched, in order, for incoming requests. * The first route that matches will be used. */ - 'routes'?: (_envoy_api_v2_route_Route)[]; + 'routes'?: (_envoy_config_route_v3_Route)[]; /** * Specifies the type of TLS enforcement the virtual host expects. If this option is not * specified, there is no TLS requirement for the virtual host. */ - 'require_tls'?: (_envoy_api_v2_route_VirtualHost_TlsRequirementType | keyof typeof _envoy_api_v2_route_VirtualHost_TlsRequirementType); + 'require_tls'?: (_envoy_config_route_v3_VirtualHost_TlsRequirementType | keyof typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType); /** * A list of virtual clusters defined for this virtual host. Virtual clusters * are used for additional statistics gathering. */ - 'virtual_clusters'?: (_envoy_api_v2_route_VirtualCluster)[]; + 'virtual_clusters'?: (_envoy_config_route_v3_VirtualCluster)[]; /** * Specifies a set of rate limit configurations that will be applied to the * virtual host. */ - 'rate_limits'?: (_envoy_api_v2_route_RateLimit)[]; + 'rate_limits'?: (_envoy_config_route_v3_RateLimit)[]; /** * Specifies a list of HTTP headers that should be added to each request * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Indicates that the virtual host has a CORS policy. */ - 'cors'?: (_envoy_api_v2_route_CorsPolicy); + 'cors'?: (_envoy_config_route_v3_CorsPolicy); /** * Specifies a list of HTTP headers that should be added to each response * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'response_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of HTTP headers that should be removed from each response * handled by this virtual host. */ 'response_headers_to_remove'?: (string)[]; - /** - * The per_filter_config field can be used to provide virtual host-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. - */ - 'per_filter_config'?: ({[key: string]: _google_protobuf_Struct}); /** * Specifies a list of HTTP headers that should be removed from each request * handled by this virtual host. @@ -133,7 +124,7 @@ export interface VirtualHost { * will see the attempt count as perceived by the second Envoy. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. * * [#next-major-version: rename to include_attempt_count_in_request.] */ @@ -144,6 +135,9 @@ export interface VirtualHost { * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); /** @@ -151,13 +145,13 @@ export interface VirtualHost { * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'retry_policy'?: (_envoy_api_v2_route_RetryPolicy); + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy); /** * Indicates the hedge policy for all routes in this virtual host. Note that setting a * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'hedge_policy'?: (_envoy_api_v2_route_HedgePolicy); + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy); /** * The maximum bytes which will be buffered for retries and shadowing. * If set and a route-specific limit is not set, the bytes actually buffered will be the minimum @@ -172,14 +166,14 @@ export interface VirtualHost { * will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. */ 'include_attempt_count_in_response'?: (boolean); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that setting a route level entry * will take precedence over this config and it'll be treated independently (e.g.: values are not - * inherited). :ref:`Retry policy ` should not be + * inherited). :ref:`Retry policy ` should not be * set if this field is used. */ 'retry_policy_typed_config'?: (_google_protobuf_Any); @@ -224,57 +218,49 @@ export interface VirtualHost__Output { * The list of routes that will be matched, in order, for incoming requests. * The first route that matches will be used. */ - 'routes': (_envoy_api_v2_route_Route__Output)[]; + 'routes': (_envoy_config_route_v3_Route__Output)[]; /** * Specifies the type of TLS enforcement the virtual host expects. If this option is not * specified, there is no TLS requirement for the virtual host. */ - 'require_tls': (keyof typeof _envoy_api_v2_route_VirtualHost_TlsRequirementType); + 'require_tls': (keyof typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType); /** * A list of virtual clusters defined for this virtual host. Virtual clusters * are used for additional statistics gathering. */ - 'virtual_clusters': (_envoy_api_v2_route_VirtualCluster__Output)[]; + 'virtual_clusters': (_envoy_config_route_v3_VirtualCluster__Output)[]; /** * Specifies a set of rate limit configurations that will be applied to the * virtual host. */ - 'rate_limits': (_envoy_api_v2_route_RateLimit__Output)[]; + 'rate_limits': (_envoy_config_route_v3_RateLimit__Output)[]; /** * Specifies a list of HTTP headers that should be added to each request * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Indicates that the virtual host has a CORS policy. */ - 'cors'?: (_envoy_api_v2_route_CorsPolicy__Output); + 'cors'?: (_envoy_config_route_v3_CorsPolicy__Output); /** * Specifies a list of HTTP headers that should be added to each response * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'response_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of HTTP headers that should be removed from each response * handled by this virtual host. */ 'response_headers_to_remove': (string)[]; - /** - * The per_filter_config field can be used to provide virtual host-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. - */ - 'per_filter_config'?: ({[key: string]: _google_protobuf_Struct__Output}); /** * Specifies a list of HTTP headers that should be removed from each request * handled by this virtual host. @@ -288,7 +274,7 @@ export interface VirtualHost__Output { * will see the attempt count as perceived by the second Envoy. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. * * [#next-major-version: rename to include_attempt_count_in_request.] */ @@ -299,6 +285,9 @@ export interface VirtualHost__Output { * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any__Output}); /** @@ -306,13 +295,13 @@ export interface VirtualHost__Output { * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'retry_policy'?: (_envoy_api_v2_route_RetryPolicy__Output); + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy__Output); /** * Indicates the hedge policy for all routes in this virtual host. Note that setting a * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'hedge_policy'?: (_envoy_api_v2_route_HedgePolicy__Output); + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy__Output); /** * The maximum bytes which will be buffered for retries and shadowing. * If set and a route-specific limit is not set, the bytes actually buffered will be the minimum @@ -327,14 +316,14 @@ export interface VirtualHost__Output { * will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. */ 'include_attempt_count_in_response': (boolean); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that setting a route level entry * will take precedence over this config and it'll be treated independently (e.g.: values are not - * inherited). :ref:`Retry policy ` should not be + * inherited). :ref:`Retry policy ` should not be * set if this field is used. */ 'retry_policy_typed_config'?: (_google_protobuf_Any__Output); diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/WeightedCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts similarity index 64% rename from packages/grpc-js-xds/src/generated/envoy/api/v2/route/WeightedCluster.ts rename to packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts index 5b283404b..7974508ea 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/route/WeightedCluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts @@ -1,15 +1,14 @@ -// Original file: deps/envoy-api/envoy/api/v2/route/route_components.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; -import type { Metadata as _envoy_api_v2_core_Metadata, Metadata__Output as _envoy_api_v2_core_Metadata__Output } from '../../../../envoy/api/v2/core/Metadata'; -import type { HeaderValueOption as _envoy_api_v2_core_HeaderValueOption, HeaderValueOption__Output as _envoy_api_v2_core_HeaderValueOption__Output } from '../../../../envoy/api/v2/core/HeaderValueOption'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; /** * [#next-free-field: 11] */ -export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight { +export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { /** * Name of the upstream cluster. The cluster must exist in the * :ref:`cluster manager configuration `. @@ -17,7 +16,7 @@ export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight { 'name'?: (string); /** * An integer between 0 and :ref:`total_weight - * `. When a request matches the route, + * `. When a request matches the route, * the choice of an upstream cluster is determined by its weight. The sum of weights across all * entries in the clusters array must add up to the total_weight, which defaults to 100. */ @@ -26,38 +25,38 @@ export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight { * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in * the upstream cluster with metadata matching what is set in this field will be considered for * load balancing. Note that this will be merged with what's provided in - * :ref:`RouteAction.metadata_match `, with + * :ref:`RouteAction.metadata_match `, with * values here taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_api_v2_core_Metadata); + 'metadata_match'?: (_envoy_config_core_v3_Metadata); /** * Specifies a list of headers to be added to requests when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of HTTP headers that should be removed from each request when - * this cluster is selected through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. */ 'request_headers_to_remove'?: (string)[]; /** * Specifies a list of headers to be added to responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'response_headers_to_add'?: (_envoy_api_v2_core_HeaderValueOption)[]; + 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of headers to be removed from responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. */ 'response_headers_to_remove'?: (string)[]; /** @@ -66,14 +65,9 @@ export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight { * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. - */ - 'per_filter_config'?: ({[key: string]: _google_protobuf_Struct}); - /** - * The per_filter_config field can be used to provide weighted cluster-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); } @@ -81,7 +75,7 @@ export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight { /** * [#next-free-field: 11] */ -export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight__Output { +export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { /** * Name of the upstream cluster. The cluster must exist in the * :ref:`cluster manager configuration `. @@ -89,7 +83,7 @@ export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight__Output { 'name': (string); /** * An integer between 0 and :ref:`total_weight - * `. When a request matches the route, + * `. When a request matches the route, * the choice of an upstream cluster is determined by its weight. The sum of weights across all * entries in the clusters array must add up to the total_weight, which defaults to 100. */ @@ -98,38 +92,38 @@ export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight__Output { * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in * the upstream cluster with metadata matching what is set in this field will be considered for * load balancing. Note that this will be merged with what's provided in - * :ref:`RouteAction.metadata_match `, with + * :ref:`RouteAction.metadata_match `, with * values here taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_api_v2_core_Metadata__Output); + 'metadata_match'?: (_envoy_config_core_v3_Metadata__Output); /** * Specifies a list of headers to be added to requests when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'request_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of HTTP headers that should be removed from each request when - * this cluster is selected through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. */ 'request_headers_to_remove': (string)[]; /** * Specifies a list of headers to be added to responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and - * :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ - 'response_headers_to_add': (_envoy_api_v2_core_HeaderValueOption__Output)[]; + 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of headers to be removed from responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. */ 'response_headers_to_remove': (string)[]; /** @@ -138,22 +132,17 @@ export interface _envoy_api_v2_route_WeightedCluster_ClusterWeight__Output { * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. - */ - 'per_filter_config'?: ({[key: string]: _google_protobuf_Struct__Output}); - /** - * The per_filter_config field can be used to provide weighted cluster-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any__Output}); } /** - * Compared to the :ref:`cluster ` field that specifies a + * Compared to the :ref:`cluster ` field that specifies a * single upstream cluster as the target of a request, the :ref:`weighted_clusters - * ` option allows for specification of + * ` option allows for specification of * multiple upstream clusters along with weights that indicate the percentage of * traffic to be forwarded to each cluster. The router selects an upstream cluster based on the * weights. @@ -162,7 +151,7 @@ export interface WeightedCluster { /** * Specifies one or more upstream clusters associated with the route. */ - 'clusters'?: (_envoy_api_v2_route_WeightedCluster_ClusterWeight)[]; + 'clusters'?: (_envoy_config_route_v3_WeightedCluster_ClusterWeight)[]; /** * Specifies the runtime key prefix that should be used to construct the * runtime keys associated with each cluster. When the *runtime_key_prefix* is @@ -182,9 +171,9 @@ export interface WeightedCluster { } /** - * Compared to the :ref:`cluster ` field that specifies a + * Compared to the :ref:`cluster ` field that specifies a * single upstream cluster as the target of a request, the :ref:`weighted_clusters - * ` option allows for specification of + * ` option allows for specification of * multiple upstream clusters along with weights that indicate the percentage of * traffic to be forwarded to each cluster. The router selects an upstream cluster based on the * weights. @@ -193,7 +182,7 @@ export interface WeightedCluster__Output { /** * Specifies one or more upstream clusters associated with the route. */ - 'clusters': (_envoy_api_v2_route_WeightedCluster_ClusterWeight__Output)[]; + 'clusters': (_envoy_config_route_v3_WeightedCluster_ClusterWeight__Output)[]; /** * Specifies the runtime key prefix that should be used to construct the * runtime keys associated with each cluster. When the *runtime_key_prefix* is diff --git a/packages/grpc-js-xds/src/generated/envoy/config/trace/v2/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts similarity index 54% rename from packages/grpc-js-xds/src/generated/envoy/config/trace/v2/Tracing.ts rename to packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts index 629e3f1cc..36dfade51 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/trace/v2/Tracing.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts @@ -1,17 +1,16 @@ -// Original file: deps/envoy-api/envoy/config/trace/v2/http_tracer.proto +// Original file: deps/envoy-api/envoy/config/trace/v3/http_tracer.proto -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; import type { 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 ` - * :ref:`provider ` + * :ref:`HttpConnectionManager.Tracing ` + * :ref:`provider ` * field. */ -export interface _envoy_config_trace_v2_Tracing_Http { +export interface _envoy_config_trace_v3_Tracing_Http { /** * The name of the HTTP trace driver to instantiate. The name must match a * supported HTTP trace driver. Built-in trace drivers: @@ -24,31 +23,30 @@ export interface _envoy_config_trace_v2_Tracing_Http { * - *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 ` - * - :ref:`ZipkinConfig ` - * - :ref:`DynamicOtConfig ` - * - :ref:`DatadogConfig ` - * - :ref:`OpenCensusConfig ` - * - :ref:`AWS X-Ray ` + * - :ref:`LightstepConfig ` + * - :ref:`ZipkinConfig ` + * - :ref:`DynamicOtConfig ` + * - :ref:`DatadogConfig ` + * - :ref:`OpenCensusConfig ` + * - :ref:`AWS X-Ray ` */ - 'config_type'?: "config"|"typed_config"; + 'config_type'?: "typed_config"; } /** * Configuration for an HTTP tracer provider used by Envoy. * * The configuration is defined by the - * :ref:`HttpConnectionManager.Tracing ` - * :ref:`provider ` + * :ref:`HttpConnectionManager.Tracing ` + * :ref:`provider ` * field. */ -export interface _envoy_config_trace_v2_Tracing_Http__Output { +export interface _envoy_config_trace_v3_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: @@ -61,20 +59,19 @@ export interface _envoy_config_trace_v2_Tracing_Http__Output { * - *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 ` - * - :ref:`ZipkinConfig ` - * - :ref:`DynamicOtConfig ` - * - :ref:`DatadogConfig ` - * - :ref:`OpenCensusConfig ` - * - :ref:`AWS X-Ray ` + * - :ref:`LightstepConfig ` + * - :ref:`ZipkinConfig ` + * - :ref:`DynamicOtConfig ` + * - :ref:`DatadogConfig ` + * - :ref:`OpenCensusConfig ` + * - :ref:`AWS X-Ray ` */ - 'config_type': "config"|"typed_config"; + 'config_type': "typed_config"; } /** @@ -86,13 +83,13 @@ export interface _envoy_config_trace_v2_Tracing_Http__Output { * .. attention:: * * Use of this message type has been deprecated in favor of direct use of - * :ref:`Tracing.Http `. + * :ref:`Tracing.Http `. */ export interface Tracing { /** * Provides configuration for the HTTP tracer. */ - 'http'?: (_envoy_config_trace_v2_Tracing_Http); + 'http'?: (_envoy_config_trace_v3_Tracing_Http); } /** @@ -104,11 +101,11 @@ export interface Tracing { * .. attention:: * * Use of this message type has been deprecated in favor of direct use of - * :ref:`Tracing.Http `. + * :ref:`Tracing.Http `. */ export interface Tracing__Output { /** * Provides configuration for the HTTP tracer. */ - 'http'?: (_envoy_config_trace_v2_Tracing_Http__Output); + 'http'?: (_envoy_config_trace_v3_Tracing_Http__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/HttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts similarity index 64% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/HttpConnectionManager.ts rename to packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts index 1838f89a7..b68e812ef 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/HttpConnectionManager.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts @@ -1,24 +1,25 @@ -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -import type { Rds as _envoy_config_filter_network_http_connection_manager_v2_Rds, Rds__Output as _envoy_config_filter_network_http_connection_manager_v2_Rds__Output } from '../../../../../../envoy/config/filter/network/http_connection_manager/v2/Rds'; -import type { RouteConfiguration as _envoy_api_v2_RouteConfiguration, RouteConfiguration__Output as _envoy_api_v2_RouteConfiguration__Output } from '../../../../../../envoy/api/v2/RouteConfiguration'; -import type { HttpFilter as _envoy_config_filter_network_http_connection_manager_v2_HttpFilter, HttpFilter__Output as _envoy_config_filter_network_http_connection_manager_v2_HttpFilter__Output } from '../../../../../../envoy/config/filter/network/http_connection_manager/v2/HttpFilter'; +import type { Rds as _envoy_extensions_filters_network_http_connection_manager_v3_Rds, Rds__Output as _envoy_extensions_filters_network_http_connection_manager_v3_Rds__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/Rds'; +import type { RouteConfiguration as _envoy_config_route_v3_RouteConfiguration, RouteConfiguration__Output as _envoy_config_route_v3_RouteConfiguration__Output } from '../../../../../../envoy/config/route/v3/RouteConfiguration'; +import type { HttpFilter as _envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter, HttpFilter__Output as _envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter'; import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../../../google/protobuf/BoolValue'; -import type { Http1ProtocolOptions as _envoy_api_v2_core_Http1ProtocolOptions, Http1ProtocolOptions__Output as _envoy_api_v2_core_Http1ProtocolOptions__Output } from '../../../../../../envoy/api/v2/core/Http1ProtocolOptions'; -import type { Http2ProtocolOptions as _envoy_api_v2_core_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_api_v2_core_Http2ProtocolOptions__Output } from '../../../../../../envoy/api/v2/core/Http2ProtocolOptions'; +import type { Http1ProtocolOptions as _envoy_config_core_v3_Http1ProtocolOptions, Http1ProtocolOptions__Output as _envoy_config_core_v3_Http1ProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/Http1ProtocolOptions'; +import type { Http2ProtocolOptions as _envoy_config_core_v3_Http2ProtocolOptions, Http2ProtocolOptions__Output as _envoy_config_core_v3_Http2ProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/Http2ProtocolOptions'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../../../google/protobuf/Duration'; -import type { AccessLog as _envoy_config_filter_accesslog_v2_AccessLog, AccessLog__Output as _envoy_config_filter_accesslog_v2_AccessLog__Output } from '../../../../../../envoy/config/filter/accesslog/v2/AccessLog'; +import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../../../envoy/config/accesslog/v3/AccessLog'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../../google/protobuf/UInt32Value'; -import type { ScopedRoutes as _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes, ScopedRoutes__Output as _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes__Output } from '../../../../../../envoy/config/filter/network/http_connection_manager/v2/ScopedRoutes'; -import type { HttpProtocolOptions as _envoy_api_v2_core_HttpProtocolOptions, HttpProtocolOptions__Output as _envoy_api_v2_core_HttpProtocolOptions__Output } from '../../../../../../envoy/api/v2/core/HttpProtocolOptions'; -import type { RequestIDExtension as _envoy_config_filter_network_http_connection_manager_v2_RequestIDExtension, RequestIDExtension__Output as _envoy_config_filter_network_http_connection_manager_v2_RequestIDExtension__Output } from '../../../../../../envoy/config/filter/network/http_connection_manager/v2/RequestIDExtension'; -import type { Percent as _envoy_type_Percent, Percent__Output as _envoy_type_Percent__Output } from '../../../../../../envoy/type/Percent'; -import type { CustomTag as _envoy_type_tracing_v2_CustomTag, CustomTag__Output as _envoy_type_tracing_v2_CustomTag__Output } from '../../../../../../envoy/type/tracing/v2/CustomTag'; -import type { _envoy_config_trace_v2_Tracing_Http, _envoy_config_trace_v2_Tracing_Http__Output } from '../../../../../../envoy/config/trace/v2/Tracing'; +import type { ScopedRoutes as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes, ScopedRoutes__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes'; +import type { HttpProtocolOptions as _envoy_config_core_v3_HttpProtocolOptions, HttpProtocolOptions__Output as _envoy_config_core_v3_HttpProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/HttpProtocolOptions'; +import type { RequestIDExtension as _envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension, RequestIDExtension__Output as _envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension'; +import type { LocalReplyConfig as _envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig, LocalReplyConfig__Output as _envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../../../envoy/type/v3/Percent'; +import type { CustomTag as _envoy_type_tracing_v3_CustomTag, CustomTag__Output as _envoy_type_tracing_v3_CustomTag__Output } from '../../../../../../envoy/type/tracing/v3/CustomTag'; +import type { _envoy_config_trace_v3_Tracing_Http, _envoy_config_trace_v3_Tracing_Http__Output } from '../../../../../../envoy/config/trace/v3/Tracing'; -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_CodecType { +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType { /** * For every new connection, the connection manager will determine which * codec to use. This mode supports both ALPN for TLS listeners as well as @@ -45,13 +46,13 @@ export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnecti HTTP3 = 3, } -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto /** * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. */ -export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ForwardClientCertDetails { +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails { /** * Do not send the XFCC header to the next hop. This is the default value. */ @@ -78,23 +79,23 @@ export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnecti ALWAYS_FORWARD_ONLY = 4, } -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_InternalAddressConfig { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig { /** * Whether unix socket addresses should be considered internal. */ 'unix_sockets'?: (boolean); } -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_InternalAddressConfig__Output { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig__Output { /** * Whether unix socket addresses should be considered internal. */ 'unix_sockets': (boolean); } -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing_OperationName { +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName { /** * The HTTP listener is used for ingress/incoming requests. */ @@ -105,9 +106,9 @@ export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnecti EGRESS = 1, } -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ServerHeaderTransformation { +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation { /** * Overwrite any Server header with the contents of server_name. */ @@ -127,7 +128,7 @@ export enum _envoy_config_filter_network_http_connection_manager_v2_HttpConnecti /** * [#next-free-field: 7] */ -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_SetCurrentClientCertDetails { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails { /** * Whether to forward the subject of the client cert. Defaults to false. */ @@ -160,7 +161,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon /** * [#next-free-field: 7] */ -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_SetCurrentClientCertDetails__Output { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails__Output { /** * Whether to forward the subject of the client cert. Defaults to false. */ @@ -193,26 +194,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon /** * [#next-free-field: 10] */ -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing { - /** - * The span name will be derived from this field. If - * :ref:`traffic_direction ` is - * specified on the parent listener, then it is used instead of this field. - * - * .. attention:: - * This field has been deprecated in favor of `traffic_direction`. - */ - 'operation_name'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing_OperationName | keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing_OperationName); - /** - * A list of header names used to create tags for the active span. The header name is used to - * populate the tag name, and the header value is used to populate the tag value. The tag is - * created if the specified header name is present in the request's headers. - * - * .. attention:: - * This field has been deprecated in favor of :ref:`custom_tags - * `. - */ - 'request_headers_for_tags'?: (string)[]; +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing { /** * Target percentage of requests managed by this HTTP connection manager that will be force * traced if the :ref:`x-client-trace-id ` @@ -221,7 +203,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_Percent); + 'client_sampling'?: (_envoy_type_v3_Percent); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -229,7 +211,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_Percent); + 'random_sampling'?: (_envoy_type_v3_Percent); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -240,7 +222,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_Percent); + 'overall_sampling'?: (_envoy_type_v3_Percent); /** * Whether to annotate spans with additional data. If true, spans will include logs for stream * events. @@ -255,7 +237,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon /** * A list of custom tags with unique tag name to create tags for the active span. */ - 'custom_tags'?: (_envoy_type_tracing_v2_CustomTag)[]; + 'custom_tags'?: (_envoy_type_tracing_v3_CustomTag)[]; /** * Configuration for an external tracing provider. * If not specified, no tracing will be performed. @@ -268,32 +250,13 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * Such a constraint is inherent to OpenCensus itself. It cannot be overcome without changes * on OpenCensus side. */ - 'provider'?: (_envoy_config_trace_v2_Tracing_Http); + 'provider'?: (_envoy_config_trace_v3_Tracing_Http); } /** * [#next-free-field: 10] */ -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing__Output { - /** - * The span name will be derived from this field. If - * :ref:`traffic_direction ` is - * specified on the parent listener, then it is used instead of this field. - * - * .. attention:: - * This field has been deprecated in favor of `traffic_direction`. - */ - 'operation_name': (keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing_OperationName); - /** - * A list of header names used to create tags for the active span. The header name is used to - * populate the tag name, and the header value is used to populate the tag value. The tag is - * created if the specified header name is present in the request's headers. - * - * .. attention:: - * This field has been deprecated in favor of :ref:`custom_tags - * `. - */ - 'request_headers_for_tags': (string)[]; +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output { /** * Target percentage of requests managed by this HTTP connection manager that will be force * traced if the :ref:`x-client-trace-id ` @@ -302,7 +265,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_Percent__Output); + 'client_sampling'?: (_envoy_type_v3_Percent__Output); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -310,7 +273,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_Percent__Output); + 'random_sampling'?: (_envoy_type_v3_Percent__Output); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -321,7 +284,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_Percent__Output); + 'overall_sampling'?: (_envoy_type_v3_Percent__Output); /** * Whether to annotate spans with additional data. If true, spans will include logs for stream * events. @@ -336,7 +299,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon /** * A list of custom tags with unique tag name to create tags for the active span. */ - 'custom_tags': (_envoy_type_tracing_v2_CustomTag__Output)[]; + 'custom_tags': (_envoy_type_tracing_v3_CustomTag__Output)[]; /** * Configuration for an external tracing provider. * If not specified, no tracing will be performed. @@ -349,7 +312,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * Such a constraint is inherent to OpenCensus itself. It cannot be overcome without changes * on OpenCensus side. */ - 'provider'?: (_envoy_config_trace_v2_Tracing_Http__Output); + 'provider'?: (_envoy_config_trace_v3_Tracing_Http__Output); } /** @@ -366,7 +329,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * The current implementation of upgrade headers does not work with HTTP/2 * upstreams. */ -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_UpgradeConfig { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig { /** * The case-insensitive name of this upgrade, e.g. "websocket". * For each upgrade type present in upgrade_configs, requests with @@ -379,11 +342,11 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * this type of upgrade. If no filters are present, the filter chain for * HTTP connections will be used for this upgrade type. */ - 'filters'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpFilter)[]; + 'filters'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter)[]; /** * Determines if upgrades are enabled or disabled by default. Defaults to true. * This can be overridden on a per-route basis with :ref:`cluster - * ` as documented in the + * ` as documented in the * :ref:`upgrade documentation `. */ 'enabled'?: (_google_protobuf_BoolValue); @@ -403,7 +366,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * The current implementation of upgrade headers does not work with HTTP/2 * upstreams. */ -export interface _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_UpgradeConfig__Output { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig__Output { /** * The case-insensitive name of this upgrade, e.g. "websocket". * For each upgrade type present in upgrade_configs, requests with @@ -416,24 +379,24 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_HttpCon * this type of upgrade. If no filters are present, the filter chain for * HTTP connections will be used for this upgrade type. */ - 'filters': (_envoy_config_filter_network_http_connection_manager_v2_HttpFilter__Output)[]; + 'filters': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter__Output)[]; /** * Determines if upgrades are enabled or disabled by default. Defaults to true. * This can be overridden on a per-route basis with :ref:`cluster - * ` as documented in the + * ` as documented in the * :ref:`upgrade documentation `. */ 'enabled'?: (_google_protobuf_BoolValue__Output); } /** - * [#next-free-field: 37] + * [#next-free-field: 43] */ export interface HttpConnectionManager { /** * Supplies the type of codec that the connection manager should use. */ - 'codec_type'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_CodecType | keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_CodecType); + 'codec_type'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType); /** * The human readable prefix to use when emitting statistics for the * connection manager. See the :ref:`statistics documentation ` for @@ -443,17 +406,17 @@ export interface HttpConnectionManager { /** * The connection manager’s route table will be dynamically loaded via the RDS API. */ - 'rds'?: (_envoy_config_filter_network_http_connection_manager_v2_Rds); + 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds); /** * The route table for the connection manager is static and is specified in this property. */ - 'route_config'?: (_envoy_api_v2_RouteConfiguration); + 'route_config'?: (_envoy_config_route_v3_RouteConfiguration); /** * A list of individual HTTP filters that make up the filter chain for * requests made to the connection manager. :ref:`Order matters ` * as the filters are processed sequentially as request events happen. */ - 'http_filters'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpFilter)[]; + 'http_filters'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter)[]; /** * Whether the connection manager manipulates the :ref:`config_http_conn_man_headers_user-agent` * and :ref:`config_http_conn_man_headers_downstream-service-cluster` headers. See the linked @@ -463,33 +426,22 @@ export interface HttpConnectionManager { /** * Presence of the object defines whether the connection manager * emits :ref:`tracing ` data to the :ref:`configured tracing provider - * `. + * `. */ - 'tracing'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing); + 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing); /** * Additional HTTP/1 settings that are passed to the HTTP/1 codec. */ - 'http_protocol_options'?: (_envoy_api_v2_core_Http1ProtocolOptions); + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions); /** * Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. */ - 'http2_protocol_options'?: (_envoy_api_v2_core_Http2ProtocolOptions); + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions); /** * An optional override that the connection manager will write to the server * header in responses. If not set, the default is *envoy*. */ 'server_name'?: (string); - /** - * The idle timeout for connections managed by the connection manager. The - * idle timeout is defined as the period in which there are no active - * requests. If not set, there is no idle timeout. When the idle timeout is - * reached the connection will be closed. If the connection is an HTTP/2 - * connection a drain sequence will occur prior to closing the connection. - * This field is deprecated. Use :ref:`idle_timeout - * ` - * instead. - */ - 'idle_timeout'?: (_google_protobuf_Duration); /** * The time that Envoy will wait between sending an HTTP/2 “shutdown * notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. @@ -506,7 +458,7 @@ export interface HttpConnectionManager { * Configuration for :ref:`HTTP access logs ` * emitted by the connection manager. */ - 'access_log'?: (_envoy_config_filter_accesslog_v2_AccessLog)[]; + 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; /** * If set to true, the connection manager will use the real remote address * of the client connection when determining internal versus external origin and manipulating @@ -528,17 +480,17 @@ export interface HttpConnectionManager { * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. */ - 'forward_client_cert_details'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ForwardClientCertDetails | keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ForwardClientCertDetails); + 'forward_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); /** * This field is valid only when :ref:`forward_client_cert_details - * ` + * ` * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in * the client certificate to be forwarded. Note that in the * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and * *By* is always set when the client certificate presents the URI type Subject Alternative Name * value. */ - 'set_current_client_cert_details'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_SetCurrentClientCertDetails); + 'set_current_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails); /** * If proxy_100_continue is true, Envoy will proxy incoming "Expect: * 100-continue" headers upstream, and forward "100 Continue" responses @@ -557,7 +509,7 @@ export interface HttpConnectionManager { /** * If * :ref:`use_remote_address - * ` + * ` * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. * This is useful for testing compatibility of upstream services that parse the header value. For @@ -575,7 +527,7 @@ export interface HttpConnectionManager { * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in * conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager * has mutated the request headers. While :ref:`use_remote_address - * ` + * ` * will also suppress XFF addition, it has consequences for logging and other * Envoy uses of the remote address, so *skip_xff_append* should be used * when only an elision of XFF addition is intended. @@ -586,7 +538,7 @@ export interface HttpConnectionManager { * empty, no via header will be appended. */ 'via'?: (string); - 'upgrade_configs'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_UpgradeConfig)[]; + 'upgrade_configs'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig)[]; /** * The stream idle timeout for connections managed by the connection manager. * If not specified, this defaults to 5 minutes. The default value was selected @@ -596,15 +548,29 @@ export interface HttpConnectionManager { * * This idle timeout applies to new streams and is overridable by the * :ref:`route-level idle_timeout - * `. Even on a stream in + * `. Even on a stream in * which the override applies, prior to receipt of the initial request * headers, the :ref:`stream_idle_timeout - * ` + * ` * applies. Each time an encode/decode event for headers or data is processed * for the stream, the timer will be reset. If the timeout fires, the stream * is terminated with a 408 Request Timeout error code if no upstream response * header has been received, otherwise a stream reset occurs. * + * This timeout also specifies the amount of time that Envoy will wait for the peer to open enough + * window to write any remaining stream data once the entirety of stream data (local end stream is + * true) has been buffered pending available window. In other words, this timeout defends against + * a peer that does not release enough window to completely write the stream, even though all + * data has been proxied within available flow control windows. If the timeout is hit in this + * case, the :ref:`tx_flush_timeout ` counter will be + * incremented. Note that :ref:`max_stream_duration + * ` does not apply to + * this corner case. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * * Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due * to the granularity of events presented to the connection manager. For example, while receiving * very large request headers, it may be the case that there is traffic regularly arriving on the @@ -621,7 +587,7 @@ export interface HttpConnectionManager { * See the documentation for :ref:`config_http_conn_man_headers_x-envoy-internal` for more * information about internal/external addresses. */ - 'internal_address_config'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_InternalAddressConfig); + 'internal_address_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig); /** * The delayed close timeout is for downstream connections managed by the HTTP connection manager. * It is defined as a grace period after connection close processing has been locally initiated @@ -678,10 +644,10 @@ export interface HttpConnectionManager { * true in the future. When not specified, this value may be overridden by the * runtime variable * :ref:`http_connection_manager.normalize_path`. - * See `Normalization and Comparison ` + * See `Normalization and Comparison `_ * for details of normalization. * Note that Envoy does not perform - * `case normalization ` + * `case normalization `_ */ 'normalize_path'?: (_google_protobuf_BoolValue); /** @@ -689,7 +655,7 @@ export interface HttpConnectionManager { * (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are * specified in this message. */ - 'scoped_routes'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes); + 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes); /** * Whether the connection manager will keep the :ref:`x-request-id * ` header if passed for a request that is edge @@ -702,7 +668,7 @@ export interface HttpConnectionManager { * requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without * setting this option, incoming requests with path `//dir///file` will not match against route * with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of - * `HTTP spec ` and is provided for convenience. + * `HTTP spec `_ and is provided for convenience. */ 'merge_slashes'?: (boolean); /** @@ -710,12 +676,12 @@ export interface HttpConnectionManager { * By default, Envoy will overwrite the header with the value specified in * server_name. */ - 'server_header_transformation'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ServerHeaderTransformation | keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ServerHeaderTransformation); + 'server_header_transformation'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation); /** * Additional settings for HTTP requests handled by the connection manager. These will be * applicable to both HTTP1 and HTTP2 requests. */ - 'common_http_protocol_options'?: (_envoy_api_v2_core_HttpProtocolOptions); + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions); /** * The configuration of the request ID extension. This includes operations such as * generation, validation, and associated tracing operations. @@ -728,18 +694,76 @@ export interface HttpConnectionManager { * * 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. */ - 'request_id_extension'?: (_envoy_config_filter_network_http_connection_manager_v2_RequestIDExtension); + 'request_id_extension'?: (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension); + /** + * If set, Envoy will always set :ref:`x-request-id ` header in response. + * If this is false or not set, the request ID is returned in responses only if tracing is forced using + * :ref:`x-envoy-force-trace ` header. + */ + 'always_set_request_id_in_response'?: (boolean); + /** + * The configuration to customize local reply returned by Envoy. It can customize status code, + * body text and response content type. If not specified, status code and text body are hard + * coded in Envoy, the response content type is plain text. + */ + 'local_reply_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + * local port and request method is not CONNECT. This affects the upstream host header as well. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_matching_host_port'?: (boolean); + /** + * Governs Envoy's behavior when receiving invalid HTTP from downstream. + * If this option is false (default), Envoy will err on the conservative side handling HTTP + * errors, terminating both HTTP/1.1 and HTTP/2 connections when receiving an invalid request. + * If this option is set to true, Envoy will be more permissive, only resetting the invalid + * stream in the case of HTTP/2 and leaving the connection open where possible (if the entire + * request is read for HTTP/1.1) + * In general this should be true for deployments receiving trusted traffic (L2 Envoys, + * company-internal mesh) and false when receiving untrusted traffic (edge deployments). + * + * If different behaviors for invalid_http_message for HTTP/1 and HTTP/2 are + * desired, one should use the new HTTP/1 option :ref:`override_stream_error_on_invalid_http_message + * ` or the new HTTP/2 option + * :ref:`override_stream_error_on_invalid_http_message + * ` + * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging + * ` + */ + 'stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue); + /** + * The amount of time that Envoy will wait for the request headers to be received. The timer is + * activated when the first byte of the headers is received, and is disarmed when the last byte of + * the headers has been received. If not specified or set to 0, this timeout is disabled. + */ + 'request_headers_timeout'?: (_google_protobuf_Duration); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. + * This affects the upstream host header as well. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_any_host_port'?: (boolean); 'route_specifier'?: "rds"|"route_config"|"scoped_routes"; + 'strip_port_mode'?: "strip_any_host_port"; } /** - * [#next-free-field: 37] + * [#next-free-field: 43] */ export interface HttpConnectionManager__Output { /** * Supplies the type of codec that the connection manager should use. */ - 'codec_type': (keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_CodecType); + 'codec_type': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType); /** * The human readable prefix to use when emitting statistics for the * connection manager. See the :ref:`statistics documentation ` for @@ -749,17 +773,17 @@ export interface HttpConnectionManager__Output { /** * The connection manager’s route table will be dynamically loaded via the RDS API. */ - 'rds'?: (_envoy_config_filter_network_http_connection_manager_v2_Rds__Output); + 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds__Output); /** * The route table for the connection manager is static and is specified in this property. */ - 'route_config'?: (_envoy_api_v2_RouteConfiguration__Output); + 'route_config'?: (_envoy_config_route_v3_RouteConfiguration__Output); /** * A list of individual HTTP filters that make up the filter chain for * requests made to the connection manager. :ref:`Order matters ` * as the filters are processed sequentially as request events happen. */ - 'http_filters': (_envoy_config_filter_network_http_connection_manager_v2_HttpFilter__Output)[]; + 'http_filters': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter__Output)[]; /** * Whether the connection manager manipulates the :ref:`config_http_conn_man_headers_user-agent` * and :ref:`config_http_conn_man_headers_downstream-service-cluster` headers. See the linked @@ -769,33 +793,22 @@ export interface HttpConnectionManager__Output { /** * Presence of the object defines whether the connection manager * emits :ref:`tracing ` data to the :ref:`configured tracing provider - * `. + * `. */ - 'tracing'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_Tracing__Output); + 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output); /** * Additional HTTP/1 settings that are passed to the HTTP/1 codec. */ - 'http_protocol_options'?: (_envoy_api_v2_core_Http1ProtocolOptions__Output); + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions__Output); /** * Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. */ - 'http2_protocol_options'?: (_envoy_api_v2_core_Http2ProtocolOptions__Output); + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions__Output); /** * An optional override that the connection manager will write to the server * header in responses. If not set, the default is *envoy*. */ 'server_name': (string); - /** - * The idle timeout for connections managed by the connection manager. The - * idle timeout is defined as the period in which there are no active - * requests. If not set, there is no idle timeout. When the idle timeout is - * reached the connection will be closed. If the connection is an HTTP/2 - * connection a drain sequence will occur prior to closing the connection. - * This field is deprecated. Use :ref:`idle_timeout - * ` - * instead. - */ - 'idle_timeout'?: (_google_protobuf_Duration__Output); /** * The time that Envoy will wait between sending an HTTP/2 “shutdown * notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. @@ -812,7 +825,7 @@ export interface HttpConnectionManager__Output { * Configuration for :ref:`HTTP access logs ` * emitted by the connection manager. */ - 'access_log': (_envoy_config_filter_accesslog_v2_AccessLog__Output)[]; + 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; /** * If set to true, the connection manager will use the real remote address * of the client connection when determining internal versus external origin and manipulating @@ -834,17 +847,17 @@ export interface HttpConnectionManager__Output { * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. */ - 'forward_client_cert_details': (keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ForwardClientCertDetails); + 'forward_client_cert_details': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); /** * This field is valid only when :ref:`forward_client_cert_details - * ` + * ` * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in * the client certificate to be forwarded. Note that in the * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and * *By* is always set when the client certificate presents the URI type Subject Alternative Name * value. */ - 'set_current_client_cert_details'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_SetCurrentClientCertDetails__Output); + 'set_current_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails__Output); /** * If proxy_100_continue is true, Envoy will proxy incoming "Expect: * 100-continue" headers upstream, and forward "100 Continue" responses @@ -863,7 +876,7 @@ export interface HttpConnectionManager__Output { /** * If * :ref:`use_remote_address - * ` + * ` * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. * This is useful for testing compatibility of upstream services that parse the header value. For @@ -881,7 +894,7 @@ export interface HttpConnectionManager__Output { * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in * conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager * has mutated the request headers. While :ref:`use_remote_address - * ` + * ` * will also suppress XFF addition, it has consequences for logging and other * Envoy uses of the remote address, so *skip_xff_append* should be used * when only an elision of XFF addition is intended. @@ -892,7 +905,7 @@ export interface HttpConnectionManager__Output { * empty, no via header will be appended. */ 'via': (string); - 'upgrade_configs': (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_UpgradeConfig__Output)[]; + 'upgrade_configs': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_UpgradeConfig__Output)[]; /** * The stream idle timeout for connections managed by the connection manager. * If not specified, this defaults to 5 minutes. The default value was selected @@ -902,15 +915,29 @@ export interface HttpConnectionManager__Output { * * This idle timeout applies to new streams and is overridable by the * :ref:`route-level idle_timeout - * `. Even on a stream in + * `. Even on a stream in * which the override applies, prior to receipt of the initial request * headers, the :ref:`stream_idle_timeout - * ` + * ` * applies. Each time an encode/decode event for headers or data is processed * for the stream, the timer will be reset. If the timeout fires, the stream * is terminated with a 408 Request Timeout error code if no upstream response * header has been received, otherwise a stream reset occurs. * + * This timeout also specifies the amount of time that Envoy will wait for the peer to open enough + * window to write any remaining stream data once the entirety of stream data (local end stream is + * true) has been buffered pending available window. In other words, this timeout defends against + * a peer that does not release enough window to completely write the stream, even though all + * data has been proxied within available flow control windows. If the timeout is hit in this + * case, the :ref:`tx_flush_timeout ` counter will be + * incremented. Note that :ref:`max_stream_duration + * ` does not apply to + * this corner case. + * + * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" + * is configured, this timeout is scaled according to the value for + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * * Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due * to the granularity of events presented to the connection manager. For example, while receiving * very large request headers, it may be the case that there is traffic regularly arriving on the @@ -927,7 +954,7 @@ export interface HttpConnectionManager__Output { * See the documentation for :ref:`config_http_conn_man_headers_x-envoy-internal` for more * information about internal/external addresses. */ - 'internal_address_config'?: (_envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_InternalAddressConfig__Output); + 'internal_address_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig__Output); /** * The delayed close timeout is for downstream connections managed by the HTTP connection manager. * It is defined as a grace period after connection close processing has been locally initiated @@ -984,10 +1011,10 @@ export interface HttpConnectionManager__Output { * true in the future. When not specified, this value may be overridden by the * runtime variable * :ref:`http_connection_manager.normalize_path`. - * See `Normalization and Comparison ` + * See `Normalization and Comparison `_ * for details of normalization. * Note that Envoy does not perform - * `case normalization ` + * `case normalization `_ */ 'normalize_path'?: (_google_protobuf_BoolValue__Output); /** @@ -995,7 +1022,7 @@ export interface HttpConnectionManager__Output { * (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are * specified in this message. */ - 'scoped_routes'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes__Output); + 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes__Output); /** * Whether the connection manager will keep the :ref:`x-request-id * ` header if passed for a request that is edge @@ -1008,7 +1035,7 @@ export interface HttpConnectionManager__Output { * requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without * setting this option, incoming requests with path `//dir///file` will not match against route * with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of - * `HTTP spec ` and is provided for convenience. + * `HTTP spec `_ and is provided for convenience. */ 'merge_slashes': (boolean); /** @@ -1016,12 +1043,12 @@ export interface HttpConnectionManager__Output { * By default, Envoy will overwrite the header with the value specified in * server_name. */ - 'server_header_transformation': (keyof typeof _envoy_config_filter_network_http_connection_manager_v2_HttpConnectionManager_ServerHeaderTransformation); + 'server_header_transformation': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation); /** * Additional settings for HTTP requests handled by the connection manager. These will be * applicable to both HTTP1 and HTTP2 requests. */ - 'common_http_protocol_options'?: (_envoy_api_v2_core_HttpProtocolOptions__Output); + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions__Output); /** * The configuration of the request ID extension. This includes operations such as * generation, validation, and associated tracing operations. @@ -1034,6 +1061,64 @@ export interface HttpConnectionManager__Output { * * 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. */ - 'request_id_extension'?: (_envoy_config_filter_network_http_connection_manager_v2_RequestIDExtension__Output); + 'request_id_extension'?: (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output); + /** + * If set, Envoy will always set :ref:`x-request-id ` header in response. + * If this is false or not set, the request ID is returned in responses only if tracing is forced using + * :ref:`x-envoy-force-trace ` header. + */ + 'always_set_request_id_in_response': (boolean); + /** + * The configuration to customize local reply returned by Envoy. It can customize status code, + * body text and response content type. If not specified, status code and text body are hard + * coded in Envoy, the response content type is plain text. + */ + 'local_reply_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + * local port and request method is not CONNECT. This affects the upstream host header as well. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_matching_host_port': (boolean); + /** + * Governs Envoy's behavior when receiving invalid HTTP from downstream. + * If this option is false (default), Envoy will err on the conservative side handling HTTP + * errors, terminating both HTTP/1.1 and HTTP/2 connections when receiving an invalid request. + * If this option is set to true, Envoy will be more permissive, only resetting the invalid + * stream in the case of HTTP/2 and leaving the connection open where possible (if the entire + * request is read for HTTP/1.1) + * In general this should be true for deployments receiving trusted traffic (L2 Envoys, + * company-internal mesh) and false when receiving untrusted traffic (edge deployments). + * + * If different behaviors for invalid_http_message for HTTP/1 and HTTP/2 are + * desired, one should use the new HTTP/1 option :ref:`override_stream_error_on_invalid_http_message + * ` or the new HTTP/2 option + * :ref:`override_stream_error_on_invalid_http_message + * ` + * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging + * ` + */ + 'stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue__Output); + /** + * The amount of time that Envoy will wait for the request headers to be received. The timer is + * activated when the first byte of the headers is received, and is disarmed when the last byte of + * the headers has been received. If not specified or set to 0, this timeout is disabled. + */ + 'request_headers_timeout'?: (_google_protobuf_Duration__Output); + /** + * Determines if the port part should be removed from host/authority header before any processing + * of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. + * This affects the upstream host header as well. + * Without setting this option, incoming requests with host `example:443` will not match against + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * of `HTTP spec `_ and is provided for convenience. + * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + */ + 'strip_any_host_port'?: (boolean); 'route_specifier': "rds"|"route_config"|"scoped_routes"; + 'strip_port_mode': "strip_any_host_port"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts new file mode 100644 index 000000000..881e2c714 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts @@ -0,0 +1,66 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../../google/protobuf/Any'; +import type { ExtensionConfigSource as _envoy_config_core_v3_ExtensionConfigSource, ExtensionConfigSource__Output as _envoy_config_core_v3_ExtensionConfigSource__Output } from '../../../../../../envoy/config/core/v3/ExtensionConfigSource'; + +/** + * [#next-free-field: 7] + */ +export interface HttpFilter { + /** + * The name of the filter configuration. The name is used as a fallback to + * select an extension if the type of the configuration proto is not + * sufficient. It also serves as a resource name in ExtensionConfigDS. + */ + 'name'?: (string); + /** + * Filter specific configuration which depends on the filter being instantiated. See the supported + * filters for further documentation. + */ + 'typed_config'?: (_google_protobuf_Any); + /** + * Configuration source specifier for an extension configuration discovery service. + * In case of a failure and without the default configuration, the HTTP listener responds with code 500. + * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource); + /** + * If true, clients that do not support this filter may ignore the + * filter but otherwise accept the config. + * Otherwise, clients that do not support this filter must reject the config. + * [#not-implemented-hide:] + */ + 'is_optional'?: (boolean); + 'config_type'?: "typed_config"|"config_discovery"; +} + +/** + * [#next-free-field: 7] + */ +export interface HttpFilter__Output { + /** + * The name of the filter configuration. The name is used as a fallback to + * select an extension if the type of the configuration proto is not + * sufficient. It also serves as a resource name in ExtensionConfigDS. + */ + 'name': (string); + /** + * Filter specific configuration which depends on the filter being instantiated. See the supported + * filters for further documentation. + */ + 'typed_config'?: (_google_protobuf_Any__Output); + /** + * Configuration source specifier for an extension configuration discovery service. + * In case of a failure and without the default configuration, the HTTP listener responds with code 500. + * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output); + /** + * If true, clients that do not support this filter may ignore the + * filter but otherwise accept the config. + * Otherwise, clients that do not support this filter must reject the config. + * [#not-implemented-hide:] + */ + 'is_optional': (boolean); + 'config_type': "typed_config"|"config_discovery"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts new file mode 100644 index 000000000..10bb6e700 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts @@ -0,0 +1,106 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ResponseMapper as _envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper, ResponseMapper__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper'; +import type { SubstitutionFormatString as _envoy_config_core_v3_SubstitutionFormatString, SubstitutionFormatString__Output as _envoy_config_core_v3_SubstitutionFormatString__Output } from '../../../../../../envoy/config/core/v3/SubstitutionFormatString'; + +/** + * The configuration to customize local reply returned by Envoy. + */ +export interface LocalReplyConfig { + /** + * Configuration of list of mappers which allows to filter and change local response. + * The mappers will be checked by the specified order until one is matched. + */ + 'mappers'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper)[]; + /** + * The configuration to form response body from the :ref:`command operators ` + * and to specify response content type as one of: plain/text or application/json. + * + * Example one: "plain/text" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * The following response body in "plain/text" format will be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Example two: "application/json" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * path: "%REQ(:path)%" + * + * The following response body in "application/json" format would be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: json + * + * { + * "status": 503, + * "message": "upstream connection error", + * "path": "/foo" + * } + */ + 'body_format'?: (_envoy_config_core_v3_SubstitutionFormatString); +} + +/** + * The configuration to customize local reply returned by Envoy. + */ +export interface LocalReplyConfig__Output { + /** + * Configuration of list of mappers which allows to filter and change local response. + * The mappers will be checked by the specified order until one is matched. + */ + 'mappers': (_envoy_extensions_filters_network_http_connection_manager_v3_ResponseMapper__Output)[]; + /** + * The configuration to form response body from the :ref:`command operators ` + * and to specify response content type as one of: plain/text or application/json. + * + * Example one: "plain/text" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * text_format: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n" + * + * The following response body in "plain/text" format will be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: text + * + * upstream connect error:503:path=/foo + * + * Example two: "application/json" ``body_format``. + * + * .. validated-code-block:: yaml + * :type-name: envoy.config.core.v3.SubstitutionFormatString + * + * json_format: + * status: "%RESPONSE_CODE%" + * message: "%LOCAL_REPLY_BODY%" + * path: "%REQ(:path)%" + * + * The following response body in "application/json" format would be generated for a request with + * local reply body of "upstream connection error", response_code=503 and path=/foo. + * + * .. code-block:: json + * + * { + * "status": 503, + * "message": "upstream connection error", + * "path": "/foo" + * } + */ + 'body_format'?: (_envoy_config_core_v3_SubstitutionFormatString__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/Rds.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts similarity index 63% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/Rds.ts rename to packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts index be9c038a6..8e06a6d67 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/Rds.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts @@ -1,12 +1,12 @@ -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -import type { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../../../../envoy/api/v2/core/ConfigSource'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../../envoy/config/core/v3/ConfigSource'; export interface Rds { /** * Configuration source specifier for RDS. */ - 'config_source'?: (_envoy_api_v2_core_ConfigSource); + 'config_source'?: (_envoy_config_core_v3_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 @@ -20,7 +20,7 @@ export interface Rds__Output { /** * Configuration source specifier for RDS. */ - 'config_source'?: (_envoy_api_v2_core_ConfigSource__Output); + 'config_source'?: (_envoy_config_core_v3_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 diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/RequestIDExtension.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts similarity index 78% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/RequestIDExtension.ts rename to packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts index 2f043d4a8..24e05ccc3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/RequestIDExtension.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../../google/protobuf/Any'; diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts new file mode 100644 index 000000000..67af3aec4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { AccessLogFilter as _envoy_config_accesslog_v3_AccessLogFilter, AccessLogFilter__Output as _envoy_config_accesslog_v3_AccessLogFilter__Output } from '../../../../../../envoy/config/accesslog/v3/AccessLogFilter'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../../google/protobuf/UInt32Value'; +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../../envoy/config/core/v3/DataSource'; +import type { SubstitutionFormatString as _envoy_config_core_v3_SubstitutionFormatString, SubstitutionFormatString__Output as _envoy_config_core_v3_SubstitutionFormatString__Output } from '../../../../../../envoy/config/core/v3/SubstitutionFormatString'; +import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../../../envoy/config/core/v3/HeaderValueOption'; + +/** + * The configuration to filter and change local response. + * [#next-free-field: 6] + */ +export interface ResponseMapper { + /** + * Filter to determine if this mapper should apply. + */ + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter); + /** + * The new response status code if specified. + */ + 'status_code'?: (_google_protobuf_UInt32Value); + /** + * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` + * command operator in the `body_format`. + */ + 'body'?: (_envoy_config_core_v3_DataSource); + /** + * A per mapper `body_format` to override the :ref:`body_format `. + * It will be used when this mapper is matched. + */ + 'body_format_override'?: (_envoy_config_core_v3_SubstitutionFormatString); + /** + * HTTP headers to add to a local reply. This allows the response mapper to append, to add + * or to override headers of any local reply before it is sent to a downstream client. + */ + 'headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; +} + +/** + * The configuration to filter and change local response. + * [#next-free-field: 6] + */ +export interface ResponseMapper__Output { + /** + * Filter to determine if this mapper should apply. + */ + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter__Output); + /** + * The new response status code if specified. + */ + 'status_code'?: (_google_protobuf_UInt32Value__Output); + /** + * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` + * command operator in the `body_format`. + */ + 'body'?: (_envoy_config_core_v3_DataSource__Output); + /** + * A per mapper `body_format` to override the :ref:`body_format `. + * It will be used when this mapper is matched. + */ + 'body_format_override'?: (_envoy_config_core_v3_SubstitutionFormatString__Output); + /** + * HTTP headers to add to a local reply. This allows the response mapper to append, to add + * or to override headers of any local reply before it is sent to a downstream client. + */ + 'headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts new file mode 100644 index 000000000..a9995b455 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../../envoy/config/core/v3/ConfigSource'; + +export interface ScopedRds { + /** + * Configuration source specifier for scoped RDS. + */ + 'scoped_rds_config_source'?: (_envoy_config_core_v3_ConfigSource); +} + +export interface ScopedRds__Output { + /** + * Configuration source specifier for scoped RDS. + */ + 'scoped_rds_config_source'?: (_envoy_config_core_v3_ConfigSource__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList.ts new file mode 100644 index 000000000..e7f05e340 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { ScopedRouteConfiguration as _envoy_config_route_v3_ScopedRouteConfiguration, ScopedRouteConfiguration__Output as _envoy_config_route_v3_ScopedRouteConfiguration__Output } from '../../../../../../envoy/config/route/v3/ScopedRouteConfiguration'; + +/** + * This message is used to work around the limitations with 'oneof' and repeated fields. + */ +export interface ScopedRouteConfigurationsList { + 'scoped_route_configurations'?: (_envoy_config_route_v3_ScopedRouteConfiguration)[]; +} + +/** + * This message is used to work around the limitations with 'oneof' and repeated fields. + */ +export interface ScopedRouteConfigurationsList__Output { + 'scoped_route_configurations': (_envoy_config_route_v3_ScopedRouteConfiguration__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRoutes.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts similarity index 57% rename from packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRoutes.ts rename to packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts index b9b20d2bf..a19e59b2a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/filter/network/http_connection_manager/v2/ScopedRoutes.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts @@ -1,28 +1,28 @@ -// Original file: deps/envoy-api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -import type { ConfigSource as _envoy_api_v2_core_ConfigSource, ConfigSource__Output as _envoy_api_v2_core_ConfigSource__Output } from '../../../../../../envoy/api/v2/core/ConfigSource'; -import type { 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 type { 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'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../../envoy/config/core/v3/ConfigSource'; +import type { ScopedRouteConfigurationsList as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList, ScopedRouteConfigurationsList__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/ScopedRouteConfigurationsList'; +import type { ScopedRds as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds, ScopedRds__Output as _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/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 { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_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); + 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_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 { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_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); + 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output); 'type': "header_value_extractor"; } @@ -45,9 +45,13 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedR * * 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 { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor { /** * The name of the header field to extract the value from. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ 'name'?: (string); /** @@ -66,7 +70,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedR /** * Specifies the key value pair to extract the value from. */ - 'element'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement); + 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement); 'extract_type'?: "index"|"element"; } @@ -89,9 +93,13 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedR * * 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 { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output { /** * The name of the header field to extract the value from. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. */ 'name': (string); /** @@ -110,14 +118,14 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedR /** * 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); + 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_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 { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_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. @@ -135,7 +143,7 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedR /** * 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 { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_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. @@ -152,42 +160,42 @@ export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedR /** * Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These - * keys are matched against a set of :ref:`Key` - * objects assembled from :ref:`ScopedRouteConfiguration` + * keys are matched against a set of :ref:`Key` + * objects assembled from :ref:`ScopedRouteConfiguration` * messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - * :ref:`scoped_route_configurations_list`. + * :ref:`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`) to use for the request. + * :ref:`RouteConfiguration`) to use for the request. */ -export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_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`. + * fragments of a :ref:`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)[]; + 'fragments'?: (_envoy_extensions_filters_network_http_connection_manager_v3_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` - * objects assembled from :ref:`ScopedRouteConfiguration` + * keys are matched against a set of :ref:`Key` + * objects assembled from :ref:`ScopedRouteConfiguration` * messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - * :ref:`scoped_route_configurations_list`. + * :ref:`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`) to use for the request. + * :ref:`RouteConfiguration`) to use for the request. */ -export interface _envoy_config_filter_network_http_connection_manager_v2_ScopedRoutes_ScopeKeyBuilder__Output { +export interface _envoy_extensions_filters_network_http_connection_manager_v3_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`. + * fragments of a :ref:`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)[]; + 'fragments': (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder__Output)[]; } /** @@ -201,29 +209,29 @@ export interface ScopedRoutes { /** * 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); + 'scope_key_builder'?: (_envoy_extensions_filters_network_http_connection_manager_v3_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); + 'rds_config_source'?: (_envoy_config_core_v3_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` + * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_route_configurations_list'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRouteConfigurationsList); + 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_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` + * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_rds'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRds); + 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds); 'config_specifier'?: "scoped_route_configurations_list"|"scoped_rds"; } @@ -238,28 +246,28 @@ export interface ScopedRoutes__Output { /** * 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); + 'scope_key_builder'?: (_envoy_extensions_filters_network_http_connection_manager_v3_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); + 'rds_config_source'?: (_envoy_config_core_v3_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` + * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_route_configurations_list'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRouteConfigurationsList__Output); + 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_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` + * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_rds'?: (_envoy_config_filter_network_http_connection_manager_v2_ScopedRds__Output); + 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds__Output); 'config_specifier': "scoped_route_configurations_list"|"scoped_rds"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AdsDummy.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AdsDummy.ts new file mode 100644 index 000000000..c15510877 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AdsDummy.ts @@ -0,0 +1,16 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/ads.proto + + +/** + * [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing + * services: https://github.com/google/protobuf/issues/4221 + */ +export interface AdsDummy { +} + +/** + * [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing + * services: https://github.com/google/protobuf/issues/4221 + */ +export interface AdsDummy__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts new file mode 100644 index 000000000..445057d44 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts @@ -0,0 +1,52 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/ads.proto + +import type * as grpc from '@grpc/grpc-js' +import type { DeltaDiscoveryRequest as _envoy_service_discovery_v3_DeltaDiscoveryRequest, DeltaDiscoveryRequest__Output as _envoy_service_discovery_v3_DeltaDiscoveryRequest__Output } from '../../../../envoy/service/discovery/v3/DeltaDiscoveryRequest'; +import type { DeltaDiscoveryResponse as _envoy_service_discovery_v3_DeltaDiscoveryResponse, DeltaDiscoveryResponse__Output as _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output } from '../../../../envoy/service/discovery/v3/DeltaDiscoveryResponse'; +import type { DiscoveryRequest as _envoy_service_discovery_v3_DiscoveryRequest, DiscoveryRequest__Output as _envoy_service_discovery_v3_DiscoveryRequest__Output } from '../../../../envoy/service/discovery/v3/DiscoveryRequest'; +import type { DiscoveryResponse as _envoy_service_discovery_v3_DiscoveryResponse, DiscoveryResponse__Output as _envoy_service_discovery_v3_DiscoveryResponse__Output } from '../../../../envoy/service/discovery/v3/DiscoveryResponse'; + +/** + * See https://github.com/lyft/envoy-api#apis for a description of the role of + * ADS and how it is intended to be used by a management server. ADS requests + * have the same structure as their singleton xDS counterparts, but can + * multiplex many resource types on a single stream. The type_url in the + * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover + * the multiplexed singleton APIs at the Envoy instance and management server. + */ +export interface AggregatedDiscoveryServiceClient extends grpc.Client { + DeltaAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + DeltaAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + deltaAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + deltaAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output>; + + /** + * This is a gRPC-only API. + */ + StreamAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + StreamAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + /** + * This is a gRPC-only API. + */ + streamAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + streamAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse__Output>; + +} + +/** + * See https://github.com/lyft/envoy-api#apis for a description of the role of + * ADS and how it is intended to be used by a management server. ADS requests + * have the same structure as their singleton xDS counterparts, but can + * multiplex many resource types on a single stream. The type_url in the + * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover + * the multiplexed singleton APIs at the Envoy instance and management server. + */ +export interface AggregatedDiscoveryServiceHandlers extends grpc.UntypedServiceImplementation { + DeltaAggregatedResources: grpc.handleBidiStreamingCall<_envoy_service_discovery_v3_DeltaDiscoveryRequest__Output, _envoy_service_discovery_v3_DeltaDiscoveryResponse>; + + /** + * This is a gRPC-only API. + */ + StreamAggregatedResources: grpc.handleBidiStreamingCall<_envoy_service_discovery_v3_DiscoveryRequest__Output, _envoy_service_discovery_v3_DiscoveryResponse>; + +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts new file mode 100644 index 000000000..bcf2de4e2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts @@ -0,0 +1,206 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../../google/rpc/Status'; + +/** + * DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC + * endpoint for Delta xDS. + * + * With Delta xDS, the DeltaDiscoveryResponses do not need to include a full + * snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a + * diff to the state of a xDS client. + * In Delta XDS there are per-resource versions, which allow tracking state at + * the resource granularity. + * An xDS Delta session is always in the context of a gRPC bidirectional + * stream. This allows the xDS server to keep track of the state of xDS clients + * connected to it. + * + * In Delta xDS the nonce field is required and used to pair + * DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. + * Optionally, a response message level system_version_info is present for + * debugging purposes only. + * + * DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest + * can be either or both of: [1] informing the server of what resources the + * client has gained/lost interest in (using resource_names_subscribe and + * resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from + * the server (using response_nonce, with presence of error_detail making it a NACK). + * Additionally, the first message (for a given type_url) of a reconnected gRPC stream + * has a third role: informing the server of the resources (and their versions) + * that the client already possesses, using the initial_resource_versions field. + * + * As with state-of-the-world, when multiple resource types are multiplexed (ADS), + * all requests/acknowledgments/updates are logically walled off by type_url: + * a Cluster ACK exists in a completely separate world from a prior Route NACK. + * In particular, initial_resource_versions being sent at the "start" of every + * gRPC stream actually entails a message for each type_url, each with its own + * initial_resource_versions. + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryRequest { + /** + * The node making the request. + */ + 'node'?: (_envoy_config_core_v3_Node); + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if + * resources are only referenced via *xds_resource_subscribe* and + * *xds_resources_unsubscribe*. + */ + 'type_url'?: (string); + /** + * DeltaDiscoveryRequests allow the client to add or remove individual + * resources to the set of tracked resources in the context of a stream. + * All resource names in the resource_names_subscribe list are added to the + * set of tracked resources and all resource names in the resource_names_unsubscribe + * list are removed from the set of tracked resources. + * + * *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or + * resource_names_unsubscribe list simply means that no resources are to be + * added or removed to the resource list. + * *Like* state-of-the-world xDS, the server must send updates for all tracked + * resources, but can also send updates for resources the client has not subscribed to. + * + * NOTE: the server must respond with all resources listed in resource_names_subscribe, + * even if it believes the client has the most recent version of them. The reason: + * the client may have dropped them, but then regained interest before it had a chance + * to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. + * + * These two fields can be set in any DeltaDiscoveryRequest, including ACKs + * and initial_resource_versions. + * + * A list of Resource names to add to the list of tracked resources. + */ + 'resource_names_subscribe'?: (string)[]; + /** + * A list of Resource names to remove from the list of tracked resources. + */ + 'resource_names_unsubscribe'?: (string)[]; + /** + * Informs the server of the versions of the resources the xDS client knows of, to enable the + * client to continue the same logical xDS session even in the face of gRPC stream reconnection. + * It will not be populated: [1] in the very first stream of a session, since the client will + * not yet have any resources, [2] in any message after the first in a stream (for a given + * type_url), since the server will already be correctly tracking the client's state. + * (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) + * The map's keys are names of xDS resources known to the xDS client. + * The map's values are opaque resource versions. + */ + 'initial_resource_versions'?: ({[key: string]: string}); + /** + * When the DeltaDiscoveryRequest is a ACK or NACK message in response + * to a previous DeltaDiscoveryResponse, the response_nonce must be the + * nonce in the DeltaDiscoveryResponse. + * Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. + */ + 'response_nonce'?: (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* + * provides the Envoy internal exception related to the failure. + */ + 'error_detail'?: (_google_rpc_Status); +} + +/** + * DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC + * endpoint for Delta xDS. + * + * With Delta xDS, the DeltaDiscoveryResponses do not need to include a full + * snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a + * diff to the state of a xDS client. + * In Delta XDS there are per-resource versions, which allow tracking state at + * the resource granularity. + * An xDS Delta session is always in the context of a gRPC bidirectional + * stream. This allows the xDS server to keep track of the state of xDS clients + * connected to it. + * + * In Delta xDS the nonce field is required and used to pair + * DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. + * Optionally, a response message level system_version_info is present for + * debugging purposes only. + * + * DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest + * can be either or both of: [1] informing the server of what resources the + * client has gained/lost interest in (using resource_names_subscribe and + * resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from + * the server (using response_nonce, with presence of error_detail making it a NACK). + * Additionally, the first message (for a given type_url) of a reconnected gRPC stream + * has a third role: informing the server of the resources (and their versions) + * that the client already possesses, using the initial_resource_versions field. + * + * As with state-of-the-world, when multiple resource types are multiplexed (ADS), + * all requests/acknowledgments/updates are logically walled off by type_url: + * a Cluster ACK exists in a completely separate world from a prior Route NACK. + * In particular, initial_resource_versions being sent at the "start" of every + * gRPC stream actually entails a message for each type_url, each with its own + * initial_resource_versions. + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryRequest__Output { + /** + * The node making the request. + */ + 'node'?: (_envoy_config_core_v3_Node__Output); + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if + * resources are only referenced via *xds_resource_subscribe* and + * *xds_resources_unsubscribe*. + */ + 'type_url': (string); + /** + * DeltaDiscoveryRequests allow the client to add or remove individual + * resources to the set of tracked resources in the context of a stream. + * All resource names in the resource_names_subscribe list are added to the + * set of tracked resources and all resource names in the resource_names_unsubscribe + * list are removed from the set of tracked resources. + * + * *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or + * resource_names_unsubscribe list simply means that no resources are to be + * added or removed to the resource list. + * *Like* state-of-the-world xDS, the server must send updates for all tracked + * resources, but can also send updates for resources the client has not subscribed to. + * + * NOTE: the server must respond with all resources listed in resource_names_subscribe, + * even if it believes the client has the most recent version of them. The reason: + * the client may have dropped them, but then regained interest before it had a chance + * to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. + * + * These two fields can be set in any DeltaDiscoveryRequest, including ACKs + * and initial_resource_versions. + * + * A list of Resource names to add to the list of tracked resources. + */ + 'resource_names_subscribe': (string)[]; + /** + * A list of Resource names to remove from the list of tracked resources. + */ + 'resource_names_unsubscribe': (string)[]; + /** + * Informs the server of the versions of the resources the xDS client knows of, to enable the + * client to continue the same logical xDS session even in the face of gRPC stream reconnection. + * It will not be populated: [1] in the very first stream of a session, since the client will + * not yet have any resources, [2] in any message after the first in a stream (for a given + * type_url), since the server will already be correctly tracking the client's state. + * (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) + * The map's keys are names of xDS resources known to the xDS client. + * The map's values are opaque resource versions. + */ + 'initial_resource_versions': ({[key: string]: string}); + /** + * When the DeltaDiscoveryRequest is a ACK or NACK message in response + * to a previous DeltaDiscoveryResponse, the response_nonce must be the + * nonce in the DeltaDiscoveryResponse. + * Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. + */ + 'response_nonce': (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* + * provides the Envoy internal exception related to the failure. + */ + 'error_detail'?: (_google_rpc_Status__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts new file mode 100644 index 000000000..c22690cd9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts @@ -0,0 +1,74 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Resource as _envoy_service_discovery_v3_Resource, Resource__Output as _envoy_service_discovery_v3_Resource__Output } from '../../../../envoy/service/discovery/v3/Resource'; +import type { ControlPlane as _envoy_config_core_v3_ControlPlane, ControlPlane__Output as _envoy_config_core_v3_ControlPlane__Output } from '../../../../envoy/config/core/v3/ControlPlane'; + +/** + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryResponse { + /** + * The version of the response data (used for debugging). + */ + 'system_version_info'?: (string); + /** + * The response resources. These are typed resources, whose types must match + * the type_url field. + */ + 'resources'?: (_envoy_service_discovery_v3_Resource)[]; + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. + */ + 'type_url'?: (string); + /** + * The nonce provides a way for DeltaDiscoveryRequests to uniquely + * reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. + */ + 'nonce'?: (string); + /** + * Resources names of resources that have be deleted and to be removed from the xDS Client. + * Removed resources for missing resources can be ignored. + */ + 'removed_resources'?: (string)[]; + /** + * [#not-implemented-hide:] + * The control plane instance that sent the response. + */ + 'control_plane'?: (_envoy_config_core_v3_ControlPlane); +} + +/** + * [#next-free-field: 8] + */ +export interface DeltaDiscoveryResponse__Output { + /** + * The version of the response data (used for debugging). + */ + 'system_version_info': (string); + /** + * The response resources. These are typed resources, whose types must match + * the type_url field. + */ + 'resources': (_envoy_service_discovery_v3_Resource__Output)[]; + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. + */ + 'type_url': (string); + /** + * The nonce provides a way for DeltaDiscoveryRequests to uniquely + * reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. + */ + 'nonce': (string); + /** + * Resources names of resources that have be deleted and to be removed from the xDS Client. + * Removed resources for missing resources can be ignored. + */ + 'removed_resources': (string)[]; + /** + * [#not-implemented-hide:] + * The control plane instance that sent the response. + */ + 'control_plane'?: (_envoy_config_core_v3_ControlPlane__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts new file mode 100644 index 000000000..2b23ee869 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts @@ -0,0 +1,110 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../../google/rpc/Status'; + +/** + * A DiscoveryRequest requests a set of versioned resources of the same type for + * a given Envoy node on some API. + * [#next-free-field: 7] + */ +export interface DiscoveryRequest { + /** + * The version_info provided in the request messages will be the version_info + * received with the most recent successfully processed response or empty on + * the first request. It is expected that no new request is sent after a + * response is received until the Envoy instance is ready to ACK/NACK the new + * configuration. ACK/NACK takes place by returning the new API config version + * as applied or the previous API config version respectively. Each type_url + * (see below) has an independent version associated with it. + */ + 'version_info'?: (string); + /** + * The node making the request. + */ + 'node'?: (_envoy_config_core_v3_Node); + /** + * List of resources to subscribe to, e.g. list of cluster names or a route + * configuration name. If this is empty, all resources for the API are + * returned. LDS/CDS may have empty resource_names, which will cause all + * resources for the Envoy instance to be returned. The LDS and CDS responses + * will then imply a number of resources that need to be fetched via EDS/RDS, + * which will be explicitly enumerated in resource_names. + */ + 'resource_names'?: (string)[]; + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This is implicit + * in requests made via singleton xDS APIs such as CDS, LDS, etc. but is + * required for ADS. + */ + 'type_url'?: (string); + /** + * nonce corresponding to DiscoveryResponse being ACK/NACKed. See above + * discussion on version_info and the DiscoveryResponse nonce comment. This + * may be empty only if 1) this is a non-persistent-stream xDS such as HTTP, + * or 2) the client has not yet accepted an update in this xDS stream (unlike + * delta, where it is populated only for new explicit ACKs). + */ + 'response_nonce'?: (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* provides the Envoy + * internal exception related to the failure. It is only intended for consumption during manual + * debugging, the string provided is not guaranteed to be stable across Envoy versions. + */ + 'error_detail'?: (_google_rpc_Status); +} + +/** + * A DiscoveryRequest requests a set of versioned resources of the same type for + * a given Envoy node on some API. + * [#next-free-field: 7] + */ +export interface DiscoveryRequest__Output { + /** + * The version_info provided in the request messages will be the version_info + * received with the most recent successfully processed response or empty on + * the first request. It is expected that no new request is sent after a + * response is received until the Envoy instance is ready to ACK/NACK the new + * configuration. ACK/NACK takes place by returning the new API config version + * as applied or the previous API config version respectively. Each type_url + * (see below) has an independent version associated with it. + */ + 'version_info': (string); + /** + * The node making the request. + */ + 'node'?: (_envoy_config_core_v3_Node__Output); + /** + * List of resources to subscribe to, e.g. list of cluster names or a route + * configuration name. If this is empty, all resources for the API are + * returned. LDS/CDS may have empty resource_names, which will cause all + * resources for the Envoy instance to be returned. The LDS and CDS responses + * will then imply a number of resources that need to be fetched via EDS/RDS, + * which will be explicitly enumerated in resource_names. + */ + 'resource_names': (string)[]; + /** + * Type of the resource that is being requested, e.g. + * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This is implicit + * in requests made via singleton xDS APIs such as CDS, LDS, etc. but is + * required for ADS. + */ + 'type_url': (string); + /** + * nonce corresponding to DiscoveryResponse being ACK/NACKed. See above + * discussion on version_info and the DiscoveryResponse nonce comment. This + * may be empty only if 1) this is a non-persistent-stream xDS such as HTTP, + * or 2) the client has not yet accepted an update in this xDS stream (unlike + * delta, where it is populated only for new explicit ACKs). + */ + 'response_nonce': (string); + /** + * This is populated when the previous :ref:`DiscoveryResponse ` + * failed to update configuration. The *message* field in *error_details* provides the Envoy + * internal exception related to the failure. It is only intended for consumption during manual + * debugging, the string provided is not guaranteed to be stable across Envoy versions. + */ + 'error_detail'?: (_google_rpc_Status__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts new file mode 100644 index 000000000..f69944b80 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts @@ -0,0 +1,106 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { ControlPlane as _envoy_config_core_v3_ControlPlane, ControlPlane__Output as _envoy_config_core_v3_ControlPlane__Output } from '../../../../envoy/config/core/v3/ControlPlane'; + +/** + * [#next-free-field: 7] + */ +export interface DiscoveryResponse { + /** + * The version of the response data. + */ + 'version_info'?: (string); + /** + * The response resources. These resources are typed and depend on the API being called. + */ + 'resources'?: (_google_protobuf_Any)[]; + /** + * [#not-implemented-hide:] + * Canary is used to support two Envoy command line flags: + * + * * --terminate-on-canary-transition-failure. When set, Envoy is able to + * terminate if it detects that configuration is stuck at canary. Consider + * this example sequence of updates: + * - Management server applies a canary config successfully. + * - Management server rolls back to a production config. + * - Envoy rejects the new production config. + * Since there is no sensible way to continue receiving configuration + * updates, Envoy will then terminate and apply production config from a + * clean slate. + * * --dry-run-canary. When set, a canary response will never be applied, only + * validated via a dry run. + */ + 'canary'?: (boolean); + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). + */ + 'type_url'?: (string); + /** + * For gRPC based subscriptions, the nonce provides a way to explicitly ack a + * specific DiscoveryResponse in a following DiscoveryRequest. Additional + * messages may have been sent by Envoy to the management server for the + * previous version on the stream prior to this DiscoveryResponse, that were + * unprocessed at response send time. The nonce allows the management server + * to ignore any further DiscoveryRequests for the previous version until a + * DiscoveryRequest bearing the nonce. The nonce is optional and is not + * required for non-stream based xDS implementations. + */ + 'nonce'?: (string); + /** + * The control plane instance that sent the response. + */ + 'control_plane'?: (_envoy_config_core_v3_ControlPlane); +} + +/** + * [#next-free-field: 7] + */ +export interface DiscoveryResponse__Output { + /** + * The version of the response data. + */ + 'version_info': (string); + /** + * The response resources. These resources are typed and depend on the API being called. + */ + 'resources': (_google_protobuf_Any__Output)[]; + /** + * [#not-implemented-hide:] + * Canary is used to support two Envoy command line flags: + * + * * --terminate-on-canary-transition-failure. When set, Envoy is able to + * terminate if it detects that configuration is stuck at canary. Consider + * this example sequence of updates: + * - Management server applies a canary config successfully. + * - Management server rolls back to a production config. + * - Envoy rejects the new production config. + * Since there is no sensible way to continue receiving configuration + * updates, Envoy will then terminate and apply production config from a + * clean slate. + * * --dry-run-canary. When set, a canary response will never be applied, only + * validated via a dry run. + */ + 'canary': (boolean); + /** + * Type URL for resources. Identifies the xDS API when muxing over ADS. + * Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). + */ + 'type_url': (string); + /** + * For gRPC based subscriptions, the nonce provides a way to explicitly ack a + * specific DiscoveryResponse in a following DiscoveryRequest. Additional + * messages may have been sent by Envoy to the management server for the + * previous version on the stream prior to this DiscoveryResponse, that were + * unprocessed at response send time. The nonce allows the management server + * to ignore any further DiscoveryRequests for the previous version until a + * DiscoveryRequest bearing the nonce. The nonce is optional and is not + * required for non-stream based xDS implementations. + */ + 'nonce': (string); + /** + * The control plane instance that sent the response. + */ + 'control_plane'?: (_envoy_config_core_v3_ControlPlane__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts new file mode 100644 index 000000000..27a70c97c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts @@ -0,0 +1,118 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ +export interface _envoy_service_discovery_v3_Resource_CacheControl { + /** + * If true, xDS proxies may not cache this resource. + * Note that this does not apply to clients other than xDS proxies, which must cache resources + * for their own use, regardless of the value of this field. + */ + 'do_not_cache'?: (boolean); +} + +/** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ +export interface _envoy_service_discovery_v3_Resource_CacheControl__Output { + /** + * If true, xDS proxies may not cache this resource. + * Note that this does not apply to clients other than xDS proxies, which must cache resources + * for their own use, regardless of the value of this field. + */ + 'do_not_cache': (boolean); +} + +/** + * [#next-free-field: 8] + */ +export interface Resource { + /** + * The resource level version. It allows xDS to track the state of individual + * resources. + */ + 'version'?: (string); + /** + * The resource being tracked. + */ + 'resource'?: (_google_protobuf_Any); + /** + * The resource's name, to distinguish it from others of the same type of resource. + */ + 'name'?: (string); + /** + * The aliases are a list of other names that this resource can go by. + */ + 'aliases'?: (string)[]; + /** + * Time-to-live value for the resource. For each resource, a timer is started. The timer is + * reset each time the resource is received with a new TTL. If the resource is received with + * no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + * configuration for the resource will be removed. + * + * The TTL can be refreshed or changed by sending a response that doesn't change the resource + * version. In this case the resource field does not need to be populated, which allows for + * light-weight "heartbeat" updates to keep a resource with a TTL alive. + * + * The TTL feature is meant to support configurations that should be removed in the event of + * a management server failure. For example, the feature may be used for fault injection + * testing where the fault injection should be terminated in the event that Envoy loses contact + * with the management server. + */ + 'ttl'?: (_google_protobuf_Duration); + /** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ + 'cache_control'?: (_envoy_service_discovery_v3_Resource_CacheControl); +} + +/** + * [#next-free-field: 8] + */ +export interface Resource__Output { + /** + * The resource level version. It allows xDS to track the state of individual + * resources. + */ + 'version': (string); + /** + * The resource being tracked. + */ + 'resource'?: (_google_protobuf_Any__Output); + /** + * The resource's name, to distinguish it from others of the same type of resource. + */ + 'name': (string); + /** + * The aliases are a list of other names that this resource can go by. + */ + 'aliases': (string)[]; + /** + * Time-to-live value for the resource. For each resource, a timer is started. The timer is + * reset each time the resource is received with a new TTL. If the resource is received with + * no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + * configuration for the resource will be removed. + * + * The TTL can be refreshed or changed by sending a response that doesn't change the resource + * version. In this case the resource field does not need to be populated, which allows for + * light-weight "heartbeat" updates to keep a resource with a TTL alive. + * + * The TTL feature is meant to support configurations that should be removed in the event of + * a management server failure. For example, the feature may be used for fault injection + * testing where the fault injection should be terminated in the event that Envoy loses contact + * with the management server. + */ + 'ttl'?: (_google_protobuf_Duration__Output); + /** + * Cache control properties for the resource. + * [#not-implemented-hide:] + */ + 'cache_control'?: (_envoy_service_discovery_v3_Resource_CacheControl__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts new file mode 100644 index 000000000..24a9de55e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts @@ -0,0 +1,108 @@ +// Original file: deps/envoy-api/envoy/service/load_stats/v3/lrs.proto + +import type * as grpc from '@grpc/grpc-js' +import type { LoadStatsRequest as _envoy_service_load_stats_v3_LoadStatsRequest, LoadStatsRequest__Output as _envoy_service_load_stats_v3_LoadStatsRequest__Output } from '../../../../envoy/service/load_stats/v3/LoadStatsRequest'; +import type { LoadStatsResponse as _envoy_service_load_stats_v3_LoadStatsResponse, LoadStatsResponse__Output as _envoy_service_load_stats_v3_LoadStatsResponse__Output } from '../../../../envoy/service/load_stats/v3/LoadStatsResponse'; + +export interface LoadReportingServiceClient extends grpc.Client { + /** + * Advanced API to allow for multi-dimensional load balancing by remote + * server. For receiving LB assignments, the steps are: + * 1, The management server is configured with per cluster/zone/load metric + * capacity configuration. The capacity configuration definition is + * outside of the scope of this document. + * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + * to balance. + * + * Independently, Envoy will initiate a StreamLoadStats bidi stream with a + * management server: + * 1. Once a connection establishes, the management server publishes a + * LoadStatsResponse for all clusters it is interested in learning load + * stats about. + * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + * based on per-zone weights and/or per-instance weights (if specified) + * based on intra-zone LbPolicy. This information comes from the above + * {Stream,Fetch}Endpoints. + * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + * 4. Envoy aggregates load reports over the period of time given to it in + * LoadStatsResponse.load_reporting_interval. This includes aggregation + * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + * well as load metrics from upstream hosts. + * 5. When the timer of load_reporting_interval expires, Envoy sends new + * LoadStatsRequest filled with load reports for each cluster. + * 6. The management server uses the load reports from all reported Envoys + * from around the world, computes global assignment and prepares traffic + * assignment destined for each zone Envoys are located in. Goto 2. + */ + StreamLoadStats(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + StreamLoadStats(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + /** + * Advanced API to allow for multi-dimensional load balancing by remote + * server. For receiving LB assignments, the steps are: + * 1, The management server is configured with per cluster/zone/load metric + * capacity configuration. The capacity configuration definition is + * outside of the scope of this document. + * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + * to balance. + * + * Independently, Envoy will initiate a StreamLoadStats bidi stream with a + * management server: + * 1. Once a connection establishes, the management server publishes a + * LoadStatsResponse for all clusters it is interested in learning load + * stats about. + * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + * based on per-zone weights and/or per-instance weights (if specified) + * based on intra-zone LbPolicy. This information comes from the above + * {Stream,Fetch}Endpoints. + * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + * 4. Envoy aggregates load reports over the period of time given to it in + * LoadStatsResponse.load_reporting_interval. This includes aggregation + * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + * well as load metrics from upstream hosts. + * 5. When the timer of load_reporting_interval expires, Envoy sends new + * LoadStatsRequest filled with load reports for each cluster. + * 6. The management server uses the load reports from all reported Envoys + * from around the world, computes global assignment and prepares traffic + * assignment destined for each zone Envoys are located in. Goto 2. + */ + streamLoadStats(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + streamLoadStats(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse__Output>; + +} + +export interface LoadReportingServiceHandlers extends grpc.UntypedServiceImplementation { + /** + * Advanced API to allow for multi-dimensional load balancing by remote + * server. For receiving LB assignments, the steps are: + * 1, The management server is configured with per cluster/zone/load metric + * capacity configuration. The capacity configuration definition is + * outside of the scope of this document. + * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + * to balance. + * + * Independently, Envoy will initiate a StreamLoadStats bidi stream with a + * management server: + * 1. Once a connection establishes, the management server publishes a + * LoadStatsResponse for all clusters it is interested in learning load + * stats about. + * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + * based on per-zone weights and/or per-instance weights (if specified) + * based on intra-zone LbPolicy. This information comes from the above + * {Stream,Fetch}Endpoints. + * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + * 4. Envoy aggregates load reports over the period of time given to it in + * LoadStatsResponse.load_reporting_interval. This includes aggregation + * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + * well as load metrics from upstream hosts. + * 5. When the timer of load_reporting_interval expires, Envoy sends new + * LoadStatsRequest filled with load reports for each cluster. + * 6. The management server uses the load reports from all reported Envoys + * from around the world, computes global assignment and prepares traffic + * assignment destined for each zone Envoys are located in. Goto 2. + */ + StreamLoadStats: grpc.handleBidiStreamingCall<_envoy_service_load_stats_v3_LoadStatsRequest__Output, _envoy_service_load_stats_v3_LoadStatsResponse>; + +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts new file mode 100644 index 000000000..6f0e19525 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts @@ -0,0 +1,32 @@ +// Original file: deps/envoy-api/envoy/service/load_stats/v3/lrs.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { ClusterStats as _envoy_config_endpoint_v3_ClusterStats, ClusterStats__Output as _envoy_config_endpoint_v3_ClusterStats__Output } from '../../../../envoy/config/endpoint/v3/ClusterStats'; + +/** + * A load report Envoy sends to the management server. + */ +export interface LoadStatsRequest { + /** + * Node identifier for Envoy instance. + */ + 'node'?: (_envoy_config_core_v3_Node); + /** + * A list of load stats to report. + */ + 'cluster_stats'?: (_envoy_config_endpoint_v3_ClusterStats)[]; +} + +/** + * A load report Envoy sends to the management server. + */ +export interface LoadStatsRequest__Output { + /** + * Node identifier for Envoy instance. + */ + 'node'?: (_envoy_config_core_v3_Node__Output); + /** + * A list of load stats to report. + */ + 'cluster_stats': (_envoy_config_endpoint_v3_ClusterStats__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts new file mode 100644 index 000000000..51f017474 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts @@ -0,0 +1,71 @@ +// Original file: deps/envoy-api/envoy/service/load_stats/v3/lrs.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * The management server sends envoy a LoadStatsResponse with all clusters it + * is interested in learning load stats about. + */ +export interface LoadStatsResponse { + /** + * Clusters to report stats for. + * Not populated if *send_all_clusters* is true. + */ + 'clusters'?: (string)[]; + /** + * The minimum interval of time to collect stats over. This is only a minimum for two reasons: + * + * 1. There may be some delay from when the timer fires until stats sampling occurs. + * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic + * that is observed in between the corresponding previous *LoadStatsRequest* and this + * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + * of inobservability that might otherwise exists between the messages. New clusters are not + * subject to this consideration. + */ + 'load_reporting_interval'?: (_google_protobuf_Duration); + /** + * Set to *true* if the management server supports endpoint granularity + * report. + */ + 'report_endpoint_granularity'?: (boolean); + /** + * If true, the client should send all clusters it knows about. + * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their + * :ref:`client_features` field will honor this field. + */ + 'send_all_clusters'?: (boolean); +} + +/** + * The management server sends envoy a LoadStatsResponse with all clusters it + * is interested in learning load stats about. + */ +export interface LoadStatsResponse__Output { + /** + * Clusters to report stats for. + * Not populated if *send_all_clusters* is true. + */ + 'clusters': (string)[]; + /** + * The minimum interval of time to collect stats over. This is only a minimum for two reasons: + * + * 1. There may be some delay from when the timer fires until stats sampling occurs. + * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic + * that is observed in between the corresponding previous *LoadStatsRequest* and this + * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + * of inobservability that might otherwise exists between the messages. New clusters are not + * subject to this consideration. + */ + 'load_reporting_interval'?: (_google_protobuf_Duration__Output); + /** + * Set to *true* if the management server supports endpoint granularity + * report. + */ + 'report_endpoint_granularity': (boolean); + /** + * If true, the client should send all clusters it knows about. + * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their + * :ref:`client_features` field will honor this field. + */ + 'send_all_clusters': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/Percent.ts b/packages/grpc-js-xds/src/generated/envoy/type/Percent.ts index f63553acd..364419994 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/Percent.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/Percent.ts @@ -12,5 +12,5 @@ export interface Percent { * Identifies a percentage, in the range [0.0, 100.0]. */ export interface Percent__Output { - 'value': (number | string); + 'value': (number); } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/ListStringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/ListStringMatcher.ts deleted file mode 100644 index 42bde031b..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/ListStringMatcher.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/type/matcher/string.proto - -import type { StringMatcher as _envoy_type_matcher_StringMatcher, StringMatcher__Output as _envoy_type_matcher_StringMatcher__Output } from '../../../envoy/type/matcher/StringMatcher'; - -/** - * Specifies a list of ways to match a string. - */ -export interface ListStringMatcher { - 'patterns'?: (_envoy_type_matcher_StringMatcher)[]; -} - -/** - * Specifies a list of ways to match a string. - */ -export interface ListStringMatcher__Output { - 'patterns': (_envoy_type_matcher_StringMatcher__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts new file mode 100644 index 000000000..e91a4898e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/number.proto + +import type { DoubleRange as _envoy_type_v3_DoubleRange, DoubleRange__Output as _envoy_type_v3_DoubleRange__Output } from '../../../../envoy/type/v3/DoubleRange'; + +/** + * Specifies the way to match a double value. + */ +export interface DoubleMatcher { + /** + * If specified, the input double value must be in the range specified here. + * Note: The range is using half-open interval semantics [start, end). + */ + 'range'?: (_envoy_type_v3_DoubleRange); + /** + * If specified, the input double value must be equal to the value specified here. + */ + 'exact'?: (number | string); + 'match_pattern'?: "range"|"exact"; +} + +/** + * Specifies the way to match a double value. + */ +export interface DoubleMatcher__Output { + /** + * If specified, the input double value must be in the range specified here. + * Note: The range is using half-open interval semantics [start, end). + */ + 'range'?: (_envoy_type_v3_DoubleRange__Output); + /** + * If specified, the input double value must be equal to the value specified here. + */ + 'exact'?: (number); + 'match_pattern': "range"|"exact"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts new file mode 100644 index 000000000..c32b72a39 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/value.proto + +import type { ValueMatcher as _envoy_type_matcher_v3_ValueMatcher, ValueMatcher__Output as _envoy_type_matcher_v3_ValueMatcher__Output } from '../../../../envoy/type/matcher/v3/ValueMatcher'; + +/** + * Specifies the way to match a list value. + */ +export interface ListMatcher { + /** + * If specified, at least one of the values in the list must match the value specified. + */ + 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher); + 'match_pattern'?: "one_of"; +} + +/** + * Specifies the way to match a list value. + */ +export interface ListMatcher__Output { + /** + * If specified, at least one of the values in the list must match the value specified. + */ + 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher__Output); + 'match_pattern': "one_of"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListStringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListStringMatcher.ts new file mode 100644 index 000000000..4d5062382 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListStringMatcher.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/string.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; + +/** + * Specifies a list of ways to match a string. + */ +export interface ListStringMatcher { + 'patterns'?: (_envoy_type_matcher_v3_StringMatcher)[]; +} + +/** + * Specifies a list of ways to match a string. + */ +export interface ListStringMatcher__Output { + 'patterns': (_envoy_type_matcher_v3_StringMatcher__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts new file mode 100644 index 000000000..8d80408a3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts @@ -0,0 +1,65 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/metadata.proto + +import type { ValueMatcher as _envoy_type_matcher_v3_ValueMatcher, ValueMatcher__Output as _envoy_type_matcher_v3_ValueMatcher__Output } from '../../../../envoy/type/matcher/v3/ValueMatcher'; + +/** + * Specifies the segment in a path to retrieve value from Metadata. + * Note: Currently it's not supported to retrieve a value from a list in Metadata. This means that + * if the segment key refers to a list, it has to be the last segment in a path. + */ +export interface _envoy_type_matcher_v3_MetadataMatcher_PathSegment { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment'?: "key"; +} + +/** + * Specifies the segment in a path to retrieve value from Metadata. + * Note: Currently it's not supported to retrieve a value from a list in Metadata. This means that + * if the segment key refers to a list, it has to be the last segment in a path. + */ +export interface _envoy_type_matcher_v3_MetadataMatcher_PathSegment__Output { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment': "key"; +} + +/** + * [#next-major-version: MetadataMatcher should use StructMatcher] + */ +export interface MetadataMatcher { + /** + * The filter name to retrieve the Struct from the Metadata. + */ + 'filter'?: (string); + /** + * The path to retrieve the Value from the Struct. + */ + 'path'?: (_envoy_type_matcher_v3_MetadataMatcher_PathSegment)[]; + /** + * The MetadataMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value'?: (_envoy_type_matcher_v3_ValueMatcher); +} + +/** + * [#next-major-version: MetadataMatcher should use StructMatcher] + */ +export interface MetadataMatcher__Output { + /** + * The filter name to retrieve the Struct from the Metadata. + */ + 'filter': (string); + /** + * The path to retrieve the Value from the Struct. + */ + 'path': (_envoy_type_matcher_v3_MetadataMatcher_PathSegment__Output)[]; + /** + * The MetadataMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value'?: (_envoy_type_matcher_v3_ValueMatcher__Output); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/RegexMatchAndSubstitute.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts similarity index 89% rename from packages/grpc-js-xds/src/generated/envoy/type/matcher/RegexMatchAndSubstitute.ts rename to packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts index 17c38c7c0..4abddc655 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/RegexMatchAndSubstitute.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts @@ -1,6 +1,6 @@ -// Original file: deps/envoy-api/envoy/type/matcher/regex.proto +// Original file: deps/envoy-api/envoy/type/matcher/v3/regex.proto -import type { RegexMatcher as _envoy_type_matcher_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_RegexMatcher__Output } from '../../../envoy/type/matcher/RegexMatcher'; +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; /** * Describes how to match a string and then produce a new string using a regular @@ -18,7 +18,7 @@ export interface RegexMatchAndSubstitute { * used in the pattern to extract portions of the subject string, and then * referenced in the substitution string. */ - 'pattern'?: (_envoy_type_matcher_RegexMatcher); + 'pattern'?: (_envoy_type_matcher_v3_RegexMatcher); /** * The string that should be substituted into matching portions of the * subject string during a substitution operation to produce a new string. @@ -49,7 +49,7 @@ export interface RegexMatchAndSubstitute__Output { * used in the pattern to extract portions of the subject string, and then * referenced in the substitution string. */ - 'pattern'?: (_envoy_type_matcher_RegexMatcher__Output); + 'pattern'?: (_envoy_type_matcher_v3_RegexMatcher__Output); /** * The string that should be substituted into matching portions of the * subject string during a substitution operation to produce a new string. diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/RegexMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts similarity index 57% rename from packages/grpc-js-xds/src/generated/envoy/type/matcher/RegexMatcher.ts rename to packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts index 8a18816b0..0e8b37f08 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/RegexMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts @@ -1,13 +1,23 @@ -// Original file: deps/envoy-api/envoy/type/matcher/regex.proto +// Original file: deps/envoy-api/envoy/type/matcher/v3/regex.proto -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../google/protobuf/UInt32Value'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; /** * Google's `RE2 `_ regex engine. The regex string must adhere to * the documented `syntax `_. The engine is designed * to complete execution in linear time as well as limit the amount of memory used. + * + * Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` + * and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + * complexity that a compiled regex can have before an exception is thrown or a warning is + * logged, respectively. `re2.max_program_size.error_level` defaults to 100, and + * `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + * + * Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, + * which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + * each time the program size exceeds the warn level threshold. */ -export interface _envoy_type_matcher_RegexMatcher_GoogleRE2 { +export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2 { /** * This field controls the RE2 "program size" which is a rough estimate of how complex a * compiled regex is to evaluate. A regex that has a program size greater than the configured @@ -24,8 +34,18 @@ export interface _envoy_type_matcher_RegexMatcher_GoogleRE2 { * Google's `RE2 `_ regex engine. The regex string must adhere to * the documented `syntax `_. The engine is designed * to complete execution in linear time as well as limit the amount of memory used. + * + * Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` + * and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + * complexity that a compiled regex can have before an exception is thrown or a warning is + * logged, respectively. `re2.max_program_size.error_level` defaults to 100, and + * `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + * + * Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, + * which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + * each time the program size exceeds the warn level threshold. */ -export interface _envoy_type_matcher_RegexMatcher_GoogleRE2__Output { +export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output { /** * This field controls the RE2 "program size" which is a rough estimate of how complex a * compiled regex is to evaluate. A regex that has a program size greater than the configured @@ -45,7 +65,7 @@ export interface RegexMatcher { /** * Google's RE2 regex engine. */ - 'google_re2'?: (_envoy_type_matcher_RegexMatcher_GoogleRE2); + 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2); /** * The regex match string. The string must be supported by the configured engine. */ @@ -60,7 +80,7 @@ export interface RegexMatcher__Output { /** * Google's RE2 regex engine. */ - 'google_re2'?: (_envoy_type_matcher_RegexMatcher_GoogleRE2__Output); + 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output); /** * The regex match string. The string must be supported by the configured engine. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/StringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts similarity index 58% rename from packages/grpc-js-xds/src/generated/envoy/type/matcher/StringMatcher.ts rename to packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts index e434b1e1b..58616dde8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/StringMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts @@ -1,10 +1,10 @@ -// Original file: deps/envoy-api/envoy/type/matcher/string.proto +// Original file: deps/envoy-api/envoy/type/matcher/v3/string.proto -import type { RegexMatcher as _envoy_type_matcher_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_RegexMatcher__Output } from '../../../envoy/type/matcher/RegexMatcher'; +import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; /** * Specifies the way to match a string. - * [#next-free-field: 7] + * [#next-free-field: 8] */ export interface StringMatcher { /** @@ -33,38 +33,31 @@ export interface StringMatcher { * * *abc* matches the value *xyz.abc* */ 'suffix'?: (string); - /** - * The input string must match the regular expression specified here. - * The regex grammar is defined `here - * `_. - * - * Examples: - * - * * The regex ``\d{3}`` matches the value *123* - * * The regex ``\d{3}`` does not match the value *1234* - * * The regex ``\d{3}`` does not match the value *123.456* - * - * .. attention:: - * This field has been deprecated in favor of `safe_regex` as it is not safe for use with - * untrusted input in all cases. - */ - 'regex'?: (string); /** * The input string must match the regular expression specified here. */ - 'safe_regex'?: (_envoy_type_matcher_RegexMatcher); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher); /** * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no * effect for the safe_regex match. * For example, the matcher *data* will match both input string *Data* and *data* if set to true. */ 'ignore_case'?: (boolean); - 'match_pattern'?: "exact"|"prefix"|"suffix"|"regex"|"safe_regex"; + /** + * The input string must have the substring specified here. + * Note: empty contains match is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc.def* + */ + 'contains'?: (string); + 'match_pattern'?: "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; } /** * Specifies the way to match a string. - * [#next-free-field: 7] + * [#next-free-field: 8] */ export interface StringMatcher__Output { /** @@ -93,31 +86,24 @@ export interface StringMatcher__Output { * * *abc* matches the value *xyz.abc* */ 'suffix'?: (string); - /** - * The input string must match the regular expression specified here. - * The regex grammar is defined `here - * `_. - * - * Examples: - * - * * The regex ``\d{3}`` matches the value *123* - * * The regex ``\d{3}`` does not match the value *1234* - * * The regex ``\d{3}`` does not match the value *123.456* - * - * .. attention:: - * This field has been deprecated in favor of `safe_regex` as it is not safe for use with - * untrusted input in all cases. - */ - 'regex'?: (string); /** * The input string must match the regular expression specified here. */ - 'safe_regex'?: (_envoy_type_matcher_RegexMatcher__Output); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output); /** * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no * effect for the safe_regex match. * For example, the matcher *data* will match both input string *Data* and *data* if set to true. */ 'ignore_case': (boolean); - 'match_pattern': "exact"|"prefix"|"suffix"|"regex"|"safe_regex"; + /** + * The input string must have the substring specified here. + * Note: empty contains match is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc.def* + */ + 'contains'?: (string); + 'match_pattern': "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts new file mode 100644 index 000000000..6c271162a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts @@ -0,0 +1,101 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/value.proto + +import type { DoubleMatcher as _envoy_type_matcher_v3_DoubleMatcher, DoubleMatcher__Output as _envoy_type_matcher_v3_DoubleMatcher__Output } from '../../../../envoy/type/matcher/v3/DoubleMatcher'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; +import type { ListMatcher as _envoy_type_matcher_v3_ListMatcher, ListMatcher__Output as _envoy_type_matcher_v3_ListMatcher__Output } from '../../../../envoy/type/matcher/v3/ListMatcher'; + +/** + * NullMatch is an empty message to specify a null value. + */ +export interface _envoy_type_matcher_v3_ValueMatcher_NullMatch { +} + +/** + * NullMatch is an empty message to specify a null value. + */ +export interface _envoy_type_matcher_v3_ValueMatcher_NullMatch__Output { +} + +/** + * Specifies the way to match a ProtobufWkt::Value. Primitive values and ListValue are supported. + * StructValue is not supported and is always not matched. + * [#next-free-field: 7] + */ +export interface ValueMatcher { + /** + * If specified, a match occurs if and only if the target value is a NullValue. + */ + 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch); + /** + * If specified, a match occurs if and only if the target value is a double value and is + * matched to this field. + */ + 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher); + /** + * If specified, a match occurs if and only if the target value is a string value and is + * matched to this field. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher); + /** + * If specified, a match occurs if and only if the target value is a bool value and is equal + * to this field. + */ + 'bool_match'?: (boolean); + /** + * If specified, value match will be performed based on whether the path is referring to a + * valid primitive value in the metadata. If the path is referring to a non-primitive value, + * the result is always not matched. + */ + 'present_match'?: (boolean); + /** + * If specified, a match occurs if and only if the target value is a list value and + * is matched to this field. + */ + 'list_match'?: (_envoy_type_matcher_v3_ListMatcher); + /** + * Specifies how to match a value. + */ + 'match_pattern'?: "null_match"|"double_match"|"string_match"|"bool_match"|"present_match"|"list_match"; +} + +/** + * Specifies the way to match a ProtobufWkt::Value. Primitive values and ListValue are supported. + * StructValue is not supported and is always not matched. + * [#next-free-field: 7] + */ +export interface ValueMatcher__Output { + /** + * If specified, a match occurs if and only if the target value is a NullValue. + */ + 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch__Output); + /** + * If specified, a match occurs if and only if the target value is a double value and is + * matched to this field. + */ + 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher__Output); + /** + * If specified, a match occurs if and only if the target value is a string value and is + * matched to this field. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output); + /** + * If specified, a match occurs if and only if the target value is a bool value and is equal + * to this field. + */ + 'bool_match'?: (boolean); + /** + * If specified, value match will be performed based on whether the path is referring to a + * valid primitive value in the metadata. If the path is referring to a non-primitive value, + * the result is always not matched. + */ + 'present_match'?: (boolean); + /** + * If specified, a match occurs if and only if the target value is a list value and + * is matched to this field. + */ + 'list_match'?: (_envoy_type_matcher_v3_ListMatcher__Output); + /** + * Specifies how to match a value. + */ + 'match_pattern': "null_match"|"double_match"|"string_match"|"bool_match"|"present_match"|"list_match"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v2/MetadataKind.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v2/MetadataKind.ts deleted file mode 100644 index 665b95f0d..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v2/MetadataKind.ts +++ /dev/null @@ -1,98 +0,0 @@ -// Original file: deps/envoy-api/envoy/type/metadata/v2/metadata.proto - - -/** - * Represents metadata from :ref:`the upstream cluster`. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Cluster { -} - -/** - * Represents metadata from :ref:`the upstream cluster`. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Cluster__Output { -} - -/** - * Represents metadata from :ref:`the upstream - * host`. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Host { -} - -/** - * Represents metadata from :ref:`the upstream - * host`. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Host__Output { -} - -/** - * Represents dynamic metadata associated with the request. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Request { -} - -/** - * Represents dynamic metadata associated with the request. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Request__Output { -} - -/** - * Represents metadata from :ref:`the route`. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Route { -} - -/** - * Represents metadata from :ref:`the route`. - */ -export interface _envoy_type_metadata_v2_MetadataKind_Route__Output { -} - -/** - * Describes what kind of metadata. - */ -export interface MetadataKind { - /** - * Request kind of metadata. - */ - 'request'?: (_envoy_type_metadata_v2_MetadataKind_Request); - /** - * Route kind of metadata. - */ - 'route'?: (_envoy_type_metadata_v2_MetadataKind_Route); - /** - * Cluster kind of metadata. - */ - 'cluster'?: (_envoy_type_metadata_v2_MetadataKind_Cluster); - /** - * Host kind of metadata. - */ - 'host'?: (_envoy_type_metadata_v2_MetadataKind_Host); - 'kind'?: "request"|"route"|"cluster"|"host"; -} - -/** - * Describes what kind of metadata. - */ -export interface MetadataKind__Output { - /** - * Request kind of metadata. - */ - 'request'?: (_envoy_type_metadata_v2_MetadataKind_Request__Output); - /** - * Route kind of metadata. - */ - 'route'?: (_envoy_type_metadata_v2_MetadataKind_Route__Output); - /** - * Cluster kind of metadata. - */ - 'cluster'?: (_envoy_type_metadata_v2_MetadataKind_Cluster__Output); - /** - * Host kind of metadata. - */ - 'host'?: (_envoy_type_metadata_v2_MetadataKind_Host__Output); - 'kind': "request"|"route"|"cluster"|"host"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v2/MetadataKey.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts similarity index 85% rename from packages/grpc-js-xds/src/generated/envoy/type/metadata/v2/MetadataKey.ts rename to packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts index 94d661879..fcc43b07b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v2/MetadataKey.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts @@ -1,11 +1,11 @@ -// Original file: deps/envoy-api/envoy/type/metadata/v2/metadata.proto +// Original file: deps/envoy-api/envoy/type/metadata/v3/metadata.proto /** * Specifies the segment in a path to retrieve value from Metadata. * Currently it is only supported to specify the key, i.e. field name, as one segment of a path. */ -export interface _envoy_type_metadata_v2_MetadataKey_PathSegment { +export interface _envoy_type_metadata_v3_MetadataKey_PathSegment { /** * If specified, use the key to retrieve the value in a Struct. */ @@ -17,7 +17,7 @@ export interface _envoy_type_metadata_v2_MetadataKey_PathSegment { * Specifies the segment in a path to retrieve value from Metadata. * Currently it is only supported to specify the key, i.e. field name, as one segment of a path. */ -export interface _envoy_type_metadata_v2_MetadataKey_PathSegment__Output { +export interface _envoy_type_metadata_v3_MetadataKey_PathSegment__Output { /** * If specified, use the key to retrieve the value in a Struct. */ @@ -27,7 +27,7 @@ export interface _envoy_type_metadata_v2_MetadataKey_PathSegment__Output { /** * MetadataKey provides a general interface using `key` and `path` to retrieve value from - * :ref:`Metadata `. + * :ref:`Metadata `. * * For example, for the following Metadata: * @@ -63,12 +63,12 @@ export interface MetadataKey { * Note: Due to that only the key type segment is supported, the path can not specify a list * unless the list is the last segment. */ - 'path'?: (_envoy_type_metadata_v2_MetadataKey_PathSegment)[]; + 'path'?: (_envoy_type_metadata_v3_MetadataKey_PathSegment)[]; } /** * MetadataKey provides a general interface using `key` and `path` to retrieve value from - * :ref:`Metadata `. + * :ref:`Metadata `. * * For example, for the following Metadata: * @@ -104,5 +104,5 @@ export interface MetadataKey__Output { * Note: Due to that only the key type segment is supported, the path can not specify a list * unless the list is the last segment. */ - 'path': (_envoy_type_metadata_v2_MetadataKey_PathSegment__Output)[]; + 'path': (_envoy_type_metadata_v3_MetadataKey_PathSegment__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts new file mode 100644 index 000000000..c231ecf98 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts @@ -0,0 +1,98 @@ +// Original file: deps/envoy-api/envoy/type/metadata/v3/metadata.proto + + +/** + * Represents metadata from :ref:`the upstream cluster`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Cluster { +} + +/** + * Represents metadata from :ref:`the upstream cluster`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Cluster__Output { +} + +/** + * Represents metadata from :ref:`the upstream + * host`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Host { +} + +/** + * Represents metadata from :ref:`the upstream + * host`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Host__Output { +} + +/** + * Represents dynamic metadata associated with the request. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Request { +} + +/** + * Represents dynamic metadata associated with the request. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Request__Output { +} + +/** + * Represents metadata from :ref:`the route`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Route { +} + +/** + * Represents metadata from :ref:`the route`. + */ +export interface _envoy_type_metadata_v3_MetadataKind_Route__Output { +} + +/** + * Describes what kind of metadata. + */ +export interface MetadataKind { + /** + * Request kind of metadata. + */ + 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request); + /** + * Route kind of metadata. + */ + 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route); + /** + * Cluster kind of metadata. + */ + 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster); + /** + * Host kind of metadata. + */ + 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host); + 'kind'?: "request"|"route"|"cluster"|"host"; +} + +/** + * Describes what kind of metadata. + */ +export interface MetadataKind__Output { + /** + * Request kind of metadata. + */ + 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request__Output); + /** + * Route kind of metadata. + */ + 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route__Output); + /** + * Cluster kind of metadata. + */ + 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster__Output); + /** + * Host kind of metadata. + */ + 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host__Output); + 'kind': "request"|"route"|"cluster"|"host"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/tracing/v2/CustomTag.ts b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts similarity index 66% rename from packages/grpc-js-xds/src/generated/envoy/type/tracing/v2/CustomTag.ts rename to packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts index 3eed4cf44..ef29c3420 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/tracing/v2/CustomTag.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts @@ -1,12 +1,12 @@ -// Original file: deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto +// Original file: deps/envoy-api/envoy/type/tracing/v3/custom_tag.proto -import type { MetadataKind as _envoy_type_metadata_v2_MetadataKind, MetadataKind__Output as _envoy_type_metadata_v2_MetadataKind__Output } from '../../../../envoy/type/metadata/v2/MetadataKind'; -import type { MetadataKey as _envoy_type_metadata_v2_MetadataKey, MetadataKey__Output as _envoy_type_metadata_v2_MetadataKey__Output } from '../../../../envoy/type/metadata/v2/MetadataKey'; +import type { MetadataKind as _envoy_type_metadata_v3_MetadataKind, MetadataKind__Output as _envoy_type_metadata_v3_MetadataKind__Output } from '../../../../envoy/type/metadata/v3/MetadataKind'; +import type { MetadataKey as _envoy_type_metadata_v3_MetadataKey, MetadataKey__Output as _envoy_type_metadata_v3_MetadataKey__Output } from '../../../../envoy/type/metadata/v3/MetadataKey'; /** * Environment type custom tag with environment name and default value. */ -export interface _envoy_type_tracing_v2_CustomTag_Environment { +export interface _envoy_type_tracing_v3_CustomTag_Environment { /** * Environment variable name to obtain the value to populate the tag value. */ @@ -22,7 +22,7 @@ export interface _envoy_type_tracing_v2_CustomTag_Environment { /** * Environment type custom tag with environment name and default value. */ -export interface _envoy_type_tracing_v2_CustomTag_Environment__Output { +export interface _envoy_type_tracing_v3_CustomTag_Environment__Output { /** * Environment variable name to obtain the value to populate the tag value. */ @@ -38,7 +38,7 @@ export interface _envoy_type_tracing_v2_CustomTag_Environment__Output { /** * Header type custom tag with header name and default value. */ -export interface _envoy_type_tracing_v2_CustomTag_Header { +export interface _envoy_type_tracing_v3_CustomTag_Header { /** * Header name to obtain the value to populate the tag value. */ @@ -54,7 +54,7 @@ export interface _envoy_type_tracing_v2_CustomTag_Header { /** * Header type custom tag with header name and default value. */ -export interface _envoy_type_tracing_v2_CustomTag_Header__Output { +export interface _envoy_type_tracing_v3_CustomTag_Header__Output { /** * Header name to obtain the value to populate the tag value. */ @@ -70,7 +70,7 @@ export interface _envoy_type_tracing_v2_CustomTag_Header__Output { /** * Literal type custom tag with static value for the tag value. */ -export interface _envoy_type_tracing_v2_CustomTag_Literal { +export interface _envoy_type_tracing_v3_CustomTag_Literal { /** * Static literal value to populate the tag value. */ @@ -80,7 +80,7 @@ export interface _envoy_type_tracing_v2_CustomTag_Literal { /** * Literal type custom tag with static value for the tag value. */ -export interface _envoy_type_tracing_v2_CustomTag_Literal__Output { +export interface _envoy_type_tracing_v3_CustomTag_Literal__Output { /** * Static literal value to populate the tag value. */ @@ -89,20 +89,20 @@ export interface _envoy_type_tracing_v2_CustomTag_Literal__Output { /** * Metadata type custom tag using - * :ref:`MetadataKey ` to retrieve the protobuf value - * from :ref:`Metadata `, and populate the tag value with + * :ref:`MetadataKey ` to retrieve the protobuf value + * from :ref:`Metadata `, and populate the tag value with * `the canonical JSON `_ * representation of it. */ -export interface _envoy_type_tracing_v2_CustomTag_Metadata { +export interface _envoy_type_tracing_v3_CustomTag_Metadata { /** * Specify what kind of metadata to obtain tag value from. */ - 'kind'?: (_envoy_type_metadata_v2_MetadataKind); + 'kind'?: (_envoy_type_metadata_v3_MetadataKind); /** * Metadata key to define the path to retrieve the tag value. */ - 'metadata_key'?: (_envoy_type_metadata_v2_MetadataKey); + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); /** * When no valid metadata is found, * the tag value would be populated with this default value if specified, @@ -113,20 +113,20 @@ export interface _envoy_type_tracing_v2_CustomTag_Metadata { /** * Metadata type custom tag using - * :ref:`MetadataKey ` to retrieve the protobuf value - * from :ref:`Metadata `, and populate the tag value with + * :ref:`MetadataKey ` to retrieve the protobuf value + * from :ref:`Metadata `, and populate the tag value with * `the canonical JSON `_ * representation of it. */ -export interface _envoy_type_tracing_v2_CustomTag_Metadata__Output { +export interface _envoy_type_tracing_v3_CustomTag_Metadata__Output { /** * Specify what kind of metadata to obtain tag value from. */ - 'kind'?: (_envoy_type_metadata_v2_MetadataKind__Output); + 'kind'?: (_envoy_type_metadata_v3_MetadataKind__Output); /** * Metadata key to define the path to retrieve the tag value. */ - 'metadata_key'?: (_envoy_type_metadata_v2_MetadataKey__Output); + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); /** * When no valid metadata is found, * the tag value would be populated with this default value if specified, @@ -147,19 +147,19 @@ export interface CustomTag { /** * A literal custom tag. */ - 'literal'?: (_envoy_type_tracing_v2_CustomTag_Literal); + 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal); /** * An environment custom tag. */ - 'environment'?: (_envoy_type_tracing_v2_CustomTag_Environment); + 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment); /** * A request header custom tag. */ - 'request_header'?: (_envoy_type_tracing_v2_CustomTag_Header); + 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header); /** * A custom tag to obtain tag value from the metadata. */ - 'metadata'?: (_envoy_type_tracing_v2_CustomTag_Metadata); + 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata); /** * Used to specify what kind of custom tag. */ @@ -178,19 +178,19 @@ export interface CustomTag__Output { /** * A literal custom tag. */ - 'literal'?: (_envoy_type_tracing_v2_CustomTag_Literal__Output); + 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal__Output); /** * An environment custom tag. */ - 'environment'?: (_envoy_type_tracing_v2_CustomTag_Environment__Output); + 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment__Output); /** * A request header custom tag. */ - 'request_header'?: (_envoy_type_tracing_v2_CustomTag_Header__Output); + 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header__Output); /** * A custom tag to obtain tag value from the metadata. */ - 'metadata'?: (_envoy_type_tracing_v2_CustomTag_Metadata__Output); + 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata__Output); /** * Used to specify what kind of custom tag. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/type/CodecClientType.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts similarity index 84% rename from packages/grpc-js-xds/src/generated/envoy/type/CodecClientType.ts rename to packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts index a0bf07351..308f14446 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/CodecClientType.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/type/http.proto +// Original file: deps/envoy-api/envoy/type/v3/http.proto export enum CodecClientType { HTTP1 = 0, diff --git a/packages/grpc-js-xds/src/generated/envoy/type/DoubleRange.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/DoubleRange.ts similarity index 82% rename from packages/grpc-js-xds/src/generated/envoy/type/DoubleRange.ts rename to packages/grpc-js-xds/src/generated/envoy/type/v3/DoubleRange.ts index 5ebc3a579..5d13bebdb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/DoubleRange.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/DoubleRange.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/type/range.proto +// Original file: deps/envoy-api/envoy/type/v3/range.proto /** @@ -24,9 +24,9 @@ export interface DoubleRange__Output { /** * start of the range (inclusive) */ - 'start': (number | string); + 'start': (number); /** * end of the range (exclusive) */ - 'end': (number | string); + 'end': (number); } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts new file mode 100644 index 000000000..564af9a0f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts @@ -0,0 +1,68 @@ +// Original file: deps/envoy-api/envoy/type/v3/percent.proto + + +// Original file: deps/envoy-api/envoy/type/v3/percent.proto + +/** + * Fraction percentages support several fixed denominator values. + */ +export enum _envoy_type_v3_FractionalPercent_DenominatorType { + /** + * 100. + * + * **Example**: 1/100 = 1%. + */ + HUNDRED = 0, + /** + * 10,000. + * + * **Example**: 1/10000 = 0.01%. + */ + TEN_THOUSAND = 1, + /** + * 1,000,000. + * + * **Example**: 1/1000000 = 0.0001%. + */ + MILLION = 2, +} + +/** + * A fractional percentage is used in cases in which for performance reasons performing floating + * point to integer conversions during randomness calculations is undesirable. The message includes + * both a numerator and denominator that together determine the final fractional value. + * + * * **Example**: 1/100 = 1%. + * * **Example**: 3/10000 = 0.03%. + */ +export interface FractionalPercent { + /** + * Specifies the numerator. Defaults to 0. + */ + 'numerator'?: (number); + /** + * Specifies the denominator. If the denominator specified is less than the numerator, the final + * fractional percentage is capped at 1 (100%). + */ + 'denominator'?: (_envoy_type_v3_FractionalPercent_DenominatorType | keyof typeof _envoy_type_v3_FractionalPercent_DenominatorType); +} + +/** + * A fractional percentage is used in cases in which for performance reasons performing floating + * point to integer conversions during randomness calculations is undesirable. The message includes + * both a numerator and denominator that together determine the final fractional value. + * + * * **Example**: 1/100 = 1%. + * * **Example**: 3/10000 = 0.03%. + */ +export interface FractionalPercent__Output { + /** + * Specifies the numerator. Defaults to 0. + */ + 'numerator': (number); + /** + * Specifies the denominator. If the denominator specified is less than the numerator, the final + * fractional percentage is capped at 1 (100%). + */ + 'denominator': (keyof typeof _envoy_type_v3_FractionalPercent_DenominatorType); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/Int32Range.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int32Range.ts similarity index 90% rename from packages/grpc-js-xds/src/generated/envoy/type/Int32Range.ts rename to packages/grpc-js-xds/src/generated/envoy/type/v3/Int32Range.ts index f5475c2db..826af6c38 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/Int32Range.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int32Range.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/type/range.proto +// Original file: deps/envoy-api/envoy/type/v3/range.proto /** diff --git a/packages/grpc-js-xds/src/generated/envoy/type/Int64Range.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int64Range.ts similarity index 91% rename from packages/grpc-js-xds/src/generated/envoy/type/Int64Range.ts rename to packages/grpc-js-xds/src/generated/envoy/type/v3/Int64Range.ts index f9664cba4..89ce7fb8d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/Int64Range.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/Int64Range.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/type/range.proto +// Original file: deps/envoy-api/envoy/type/v3/range.proto import type { Long } from '@grpc/proto-loader'; diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/Percent.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/Percent.ts new file mode 100644 index 000000000..01d236c4e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/Percent.ts @@ -0,0 +1,16 @@ +// Original file: deps/envoy-api/envoy/type/v3/percent.proto + + +/** + * Identifies a percentage, in the range [0.0, 100.0]. + */ +export interface Percent { + 'value'?: (number | string); +} + +/** + * Identifies a percentage, in the range [0.0, 100.0]. + */ +export interface Percent__Output { + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/SemanticVersion.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/SemanticVersion.ts new file mode 100644 index 000000000..3f714a766 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/SemanticVersion.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/type/v3/semantic_version.proto + + +/** + * Envoy uses SemVer (https://semver.org/). Major/minor versions indicate + * expected behaviors and APIs, the patch version field is used only + * for security fixes and can be generally ignored. + */ +export interface SemanticVersion { + 'major_number'?: (number); + 'minor_number'?: (number); + 'patch'?: (number); +} + +/** + * Envoy uses SemVer (https://semver.org/). Major/minor versions indicate + * expected behaviors and APIs, the patch version field is used only + * for security fixes and can be generally ignored. + */ +export interface SemanticVersion__Output { + 'major_number': (number); + 'minor_number': (number); + 'patch': (number); +} diff --git a/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts b/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts deleted file mode 100644 index 2b6490be6..000000000 --- a/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Original file: deps/googleapis/google/api/http.proto - - -/** - * A custom pattern is used for defining custom HTTP verb. - */ -export interface CustomHttpPattern { - /** - * The name of this custom HTTP verb. - */ - 'kind'?: (string); - /** - * The path matched by this custom verb. - */ - 'path'?: (string); -} - -/** - * A custom pattern is used for defining custom HTTP verb. - */ -export interface CustomHttpPattern__Output { - /** - * The name of this custom HTTP verb. - */ - 'kind': (string); - /** - * The path matched by this custom verb. - */ - 'path': (string); -} diff --git a/packages/grpc-js-xds/src/generated/google/api/Http.ts b/packages/grpc-js-xds/src/generated/google/api/Http.ts deleted file mode 100644 index e9b3cb309..000000000 --- a/packages/grpc-js-xds/src/generated/google/api/Http.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Original file: deps/googleapis/google/api/http.proto - -import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; - -/** - * Defines the HTTP configuration for an API service. It contains a list of - * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method - * to one or more HTTP REST API methods. - */ -export interface Http { - /** - * A list of HTTP configuration rules that apply to individual API methods. - * - * **NOTE:** All service configuration rules follow "last one wins" order. - */ - 'rules'?: (_google_api_HttpRule)[]; - /** - * When set to true, URL path parameters will be fully URI-decoded except in - * cases of single segment matches in reserved expansion, where "%2F" will be - * left encoded. - * - * The default behavior is to not decode RFC 6570 reserved characters in multi - * segment matches. - */ - 'fully_decode_reserved_expansion'?: (boolean); -} - -/** - * Defines the HTTP configuration for an API service. It contains a list of - * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method - * to one or more HTTP REST API methods. - */ -export interface Http__Output { - /** - * A list of HTTP configuration rules that apply to individual API methods. - * - * **NOTE:** All service configuration rules follow "last one wins" order. - */ - 'rules': (_google_api_HttpRule__Output)[]; - /** - * When set to true, URL path parameters will be fully URI-decoded except in - * cases of single segment matches in reserved expansion, where "%2F" will be - * left encoded. - * - * The default behavior is to not decode RFC 6570 reserved characters in multi - * segment matches. - */ - 'fully_decode_reserved_expansion': (boolean); -} diff --git a/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts b/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts deleted file mode 100644 index 21ad897ee..000000000 --- a/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts +++ /dev/null @@ -1,680 +0,0 @@ -// Original file: deps/googleapis/google/api/http.proto - -import type { CustomHttpPattern as _google_api_CustomHttpPattern, CustomHttpPattern__Output as _google_api_CustomHttpPattern__Output } from '../../google/api/CustomHttpPattern'; -import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; - -/** - * # gRPC Transcoding - * - * gRPC Transcoding is a feature for mapping between a gRPC method and one or - * more HTTP REST endpoints. It allows developers to build a single API service - * that supports both gRPC APIs and REST APIs. Many systems, including [Google - * APIs](https://github.com/googleapis/googleapis), - * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC - * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), - * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature - * and use it for large scale production services. - * - * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies - * how different portions of the gRPC request message are mapped to the URL - * path, URL query parameters, and HTTP request body. It also controls how the - * gRPC response message is mapped to the HTTP response body. `HttpRule` is - * typically specified as an `google.api.http` annotation on the gRPC method. - * - * Each mapping specifies a URL path template and an HTTP method. The path - * template may refer to one or more fields in the gRPC request message, as long - * as each field is a non-repeated field with a primitive (non-message) type. - * The path template controls how fields of the request message are mapped to - * the URL path. - * - * Example: - * - * service Messaging { - * rpc GetMessage(GetMessageRequest) returns (Message) { - * option (google.api.http) = { - * get: "/v1/{name=messages/*}" - * }; - * } - * } - * message GetMessageRequest { - * string name = 1; // Mapped to URL path. - * } - * message Message { - * string text = 1; // The resource content. - * } - * - * This enables an HTTP REST to gRPC mapping as below: - * - * HTTP | gRPC - * -----|----- - * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` - * - * Any fields in the request message which are not bound by the path template - * automatically become HTTP query parameters if there is no HTTP request body. - * For example: - * - * service Messaging { - * rpc GetMessage(GetMessageRequest) returns (Message) { - * option (google.api.http) = { - * get:"/v1/messages/{message_id}" - * }; - * } - * } - * message GetMessageRequest { - * message SubMessage { - * string subfield = 1; - * } - * string message_id = 1; // Mapped to URL path. - * int64 revision = 2; // Mapped to URL query parameter `revision`. - * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. - * } - * - * This enables a HTTP JSON to RPC mapping as below: - * - * HTTP | gRPC - * -----|----- - * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | - * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: - * "foo"))` - * - * Note that fields which are mapped to URL query parameters must have a - * primitive type or a repeated primitive type or a non-repeated message type. - * In the case of a repeated type, the parameter can be repeated in the URL - * as `...?param=A¶m=B`. In the case of a message type, each field of the - * message is mapped to a separate parameter, such as - * `...?foo.a=A&foo.b=B&foo.c=C`. - * - * For HTTP methods that allow a request body, the `body` field - * specifies the mapping. Consider a REST update method on the - * message resource collection: - * - * service Messaging { - * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { - * option (google.api.http) = { - * patch: "/v1/messages/{message_id}" - * body: "message" - * }; - * } - * } - * message UpdateMessageRequest { - * string message_id = 1; // mapped to the URL - * Message message = 2; // mapped to the body - * } - * - * The following HTTP JSON to RPC mapping is enabled, where the - * representation of the JSON in the request body is determined by - * protos JSON encoding: - * - * HTTP | gRPC - * -----|----- - * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: - * "123456" message { text: "Hi!" })` - * - * The special name `*` can be used in the body mapping to define that - * every field not bound by the path template should be mapped to the - * request body. This enables the following alternative definition of - * the update method: - * - * service Messaging { - * rpc UpdateMessage(Message) returns (Message) { - * option (google.api.http) = { - * patch: "/v1/messages/{message_id}" - * body: "*" - * }; - * } - * } - * message Message { - * string message_id = 1; - * string text = 2; - * } - * - * - * The following HTTP JSON to RPC mapping is enabled: - * - * HTTP | gRPC - * -----|----- - * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: - * "123456" text: "Hi!")` - * - * Note that when using `*` in the body mapping, it is not possible to - * have HTTP parameters, as all fields not bound by the path end in - * the body. This makes this option more rarely used in practice when - * defining REST APIs. The common usage of `*` is in custom methods - * which don't use the URL at all for transferring data. - * - * It is possible to define multiple HTTP methods for one RPC by using - * the `additional_bindings` option. Example: - * - * service Messaging { - * rpc GetMessage(GetMessageRequest) returns (Message) { - * option (google.api.http) = { - * get: "/v1/messages/{message_id}" - * additional_bindings { - * get: "/v1/users/{user_id}/messages/{message_id}" - * } - * }; - * } - * } - * message GetMessageRequest { - * string message_id = 1; - * string user_id = 2; - * } - * - * This enables the following two alternative HTTP JSON to RPC mappings: - * - * HTTP | gRPC - * -----|----- - * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` - * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: - * "123456")` - * - * ## Rules for HTTP mapping - * - * 1. Leaf request fields (recursive expansion nested messages in the request - * message) are classified into three categories: - * - Fields referred by the path template. They are passed via the URL path. - * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP - * request body. - * - All other fields are passed via the URL query parameters, and the - * parameter name is the field path in the request message. A repeated - * field can be represented as multiple query parameters under the same - * name. - * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields - * are passed via URL path and HTTP request body. - * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all - * fields are passed via URL path and URL query parameters. - * - * ### Path template syntax - * - * Template = "/" Segments [ Verb ] ; - * Segments = Segment { "/" Segment } ; - * Segment = "*" | "**" | LITERAL | Variable ; - * Variable = "{" FieldPath [ "=" Segments ] "}" ; - * FieldPath = IDENT { "." IDENT } ; - * Verb = ":" LITERAL ; - * - * The syntax `*` matches a single URL path segment. The syntax `**` matches - * zero or more URL path segments, which must be the last part of the URL path - * except the `Verb`. - * - * The syntax `Variable` matches part of the URL path as specified by its - * template. A variable template must not contain other variables. If a variable - * matches a single path segment, its template may be omitted, e.g. `{var}` - * is equivalent to `{var=*}`. - * - * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` - * contains any reserved character, such characters should be percent-encoded - * before the matching. - * - * If a variable contains exactly one path segment, such as `"{var}"` or - * `"{var=*}"`, when such a variable is expanded into a URL path on the client - * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The - * server side does the reverse decoding. Such variables show up in the - * [Discovery - * Document](https://developers.google.com/discovery/v1/reference/apis) as - * `{var}`. - * - * If a variable contains multiple path segments, such as `"{var=foo/*}"` - * or `"{var=**}"`, when such a variable is expanded into a URL path on the - * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. - * The server side does the reverse decoding, except "%2F" and "%2f" are left - * unchanged. Such variables show up in the - * [Discovery - * Document](https://developers.google.com/discovery/v1/reference/apis) as - * `{+var}`. - * - * ## Using gRPC API Service Configuration - * - * gRPC API Service Configuration (service config) is a configuration language - * for configuring a gRPC service to become a user-facing product. The - * service config is simply the YAML representation of the `google.api.Service` - * proto message. - * - * As an alternative to annotating your proto file, you can configure gRPC - * transcoding in your service config YAML files. You do this by specifying a - * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same - * effect as the proto annotation. This can be particularly useful if you - * have a proto that is reused in multiple services. Note that any transcoding - * specified in the service config will override any matching transcoding - * configuration in the proto. - * - * Example: - * - * http: - * rules: - * # Selects a gRPC method and applies HttpRule to it. - * - selector: example.v1.Messaging.GetMessage - * get: /v1/messages/{message_id}/{sub.subfield} - * - * ## Special notes - * - * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the - * proto to JSON conversion must follow the [proto3 - * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). - * - * While the single segment variable follows the semantics of - * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String - * Expansion, the multi segment variable **does not** follow RFC 6570 Section - * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion - * does not expand special characters like `?` and `#`, which would lead - * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding - * for multi segment variables. - * - * The path variables **must not** refer to any repeated or mapped field, - * because client libraries are not capable of handling such variable expansion. - * - * The path variables **must not** capture the leading "/" character. The reason - * is that the most common use case "{var}" does not capture the leading "/" - * character. For consistency, all path variables must share the same behavior. - * - * Repeated message fields must not be mapped to URL query parameters, because - * no client library can support such complicated mapping. - * - * If an API needs to use a JSON array for request or response body, it can map - * the request or response body to a repeated field. However, some gRPC - * Transcoding implementations may not support this feature. - */ -export interface HttpRule { - /** - * Selects a method to which this rule applies. - * - * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. - */ - 'selector'?: (string); - /** - * Maps to HTTP GET. Used for listing and getting information about - * resources. - */ - 'get'?: (string); - /** - * Maps to HTTP PUT. Used for replacing a resource. - */ - 'put'?: (string); - /** - * Maps to HTTP POST. Used for creating a resource or performing an action. - */ - 'post'?: (string); - /** - * Maps to HTTP DELETE. Used for deleting a resource. - */ - 'delete'?: (string); - /** - * Maps to HTTP PATCH. Used for updating a resource. - */ - 'patch'?: (string); - /** - * The name of the request field whose value is mapped to the HTTP request - * body, or `*` for mapping all request fields not captured by the path - * pattern to the HTTP body, or omitted for not having any HTTP request body. - * - * NOTE: the referred field must be present at the top-level of the request - * message type. - */ - 'body'?: (string); - /** - * The custom pattern is used for specifying an HTTP method that is not - * included in the `pattern` field, such as HEAD, or "*" to leave the - * HTTP method unspecified for this rule. The wild-card rule is useful - * for services that provide content to Web (HTML) clients. - */ - 'custom'?: (_google_api_CustomHttpPattern); - /** - * Additional HTTP bindings for the selector. Nested bindings must - * not contain an `additional_bindings` field themselves (that is, - * the nesting may only be one level deep). - */ - 'additional_bindings'?: (_google_api_HttpRule)[]; - /** - * Optional. The name of the response field whose value is mapped to the HTTP - * response body. When omitted, the entire response message will be used - * as the HTTP response body. - * - * NOTE: The referred field must be present at the top-level of the response - * message type. - */ - 'response_body'?: (string); - /** - * Determines the URL pattern is matched by this rules. This pattern can be - * used with any of the {get|put|post|delete|patch} methods. A custom method - * can be defined using the 'custom' field. - */ - 'pattern'?: "get"|"put"|"post"|"delete"|"patch"|"custom"; -} - -/** - * # gRPC Transcoding - * - * gRPC Transcoding is a feature for mapping between a gRPC method and one or - * more HTTP REST endpoints. It allows developers to build a single API service - * that supports both gRPC APIs and REST APIs. Many systems, including [Google - * APIs](https://github.com/googleapis/googleapis), - * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC - * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), - * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature - * and use it for large scale production services. - * - * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies - * how different portions of the gRPC request message are mapped to the URL - * path, URL query parameters, and HTTP request body. It also controls how the - * gRPC response message is mapped to the HTTP response body. `HttpRule` is - * typically specified as an `google.api.http` annotation on the gRPC method. - * - * Each mapping specifies a URL path template and an HTTP method. The path - * template may refer to one or more fields in the gRPC request message, as long - * as each field is a non-repeated field with a primitive (non-message) type. - * The path template controls how fields of the request message are mapped to - * the URL path. - * - * Example: - * - * service Messaging { - * rpc GetMessage(GetMessageRequest) returns (Message) { - * option (google.api.http) = { - * get: "/v1/{name=messages/*}" - * }; - * } - * } - * message GetMessageRequest { - * string name = 1; // Mapped to URL path. - * } - * message Message { - * string text = 1; // The resource content. - * } - * - * This enables an HTTP REST to gRPC mapping as below: - * - * HTTP | gRPC - * -----|----- - * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` - * - * Any fields in the request message which are not bound by the path template - * automatically become HTTP query parameters if there is no HTTP request body. - * For example: - * - * service Messaging { - * rpc GetMessage(GetMessageRequest) returns (Message) { - * option (google.api.http) = { - * get:"/v1/messages/{message_id}" - * }; - * } - * } - * message GetMessageRequest { - * message SubMessage { - * string subfield = 1; - * } - * string message_id = 1; // Mapped to URL path. - * int64 revision = 2; // Mapped to URL query parameter `revision`. - * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. - * } - * - * This enables a HTTP JSON to RPC mapping as below: - * - * HTTP | gRPC - * -----|----- - * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | - * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: - * "foo"))` - * - * Note that fields which are mapped to URL query parameters must have a - * primitive type or a repeated primitive type or a non-repeated message type. - * In the case of a repeated type, the parameter can be repeated in the URL - * as `...?param=A¶m=B`. In the case of a message type, each field of the - * message is mapped to a separate parameter, such as - * `...?foo.a=A&foo.b=B&foo.c=C`. - * - * For HTTP methods that allow a request body, the `body` field - * specifies the mapping. Consider a REST update method on the - * message resource collection: - * - * service Messaging { - * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { - * option (google.api.http) = { - * patch: "/v1/messages/{message_id}" - * body: "message" - * }; - * } - * } - * message UpdateMessageRequest { - * string message_id = 1; // mapped to the URL - * Message message = 2; // mapped to the body - * } - * - * The following HTTP JSON to RPC mapping is enabled, where the - * representation of the JSON in the request body is determined by - * protos JSON encoding: - * - * HTTP | gRPC - * -----|----- - * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: - * "123456" message { text: "Hi!" })` - * - * The special name `*` can be used in the body mapping to define that - * every field not bound by the path template should be mapped to the - * request body. This enables the following alternative definition of - * the update method: - * - * service Messaging { - * rpc UpdateMessage(Message) returns (Message) { - * option (google.api.http) = { - * patch: "/v1/messages/{message_id}" - * body: "*" - * }; - * } - * } - * message Message { - * string message_id = 1; - * string text = 2; - * } - * - * - * The following HTTP JSON to RPC mapping is enabled: - * - * HTTP | gRPC - * -----|----- - * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: - * "123456" text: "Hi!")` - * - * Note that when using `*` in the body mapping, it is not possible to - * have HTTP parameters, as all fields not bound by the path end in - * the body. This makes this option more rarely used in practice when - * defining REST APIs. The common usage of `*` is in custom methods - * which don't use the URL at all for transferring data. - * - * It is possible to define multiple HTTP methods for one RPC by using - * the `additional_bindings` option. Example: - * - * service Messaging { - * rpc GetMessage(GetMessageRequest) returns (Message) { - * option (google.api.http) = { - * get: "/v1/messages/{message_id}" - * additional_bindings { - * get: "/v1/users/{user_id}/messages/{message_id}" - * } - * }; - * } - * } - * message GetMessageRequest { - * string message_id = 1; - * string user_id = 2; - * } - * - * This enables the following two alternative HTTP JSON to RPC mappings: - * - * HTTP | gRPC - * -----|----- - * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` - * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: - * "123456")` - * - * ## Rules for HTTP mapping - * - * 1. Leaf request fields (recursive expansion nested messages in the request - * message) are classified into three categories: - * - Fields referred by the path template. They are passed via the URL path. - * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP - * request body. - * - All other fields are passed via the URL query parameters, and the - * parameter name is the field path in the request message. A repeated - * field can be represented as multiple query parameters under the same - * name. - * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields - * are passed via URL path and HTTP request body. - * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all - * fields are passed via URL path and URL query parameters. - * - * ### Path template syntax - * - * Template = "/" Segments [ Verb ] ; - * Segments = Segment { "/" Segment } ; - * Segment = "*" | "**" | LITERAL | Variable ; - * Variable = "{" FieldPath [ "=" Segments ] "}" ; - * FieldPath = IDENT { "." IDENT } ; - * Verb = ":" LITERAL ; - * - * The syntax `*` matches a single URL path segment. The syntax `**` matches - * zero or more URL path segments, which must be the last part of the URL path - * except the `Verb`. - * - * The syntax `Variable` matches part of the URL path as specified by its - * template. A variable template must not contain other variables. If a variable - * matches a single path segment, its template may be omitted, e.g. `{var}` - * is equivalent to `{var=*}`. - * - * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` - * contains any reserved character, such characters should be percent-encoded - * before the matching. - * - * If a variable contains exactly one path segment, such as `"{var}"` or - * `"{var=*}"`, when such a variable is expanded into a URL path on the client - * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The - * server side does the reverse decoding. Such variables show up in the - * [Discovery - * Document](https://developers.google.com/discovery/v1/reference/apis) as - * `{var}`. - * - * If a variable contains multiple path segments, such as `"{var=foo/*}"` - * or `"{var=**}"`, when such a variable is expanded into a URL path on the - * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. - * The server side does the reverse decoding, except "%2F" and "%2f" are left - * unchanged. Such variables show up in the - * [Discovery - * Document](https://developers.google.com/discovery/v1/reference/apis) as - * `{+var}`. - * - * ## Using gRPC API Service Configuration - * - * gRPC API Service Configuration (service config) is a configuration language - * for configuring a gRPC service to become a user-facing product. The - * service config is simply the YAML representation of the `google.api.Service` - * proto message. - * - * As an alternative to annotating your proto file, you can configure gRPC - * transcoding in your service config YAML files. You do this by specifying a - * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same - * effect as the proto annotation. This can be particularly useful if you - * have a proto that is reused in multiple services. Note that any transcoding - * specified in the service config will override any matching transcoding - * configuration in the proto. - * - * Example: - * - * http: - * rules: - * # Selects a gRPC method and applies HttpRule to it. - * - selector: example.v1.Messaging.GetMessage - * get: /v1/messages/{message_id}/{sub.subfield} - * - * ## Special notes - * - * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the - * proto to JSON conversion must follow the [proto3 - * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). - * - * While the single segment variable follows the semantics of - * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String - * Expansion, the multi segment variable **does not** follow RFC 6570 Section - * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion - * does not expand special characters like `?` and `#`, which would lead - * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding - * for multi segment variables. - * - * The path variables **must not** refer to any repeated or mapped field, - * because client libraries are not capable of handling such variable expansion. - * - * The path variables **must not** capture the leading "/" character. The reason - * is that the most common use case "{var}" does not capture the leading "/" - * character. For consistency, all path variables must share the same behavior. - * - * Repeated message fields must not be mapped to URL query parameters, because - * no client library can support such complicated mapping. - * - * If an API needs to use a JSON array for request or response body, it can map - * the request or response body to a repeated field. However, some gRPC - * Transcoding implementations may not support this feature. - */ -export interface HttpRule__Output { - /** - * Selects a method to which this rule applies. - * - * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. - */ - 'selector': (string); - /** - * Maps to HTTP GET. Used for listing and getting information about - * resources. - */ - 'get'?: (string); - /** - * Maps to HTTP PUT. Used for replacing a resource. - */ - 'put'?: (string); - /** - * Maps to HTTP POST. Used for creating a resource or performing an action. - */ - 'post'?: (string); - /** - * Maps to HTTP DELETE. Used for deleting a resource. - */ - 'delete'?: (string); - /** - * Maps to HTTP PATCH. Used for updating a resource. - */ - 'patch'?: (string); - /** - * The name of the request field whose value is mapped to the HTTP request - * body, or `*` for mapping all request fields not captured by the path - * pattern to the HTTP body, or omitted for not having any HTTP request body. - * - * NOTE: the referred field must be present at the top-level of the request - * message type. - */ - 'body': (string); - /** - * The custom pattern is used for specifying an HTTP method that is not - * included in the `pattern` field, such as HEAD, or "*" to leave the - * HTTP method unspecified for this rule. The wild-card rule is useful - * for services that provide content to Web (HTML) clients. - */ - 'custom'?: (_google_api_CustomHttpPattern__Output); - /** - * Additional HTTP bindings for the selector. Nested bindings must - * not contain an `additional_bindings` field themselves (that is, - * the nesting may only be one level deep). - */ - 'additional_bindings': (_google_api_HttpRule__Output)[]; - /** - * Optional. The name of the response field whose value is mapped to the HTTP - * response body. When omitted, the entire response message will be used - * as the HTTP response body. - * - * NOTE: The referred field must be present at the top-level of the response - * message type. - */ - 'response_body': (string); - /** - * Determines the URL pattern is matched by this rules. This pattern can be - * used with any of the {get|put|post|delete|patch} methods. A custom method - * can be defined using the 'custom' field. - */ - 'pattern': "get"|"put"|"post"|"delete"|"patch"|"custom"; -} diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Any.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Any.ts index fe0d05f12..fcaa6724e 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/Any.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Any.ts @@ -7,7 +7,7 @@ export type Any = AnyExtension | { value: Buffer | Uint8Array | string; } -export type Any__Output = AnyExtension | { - type_url: string; - value: Buffer; +export interface Any__Output { + 'type_url': (string); + 'value': (Buffer); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/DoubleValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/DoubleValue.ts index e4f2eb4b8..d70b303c2 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/DoubleValue.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/DoubleValue.ts @@ -6,5 +6,5 @@ export interface DoubleValue { } export interface DoubleValue__Output { - 'value': (number | string); + 'value': (number); } 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 b76a60815..63f8a015f 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -2,6 +2,7 @@ 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 { FieldSecurityAnnotation as _udpa_annotations_FieldSecurityAnnotation, FieldSecurityAnnotation__Output as _udpa_annotations_FieldSecurityAnnotation__Output } from '../../udpa/annotations/FieldSecurityAnnotation'; import type { FieldMigrateAnnotation as _udpa_annotations_FieldMigrateAnnotation, FieldMigrateAnnotation__Output as _udpa_annotations_FieldMigrateAnnotation__Output } from '../../udpa/annotations/FieldMigrateAnnotation'; // Original file: null @@ -29,6 +30,7 @@ export interface FieldOptions { 'weak'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; '.validate.rules'?: (_validate_FieldRules); + '.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation); '.udpa.annotations.sensitive'?: (boolean); '.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation); '.envoy.annotations.disallowed_by_default'?: (boolean); @@ -43,6 +45,7 @@ export interface FieldOptions__Output { 'weak': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; '.validate.rules'?: (_validate_FieldRules__Output); + '.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation__Output); '.udpa.annotations.sensitive': (boolean); '.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation__Output); '.envoy.annotations.disallowed_by_default': (boolean); diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FloatValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FloatValue.ts index 144a9a585..54a655fbb 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FloatValue.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FloatValue.ts @@ -6,5 +6,5 @@ export interface FloatValue { } export interface FloatValue__Output { - 'value': (number | string); + 'value': (number); } 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 7560daa28..219e4bfd2 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts @@ -1,6 +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 { 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'; export interface MessageOptions { @@ -10,6 +11,7 @@ export interface MessageOptions { 'mapEntry'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; '.validate.disabled'?: (boolean); + '.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation); '.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation); } @@ -20,5 +22,6 @@ export interface MessageOptions__Output { 'mapEntry': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; '.validate.disabled': (boolean); + '.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation__Output); '.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation__Output); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/UninterpretedOption.ts b/packages/grpc-js-xds/src/generated/google/protobuf/UninterpretedOption.ts index 433820f55..6e9fc275b 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/UninterpretedOption.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/UninterpretedOption.ts @@ -27,7 +27,7 @@ export interface UninterpretedOption__Output { 'identifierValue': (string); 'positiveIntValue': (string); 'negativeIntValue': (string); - 'doubleValue': (number | string); + 'doubleValue': (number); 'stringValue': (Buffer); 'aggregateValue': (string); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts index 68b665dcb..0860535dc 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts @@ -16,7 +16,7 @@ export interface Value { export interface Value__Output { 'nullValue'?: (keyof typeof _google_protobuf_NullValue); - 'numberValue'?: (number | string); + 'numberValue'?: (number); 'stringValue'?: (string); 'boolValue'?: (boolean); 'structValue'?: (_google_protobuf_Struct__Output); diff --git a/packages/grpc-js-xds/src/generated/http_connection_manager.ts b/packages/grpc-js-xds/src/generated/http_connection_manager.ts index 7e822564d..9cb81bb0e 100644 --- a/packages/grpc-js-xds/src/generated/http_connection_manager.ts +++ b/packages/grpc-js-xds/src/generated/http_connection_manager.ts @@ -10,12 +10,28 @@ export interface ProtoGrpcType { envoy: { annotations: { } - api: { - v2: { - RouteConfiguration: MessageTypeDefinition - ScopedRouteConfiguration: MessageTypeDefinition - Vhds: MessageTypeDefinition - core: { + config: { + accesslog: { + v3: { + AccessLog: MessageTypeDefinition + AccessLogFilter: MessageTypeDefinition + AndFilter: MessageTypeDefinition + ComparisonFilter: MessageTypeDefinition + DurationFilter: MessageTypeDefinition + ExtensionFilter: MessageTypeDefinition + GrpcStatusFilter: MessageTypeDefinition + HeaderFilter: MessageTypeDefinition + MetadataFilter: MessageTypeDefinition + NotHealthCheckFilter: MessageTypeDefinition + OrFilter: MessageTypeDefinition + ResponseFlagFilter: MessageTypeDefinition + RuntimeFilter: MessageTypeDefinition + StatusCodeFilter: MessageTypeDefinition + TraceableFilter: MessageTypeDefinition + } + } + core: { + v3: { Address: MessageTypeDefinition AggregatedConfigSource: MessageTypeDefinition ApiConfigSource: MessageTypeDefinition @@ -28,7 +44,9 @@ export interface ProtoGrpcType { ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition GrpcProtocolOptions: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition @@ -36,12 +54,15 @@ export interface ProtoGrpcType { HeaderValueOption: MessageTypeDefinition Http1ProtocolOptions: MessageTypeDefinition Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition HttpProtocolOptions: MessageTypeDefinition HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -50,64 +71,64 @@ export interface ProtoGrpcType { RuntimeDouble: MessageTypeDefinition RuntimeFeatureFlag: MessageTypeDefinition RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SubstitutionFormatString: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TcpProtocolOptions: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition } - route: { + } + route: { + v3: { CorsPolicy: MessageTypeDefinition Decorator: MessageTypeDefinition DirectResponseAction: MessageTypeDefinition FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition HeaderMatcher: MessageTypeDefinition HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition QueryParameterMatcher: MessageTypeDefinition RateLimit: MessageTypeDefinition RedirectAction: MessageTypeDefinition RetryPolicy: MessageTypeDefinition Route: MessageTypeDefinition RouteAction: MessageTypeDefinition + RouteConfiguration: MessageTypeDefinition RouteMatch: MessageTypeDefinition + ScopedRouteConfiguration: MessageTypeDefinition Tracing: MessageTypeDefinition + Vhds: 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 - } + trace: { + v3: { + Tracing: MessageTypeDefinition } + } + } + extensions: { + filters: { network: { http_connection_manager: { - v2: { + v3: { HttpConnectionManager: MessageTypeDefinition HttpFilter: MessageTypeDefinition + LocalReplyConfig: MessageTypeDefinition Rds: MessageTypeDefinition RequestIDExtension: MessageTypeDefinition + ResponseMapper: MessageTypeDefinition ScopedRds: MessageTypeDefinition ScopedRouteConfigurationsList: MessageTypeDefinition ScopedRoutes: MessageTypeDefinition @@ -115,36 +136,39 @@ export interface ProtoGrpcType { } } } - 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 + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } } metadata: { - v2: { + v3: { MetadataKey: MessageTypeDefinition MetadataKind: MessageTypeDefinition } } tracing: { - v2: { + v3: { CustomTag: MessageTypeDefinition } } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } } } google: { @@ -191,10 +215,12 @@ export interface ProtoGrpcType { udpa: { annotations: { FieldMigrateAnnotation: MessageTypeDefinition + FieldSecurityAnnotation: MessageTypeDefinition FileMigrateAnnotation: MessageTypeDefinition MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition } } validate: { @@ -223,5 +249,12 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + core: { + v3: { + Authority: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/listener.ts b/packages/grpc-js-xds/src/generated/listener.ts index ab67cd096..69454efdc 100644 --- a/packages/grpc-js-xds/src/generated/listener.ts +++ b/packages/grpc-js-xds/src/generated/listener.ts @@ -10,23 +10,28 @@ export interface ProtoGrpcType { envoy: { annotations: { } - api: { - v2: { - Listener: MessageTypeDefinition - auth: { - CertificateValidationContext: MessageTypeDefinition - CommonTlsContext: MessageTypeDefinition - DownstreamTlsContext: MessageTypeDefinition - GenericSecret: MessageTypeDefinition - PrivateKeyProvider: MessageTypeDefinition - SdsSecretConfig: MessageTypeDefinition - Secret: MessageTypeDefinition - TlsCertificate: MessageTypeDefinition - TlsParameters: MessageTypeDefinition - TlsSessionTicketKeys: MessageTypeDefinition - UpstreamTlsContext: MessageTypeDefinition + config: { + accesslog: { + v3: { + AccessLog: MessageTypeDefinition + AccessLogFilter: MessageTypeDefinition + AndFilter: MessageTypeDefinition + ComparisonFilter: MessageTypeDefinition + DurationFilter: MessageTypeDefinition + ExtensionFilter: MessageTypeDefinition + GrpcStatusFilter: MessageTypeDefinition + HeaderFilter: MessageTypeDefinition + MetadataFilter: MessageTypeDefinition + NotHealthCheckFilter: MessageTypeDefinition + OrFilter: MessageTypeDefinition + ResponseFlagFilter: MessageTypeDefinition + RuntimeFilter: MessageTypeDefinition + StatusCodeFilter: MessageTypeDefinition + TraceableFilter: MessageTypeDefinition } - core: { + } + core: { + v3: { Address: MessageTypeDefinition AggregatedConfigSource: MessageTypeDefinition ApiConfigSource: MessageTypeDefinition @@ -39,7 +44,9 @@ export interface ProtoGrpcType { ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition @@ -49,6 +56,7 @@ export interface ProtoGrpcType { Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -57,6 +65,7 @@ export interface ProtoGrpcType { RuntimeDouble: MessageTypeDefinition RuntimeFeatureFlag: MessageTypeDefinition RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition @@ -64,23 +73,34 @@ export interface ProtoGrpcType { TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition } - listener: { + } + listener: { + v3: { ActiveRawUdpListenerConfig: MessageTypeDefinition + ApiListener: MessageTypeDefinition Filter: MessageTypeDefinition FilterChain: MessageTypeDefinition FilterChainMatch: MessageTypeDefinition + Listener: MessageTypeDefinition + ListenerCollection: MessageTypeDefinition ListenerFilter: MessageTypeDefinition ListenerFilterChainMatchPredicate: MessageTypeDefinition UdpListenerConfig: MessageTypeDefinition } - route: { + } + route: { + v3: { CorsPolicy: MessageTypeDefinition Decorator: MessageTypeDefinition DirectResponseAction: MessageTypeDefinition FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition HeaderMatcher: MessageTypeDefinition HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition QueryParameterMatcher: MessageTypeDefinition RateLimit: MessageTypeDefinition RedirectAction: MessageTypeDefinition @@ -95,65 +115,41 @@ export interface ProtoGrpcType { } } } - 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 - } - } - } - listener: { - v2: { - ApiListener: MessageTypeDefinition - } - } - } type: { - DoubleRange: MessageTypeDefinition - FractionalPercent: MessageTypeDefinition - Int32Range: MessageTypeDefinition - Int64Range: MessageTypeDefinition - Percent: MessageTypeDefinition - SemanticVersion: MessageTypeDefinition matcher: { - ListStringMatcher: MessageTypeDefinition - RegexMatchAndSubstitute: MessageTypeDefinition - RegexMatcher: MessageTypeDefinition - StringMatcher: MessageTypeDefinition + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } } metadata: { - v2: { + v3: { MetadataKey: MessageTypeDefinition MetadataKind: MessageTypeDefinition } } tracing: { - v2: { + v3: { CustomTag: MessageTypeDefinition } } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } } } google: { - api: { - CustomHttpPattern: MessageTypeDefinition - Http: MessageTypeDefinition - HttpRule: MessageTypeDefinition - } protobuf: { Any: MessageTypeDefinition BoolValue: MessageTypeDefinition @@ -197,10 +193,12 @@ export interface ProtoGrpcType { udpa: { annotations: { FieldMigrateAnnotation: MessageTypeDefinition + FieldSecurityAnnotation: MessageTypeDefinition FileMigrateAnnotation: MessageTypeDefinition MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition } } validate: { @@ -229,5 +227,15 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + core: { + v3: { + Authority: MessageTypeDefinition + CollectionEntry: MessageTypeDefinition + ContextParams: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/lrs.ts b/packages/grpc-js-xds/src/generated/lrs.ts index f3f180807..9a2864fcf 100644 --- a/packages/grpc-js-xds/src/generated/lrs.ts +++ b/packages/grpc-js-xds/src/generated/lrs.ts @@ -2,6 +2,7 @@ import type * as grpc from '@grpc/grpc-js'; import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; import type { LoadReportingServiceClient as _envoy_service_load_stats_v2_LoadReportingServiceClient } from './envoy/service/load_stats/v2/LoadReportingService'; +import type { LoadReportingServiceClient as _envoy_service_load_stats_v3_LoadReportingServiceClient } from './envoy/service/load_stats/v3/LoadReportingService'; type SubtypeConstructor any, Subtype> = { new(...args: ConstructorParameters): Subtype; @@ -51,6 +52,53 @@ export interface ProtoGrpcType { } } } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + endpoint: { + v3: { + ClusterStats: MessageTypeDefinition + EndpointLoadMetricStats: MessageTypeDefinition + UpstreamEndpointStats: MessageTypeDefinition + UpstreamLocalityStats: MessageTypeDefinition + } + } + } service: { load_stats: { v2: { @@ -58,12 +106,22 @@ export interface ProtoGrpcType { LoadStatsRequest: MessageTypeDefinition LoadStatsResponse: MessageTypeDefinition } + v3: { + LoadReportingService: SubtypeConstructor & { service: ServiceDefinition } + LoadStatsRequest: MessageTypeDefinition + LoadStatsResponse: MessageTypeDefinition + } } } type: { FractionalPercent: MessageTypeDefinition Percent: MessageTypeDefinition SemanticVersion: MessageTypeDefinition + v3: { + FractionalPercent: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } } } google: { @@ -113,6 +171,7 @@ export interface ProtoGrpcType { MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition } } validate: { diff --git a/packages/grpc-js-xds/src/generated/route.ts b/packages/grpc-js-xds/src/generated/route.ts index 05332fe37..a51060779 100644 --- a/packages/grpc-js-xds/src/generated/route.ts +++ b/packages/grpc-js-xds/src/generated/route.ts @@ -10,11 +10,9 @@ export interface ProtoGrpcType { envoy: { annotations: { } - api: { - v2: { - RouteConfiguration: MessageTypeDefinition - Vhds: MessageTypeDefinition - core: { + config: { + core: { + v3: { Address: MessageTypeDefinition AggregatedConfigSource: MessageTypeDefinition ApiConfigSource: MessageTypeDefinition @@ -27,7 +25,9 @@ export interface ProtoGrpcType { ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition @@ -37,6 +37,7 @@ export interface ProtoGrpcType { Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -45,6 +46,7 @@ export interface ProtoGrpcType { RuntimeDouble: MessageTypeDefinition RuntimeFeatureFlag: MessageTypeDefinition RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition @@ -52,22 +54,30 @@ export interface ProtoGrpcType { TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition } - route: { + } + route: { + v3: { CorsPolicy: MessageTypeDefinition Decorator: MessageTypeDefinition DirectResponseAction: MessageTypeDefinition FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition HeaderMatcher: MessageTypeDefinition HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition QueryParameterMatcher: MessageTypeDefinition RateLimit: MessageTypeDefinition RedirectAction: MessageTypeDefinition RetryPolicy: MessageTypeDefinition Route: MessageTypeDefinition RouteAction: MessageTypeDefinition + RouteConfiguration: MessageTypeDefinition RouteMatch: MessageTypeDefinition Tracing: MessageTypeDefinition + Vhds: MessageTypeDefinition VirtualCluster: MessageTypeDefinition VirtualHost: MessageTypeDefinition WeightedCluster: MessageTypeDefinition @@ -75,29 +85,33 @@ export interface ProtoGrpcType { } } type: { - DoubleRange: MessageTypeDefinition - FractionalPercent: MessageTypeDefinition - Int32Range: MessageTypeDefinition - Int64Range: MessageTypeDefinition - Percent: MessageTypeDefinition - SemanticVersion: MessageTypeDefinition matcher: { - ListStringMatcher: MessageTypeDefinition - RegexMatchAndSubstitute: MessageTypeDefinition - RegexMatcher: MessageTypeDefinition - StringMatcher: MessageTypeDefinition + v3: { + ListStringMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } } metadata: { - v2: { + v3: { MetadataKey: MessageTypeDefinition MetadataKind: MessageTypeDefinition } } tracing: { - v2: { + v3: { CustomTag: MessageTypeDefinition } } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } } } google: { @@ -148,6 +162,7 @@ export interface ProtoGrpcType { MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: MessageTypeDefinition } } validate: { @@ -176,5 +191,12 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + core: { + v3: { + Authority: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts new file mode 100644 index 000000000..13d48b5ce --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts @@ -0,0 +1,32 @@ +// Original file: deps/udpa/udpa/annotations/security.proto + + +/** + * These annotations indicate metadata for the purpose of understanding the + * security significance of fields. + */ +export interface FieldSecurityAnnotation { + /** + * Field should be set in the presence of untrusted downstreams. + */ + 'configure_for_untrusted_downstream'?: (boolean); + /** + * Field should be set in the presence of untrusted upstreams. + */ + 'configure_for_untrusted_upstream'?: (boolean); +} + +/** + * These annotations indicate metadata for the purpose of understanding the + * security significance of fields. + */ +export interface FieldSecurityAnnotation__Output { + /** + * Field should be set in the presence of untrusted downstreams. + */ + 'configure_for_untrusted_downstream': (boolean); + /** + * Field should be set in the presence of untrusted upstreams. + */ + 'configure_for_untrusted_upstream': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts new file mode 100644 index 000000000..1a3d09dc8 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts @@ -0,0 +1,20 @@ +// Original file: deps/udpa/udpa/annotations/versioning.proto + + +export interface VersioningAnnotation { + /** + * Track the previous message type. E.g. this message might be + * udpa.foo.v3alpha.Foo and it was previously udpa.bar.v2.Bar. This + * information is consumed by UDPA via proto descriptors. + */ + 'previous_message_type'?: (string); +} + +export interface VersioningAnnotation__Output { + /** + * Track the previous message type. E.g. this message might be + * udpa.foo.v3alpha.Foo and it was previously udpa.bar.v2.Bar. This + * information is consumed by UDPA via proto descriptors. + */ + 'previous_message_type': (string); +} diff --git a/packages/grpc-js-xds/src/generated/validate/DoubleRules.ts b/packages/grpc-js-xds/src/generated/validate/DoubleRules.ts index fead5072a..e756c86b9 100644 --- a/packages/grpc-js-xds/src/generated/validate/DoubleRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/DoubleRules.ts @@ -50,37 +50,37 @@ export interface DoubleRules__Output { /** * Const specifies that this field must be exactly the specified value */ - 'const': (number | string); + 'const': (number); /** * Lt specifies that this field must be less than the specified value, * exclusive */ - 'lt': (number | string); + 'lt': (number); /** * Lte specifies that this field must be less than or equal to the * specified value, inclusive */ - 'lte': (number | string); + 'lte': (number); /** * Gt specifies that this field must be greater than the specified value, * exclusive. If the value of Gt is larger than a specified Lt or Lte, the * range is reversed. */ - 'gt': (number | string); + 'gt': (number); /** * Gte specifies that this field must be greater than or equal to the * specified value, inclusive. If the value of Gte is larger than a * specified Lt or Lte, the range is reversed. */ - 'gte': (number | string); + 'gte': (number); /** * In specifies that this field must be equal to one of the specified * values */ - 'in': (number | string)[]; + 'in': (number)[]; /** * NotIn specifies that this field cannot be equal to one of the specified * values */ - 'not_in': (number | string)[]; + 'not_in': (number)[]; } diff --git a/packages/grpc-js-xds/src/generated/validate/FloatRules.ts b/packages/grpc-js-xds/src/generated/validate/FloatRules.ts index 35aafa809..8d5244c2b 100644 --- a/packages/grpc-js-xds/src/generated/validate/FloatRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/FloatRules.ts @@ -50,37 +50,37 @@ export interface FloatRules__Output { /** * Const specifies that this field must be exactly the specified value */ - 'const': (number | string); + 'const': (number); /** * Lt specifies that this field must be less than the specified value, * exclusive */ - 'lt': (number | string); + 'lt': (number); /** * Lte specifies that this field must be less than or equal to the * specified value, inclusive */ - 'lte': (number | string); + 'lte': (number); /** * Gt specifies that this field must be greater than the specified value, * exclusive. If the value of Gt is larger than a specified Lt or Lte, the * range is reversed. */ - 'gt': (number | string); + 'gt': (number); /** * Gte specifies that this field must be greater than or equal to the * specified value, inclusive. If the value of Gte is larger than a * specified Lt or Lte, the range is reversed. */ - 'gte': (number | string); + 'gte': (number); /** * In specifies that this field must be equal to one of the specified * values */ - 'in': (number | string)[]; + 'in': (number)[]; /** * NotIn specifies that this field cannot be equal to one of the specified * values */ - 'not_in': (number | string)[]; + 'not_in': (number)[]; } diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts new file mode 100644 index 000000000..8e9211838 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts @@ -0,0 +1,16 @@ +// Original file: deps/udpa/xds/core/v3/authority.proto + + +/** + * xDS authority information. + */ +export interface Authority { + 'name'?: (string); +} + +/** + * xDS authority information. + */ +export interface Authority__Output { + 'name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts new file mode 100644 index 000000000..4a9a45527 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts @@ -0,0 +1,90 @@ +// Original file: deps/udpa/xds/core/v3/collection_entry.proto + +import type { ResourceLocator as _xds_core_v3_ResourceLocator, ResourceLocator__Output as _xds_core_v3_ResourceLocator__Output } from '../../../xds/core/v3/ResourceLocator'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * Inlined resource entry. + */ +export interface _xds_core_v3_CollectionEntry_InlineEntry { + /** + * Optional name to describe the inlined resource. Resource names must + * [a-zA-Z0-9_-\./]+ (TODO(htuch): turn this into a PGV constraint once + * finalized, probably should be a RFC3986 pchar). This name allows + * reference via the #entry directive in ResourceLocator. + */ + 'name'?: (string); + /** + * The resource's logical version. It is illegal to have the same named xDS + * resource name at a given version with different resource payloads. + */ + 'version'?: (string); + /** + * The resource payload, including type URL. + */ + 'resource'?: (_google_protobuf_Any); +} + +/** + * Inlined resource entry. + */ +export interface _xds_core_v3_CollectionEntry_InlineEntry__Output { + /** + * Optional name to describe the inlined resource. Resource names must + * [a-zA-Z0-9_-\./]+ (TODO(htuch): turn this into a PGV constraint once + * finalized, probably should be a RFC3986 pchar). This name allows + * reference via the #entry directive in ResourceLocator. + */ + 'name': (string); + /** + * The resource's logical version. It is illegal to have the same named xDS + * resource name at a given version with different resource payloads. + */ + 'version': (string); + /** + * The resource payload, including type URL. + */ + 'resource'?: (_google_protobuf_Any__Output); +} + +/** + * xDS collection resource wrapper. This encapsulates a xDS resource when + * appearing inside a list collection resource. List collection resources are + * regular Resource messages of type: + * + * message Collection { + * repeated CollectionEntry resources = 1; + * } + */ +export interface CollectionEntry { + /** + * A resource locator describing how the member resource is to be located. + */ + 'locator'?: (_xds_core_v3_ResourceLocator); + /** + * The resource is inlined in the list collection. + */ + 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry); + 'resource_specifier'?: "locator"|"inline_entry"; +} + +/** + * xDS collection resource wrapper. This encapsulates a xDS resource when + * appearing inside a list collection resource. List collection resources are + * regular Resource messages of type: + * + * message Collection { + * repeated CollectionEntry resources = 1; + * } + */ +export interface CollectionEntry__Output { + /** + * A resource locator describing how the member resource is to be located. + */ + 'locator'?: (_xds_core_v3_ResourceLocator__Output); + /** + * The resource is inlined in the list collection. + */ + 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry__Output); + 'resource_specifier': "locator"|"inline_entry"; +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts new file mode 100644 index 000000000..f9c57249c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts @@ -0,0 +1,26 @@ +// Original file: deps/udpa/xds/core/v3/context_params.proto + + +/** + * Additional parameters that can be used to select resource variants. These include any + * global context parameters, per-resource type client feature capabilities and per-resource + * type functional attributes. All per-resource type attributes will be `xds.resource.` + * prefixed and some of these are documented below: + * `xds.resource.listening_address`: The value is "IP:port" (e.g. "10.1.1.3:8080") which is + * the listening address of a Listener. Used in a Listener resource query. + */ +export interface ContextParams { + 'params'?: ({[key: string]: string}); +} + +/** + * Additional parameters that can be used to select resource variants. These include any + * global context parameters, per-resource type client feature capabilities and per-resource + * type functional attributes. All per-resource type attributes will be `xds.resource.` + * prefixed and some of these are documented below: + * `xds.resource.listening_address`: The value is "IP:port" (e.g. "10.1.1.3:8080") which is + * the listening address of a Listener. Used in a Listener resource query. + */ +export interface ContextParams__Output { + 'params': ({[key: string]: string}); +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts new file mode 100644 index 000000000..f28a31d20 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts @@ -0,0 +1,220 @@ +// Original file: deps/udpa/xds/core/v3/resource_locator.proto + +import type { ContextParams as _xds_core_v3_ContextParams, ContextParams__Output as _xds_core_v3_ContextParams__Output } from '../../../xds/core/v3/ContextParams'; +import type { ResourceLocator as _xds_core_v3_ResourceLocator, ResourceLocator__Output as _xds_core_v3_ResourceLocator__Output } from '../../../xds/core/v3/ResourceLocator'; + +/** + * Directives provide information to data-plane load balancers on how xDS + * resource names are to be interpreted and potentially further resolved. For + * example, they may provide alternative resource locators for when primary + * resolution fails. Directives are not part of resource names and do not + * appear in a xDS transport discovery request. + * + * When encoding to URIs, directives take the form: + * + * = + * + * For example, we can have alt=xdstp://foo/bar or entry=some%20thing. Each + * directive value type may have its own string encoding, in the case of + * ResourceLocator there is a recursive URI encoding. + * + * Percent encoding applies to the URI encoding of the directive value. + * Multiple directives are comma-separated, so the reserved characters that + * require percent encoding in a directive value are [',', '#', '[', ']', + * '%']. These are the RFC3986 fragment reserved characters with the addition + * of the xDS scheme specific ','. See + * https://tools.ietf.org/html/rfc3986#page-49 for further details on URI ABNF + * and reserved characters. + */ +export interface _xds_core_v3_ResourceLocator_Directive { + /** + * An alternative resource locator for fallback if the resource is + * unavailable. For example, take the resource locator: + * + * xdstp://foo/some-type/some-route-table#alt=xdstp://bar/some-type/another-route-table + * + * If the data-plane load balancer is unable to reach `foo` to fetch the + * resource, it will fallback to `bar`. Alternative resources do not need + * to have equivalent content, but they should be functional substitutes. + */ + 'alt'?: (_xds_core_v3_ResourceLocator); + /** + * List collections support inlining of resources via the entry field in + * Resource. These inlined Resource objects may have an optional name + * field specified. When specified, the entry directive allows + * ResourceLocator to directly reference these inlined resources, e.g. + * xdstp://.../foo#entry=bar. + */ + 'entry'?: (string); + 'directive'?: "alt"|"entry"; +} + +/** + * Directives provide information to data-plane load balancers on how xDS + * resource names are to be interpreted and potentially further resolved. For + * example, they may provide alternative resource locators for when primary + * resolution fails. Directives are not part of resource names and do not + * appear in a xDS transport discovery request. + * + * When encoding to URIs, directives take the form: + * + * = + * + * For example, we can have alt=xdstp://foo/bar or entry=some%20thing. Each + * directive value type may have its own string encoding, in the case of + * ResourceLocator there is a recursive URI encoding. + * + * Percent encoding applies to the URI encoding of the directive value. + * Multiple directives are comma-separated, so the reserved characters that + * require percent encoding in a directive value are [',', '#', '[', ']', + * '%']. These are the RFC3986 fragment reserved characters with the addition + * of the xDS scheme specific ','. See + * https://tools.ietf.org/html/rfc3986#page-49 for further details on URI ABNF + * and reserved characters. + */ +export interface _xds_core_v3_ResourceLocator_Directive__Output { + /** + * An alternative resource locator for fallback if the resource is + * unavailable. For example, take the resource locator: + * + * xdstp://foo/some-type/some-route-table#alt=xdstp://bar/some-type/another-route-table + * + * If the data-plane load balancer is unable to reach `foo` to fetch the + * resource, it will fallback to `bar`. Alternative resources do not need + * to have equivalent content, but they should be functional substitutes. + */ + 'alt'?: (_xds_core_v3_ResourceLocator__Output); + /** + * List collections support inlining of resources via the entry field in + * Resource. These inlined Resource objects may have an optional name + * field specified. When specified, the entry directive allows + * ResourceLocator to directly reference these inlined resources, e.g. + * xdstp://.../foo#entry=bar. + */ + 'entry'?: (string); + 'directive': "alt"|"entry"; +} + +// Original file: deps/udpa/xds/core/v3/resource_locator.proto + +export enum _xds_core_v3_ResourceLocator_Scheme { + XDSTP = 0, + HTTP = 1, + FILE = 2, +} + +/** + * xDS resource locators identify a xDS resource name and instruct the + * data-plane load balancer on how the resource may be located. + * + * Resource locators have a canonical xdstp:// URI representation: + * + * xdstp://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * where context_params take the form of URI query parameters. + * + * Resource locators have a similar canonical http:// URI representation: + * + * http://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * Resource locators also have a simplified file:// URI representation: + * + * file:///{id}{#directive,*} + */ +export interface ResourceLocator { + /** + * URI scheme. + */ + 'scheme'?: (_xds_core_v3_ResourceLocator_Scheme | keyof typeof _xds_core_v3_ResourceLocator_Scheme); + /** + * Opaque identifier for the resource. Any '/' will not be escaped during URI + * encoding and will form part of the URI path. This may end + * with ‘*’ for glob collection references. + */ + 'id'?: (string); + /** + * Logical authority for resource (not necessarily transport network address). + * Authorities are opaque in the xDS API, data-plane load balancers will map + * them to concrete network transports such as an xDS management server, e.g. + * via envoy.config.core.v3.ConfigSource. + */ + 'authority'?: (string); + /** + * Fully qualified resource type (as in type URL without types.googleapis.com/ + * prefix). + */ + 'resource_type'?: (string); + /** + * Additional parameters that can be used to select resource variants. + * Matches must be exact, i.e. all context parameters must match exactly and + * there must be no additional context parameters set on the matched + * resource. + */ + 'exact_context'?: (_xds_core_v3_ContextParams); + /** + * A list of directives that appear in the xDS resource locator #fragment. + * + * When encoding to URI form, directives are percent encoded with comma + * separation. + */ + 'directives'?: (_xds_core_v3_ResourceLocator_Directive)[]; + 'context_param_specifier'?: "exact_context"; +} + +/** + * xDS resource locators identify a xDS resource name and instruct the + * data-plane load balancer on how the resource may be located. + * + * Resource locators have a canonical xdstp:// URI representation: + * + * xdstp://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * where context_params take the form of URI query parameters. + * + * Resource locators have a similar canonical http:// URI representation: + * + * http://{authority}/{type_url}/{id}?{context_params}{#directive,*} + * + * Resource locators also have a simplified file:// URI representation: + * + * file:///{id}{#directive,*} + */ +export interface ResourceLocator__Output { + /** + * URI scheme. + */ + 'scheme': (keyof typeof _xds_core_v3_ResourceLocator_Scheme); + /** + * Opaque identifier for the resource. Any '/' will not be escaped during URI + * encoding and will form part of the URI path. This may end + * with ‘*’ for glob collection references. + */ + 'id': (string); + /** + * Logical authority for resource (not necessarily transport network address). + * Authorities are opaque in the xDS API, data-plane load balancers will map + * them to concrete network transports such as an xDS management server, e.g. + * via envoy.config.core.v3.ConfigSource. + */ + 'authority': (string); + /** + * Fully qualified resource type (as in type URL without types.googleapis.com/ + * prefix). + */ + 'resource_type': (string); + /** + * Additional parameters that can be used to select resource variants. + * Matches must be exact, i.e. all context parameters must match exactly and + * there must be no additional context parameters set on the matched + * resource. + */ + 'exact_context'?: (_xds_core_v3_ContextParams__Output); + /** + * A list of directives that appear in the xDS resource locator #fragment. + * + * When encoding to URI form, directives are percent encoded with comma + * separation. + */ + 'directives': (_xds_core_v3_ResourceLocator_Directive__Output)[]; + 'context_param_specifier': "exact_context"; +} From 6711620c1a36dc20ed7609afc2f9f35c53651001 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 14 Apr 2021 14:02:10 -0700 Subject: [PATCH 002/694] grpc-js-xds: Add xDS v3 support to the client Add xDS v3 test job --- packages/grpc-js-xds/scripts/xds-v3.sh | 16 + packages/grpc-js-xds/scripts/xds.sh | 1 + packages/grpc-js-xds/src/load-balancer-cds.ts | 2 +- packages/grpc-js-xds/src/load-balancer-eds.ts | 2 +- packages/grpc-js-xds/src/resolver-xds.ts | 18 +- packages/grpc-js-xds/src/resources.ts | 96 +++ packages/grpc-js-xds/src/xds-bootstrap.ts | 33 +- packages/grpc-js-xds/src/xds-client.ts | 749 +++++++++++------- .../src/xds-stream-state/cds-state.ts | 2 +- .../src/xds-stream-state/eds-state.ts | 2 +- .../src/xds-stream-state/lds-state.ts | 17 +- .../src/xds-stream-state/rds-state.ts | 2 +- test/kokoro/linux.cfg | 3 +- test/kokoro/xds-v3-interop.cfg | 24 + 14 files changed, 661 insertions(+), 306 deletions(-) create mode 100755 packages/grpc-js-xds/scripts/xds-v3.sh mode change 100644 => 100755 packages/grpc-js-xds/scripts/xds.sh create mode 100644 packages/grpc-js-xds/src/resources.ts create mode 100644 test/kokoro/xds-v3-interop.cfg diff --git a/packages/grpc-js-xds/scripts/xds-v3.sh b/packages/grpc-js-xds/scripts/xds-v3.sh new file mode 100755 index 000000000..103cbc429 --- /dev/null +++ b/packages/grpc-js-xds/scripts/xds-v3.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2021 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. + +XDS_V3_OPT="--xds_v3_support" $(dirname $0)/xds.sh \ No newline at end of file diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh old mode 100644 new mode 100755 index 714b6fff8..9008b9731 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -58,6 +58,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ + ${XDS_V3_OPT-} \ --client_cmd="$(which node) grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ --stats_port={stats_port} \ diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index d0fe2338a..6c57a410f 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -17,7 +17,7 @@ import { connectivityState, status, Metadata, logVerbosity, experimental } from '@grpc/grpc-js'; import { getSingletonXdsClient, XdsClient } from './xds-client'; -import { Cluster__Output } from './generated/envoy/api/v2/Cluster'; +import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import SubchannelAddress = experimental.SubchannelAddress; import UnavailablePicker = experimental.UnavailablePicker; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index 657dc3a82..ae9a781bd 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -17,7 +17,7 @@ import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental } from '@grpc/grpc-js'; import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from './xds-client'; -import { ClusterLoadAssignment__Output } from './generated/envoy/api/v2/ClusterLoadAssignment'; +import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; import { Locality__Output } from './generated/envoy/api/v2/core/Locality'; import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from './load-balancer-priority'; import LoadBalancer = experimental.LoadBalancer; diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 102a52349..a9ee1af7d 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -26,20 +26,20 @@ import ResolverListener = experimental.ResolverListener; import uriToString = experimental.uriToString; import ServiceConfig = experimental.ServiceConfig; import registerResolver = experimental.registerResolver; -import { Listener__Output } from './generated/envoy/api/v2/Listener'; +import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; import { Watcher } from './xds-stream-state/xds-stream-state'; -import { RouteConfiguration__Output } from './generated/envoy/api/v2/RouteConfiguration'; -import { HttpConnectionManager__Output } from './generated/envoy/config/filter/network/http_connection_manager/v2/HttpConnectionManager'; +import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; +import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; import { CdsLoadBalancingConfig } from './load-balancer-cds'; -import { VirtualHost__Output } from './generated/envoy/api/v2/route/VirtualHost'; -import { RouteMatch__Output } from './generated/envoy/api/v2/route/RouteMatch'; -import { HeaderMatcher__Output } from './generated/envoy/api/v2/route/HeaderMatcher'; +import { VirtualHost__Output } from './generated/envoy/config/route/v3/VirtualHost'; +import { RouteMatch__Output } from './generated/envoy/config/route/v3/RouteMatch'; +import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderMatcher'; import ConfigSelector = experimental.ConfigSelector; import LoadBalancingConfig = experimental.LoadBalancingConfig; import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluster-manager'; import { ExactValueMatcher, Fraction, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; -import { LogVerbosity } from '@grpc/grpc-js/build/src/constants'; +import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from './resources'; const TRACER_NAME = 'xds_resolver'; @@ -210,9 +210,7 @@ class XdsResolver implements Resolver { ) { this.ldsWatcher = { onValidUpdate: (update: Listener__Output) => { - const httpConnectionManager = update.api_listener! - .api_listener as protoLoader.AnyExtension & - HttpConnectionManager__Output; + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, update.api_listener!.api_listener!.value); switch (httpConnectionManager.route_specifier) { case 'rds': { const routeConfigName = httpConnectionManager.rds!.route_config_name; diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts new file mode 100644 index 000000000..516980de8 --- /dev/null +++ b/packages/grpc-js-xds/src/resources.ts @@ -0,0 +1,96 @@ +/* + * Copyright 2021 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. + * + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; +import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; +import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; +import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; +import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; + +export const EDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.ClusterLoadAssignment'; +export const CDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.Cluster'; +export const LDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.Listener'; +export const RDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.RouteConfiguration'; + +export const EDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; +export const CDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; +export const LDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.listener.v3.Listener'; +export const RDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; + +export type EdsTypeUrl = 'type.googleapis.com/envoy.api.v2.ClusterLoadAssignment' | 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; +export type CdsTypeUrl = 'type.googleapis.com/envoy.api.v2.Cluster' | 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; +export type LdsTypeUrl = 'type.googleapis.com/envoy.api.v2.Listener' | 'type.googleapis.com/envoy.config.listener.v3.Listener'; +export type RdsTypeUrl = 'type.googleapis.com/envoy.api.v2.RouteConfiguration' | 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; + +export type AdsTypeUrl = EdsTypeUrl | CdsTypeUrl | RdsTypeUrl | LdsTypeUrl; + +export const HTTP_CONNECTION_MANGER_TYPE_URL_V2 = + 'type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager'; +export const HTTP_CONNECTION_MANGER_TYPE_URL_V3 = + 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; + +export type HttpConnectionManagerTypeUrl = 'type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager' | 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; + +/** + * Map type URLs to their corresponding message types + */ +export type AdsOutputType = T extends EdsTypeUrl + ? ClusterLoadAssignment__Output + : T extends CdsTypeUrl + ? Cluster__Output + : T extends RdsTypeUrl + ? RouteConfiguration__Output + : T extends LdsTypeUrl + ? Listener__Output + : HttpConnectionManager__Output; + +const resourceRoot = loadProtosWithOptionsSync([ + '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'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/udpa/', + __dirname + '/../../deps/googleapis/', + __dirname + '/../../deps/protoc-gen-validate/', + ], + } +); + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +export function decodeSingleResource(targetTypeUrl: T, message: Buffer): AdsOutputType { + const name = targetTypeUrl.substring(targetTypeUrl.lastIndexOf('/') + 1); + const type = resourceRoot.lookup(name); + if (type) { + const decodedMessage = (type as any).decode(message); + return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as AdsOutputType; + } else { + throw new Error(`ADS Error: unknown resource type ${targetTypeUrl}`); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 00e13d09f..3da4ec3e6 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -17,11 +17,23 @@ import * as fs from 'fs'; import { Struct } from './generated/google/protobuf/Struct'; -import { Node } from './generated/envoy/api/v2/core/Node'; import { Value } from './generated/google/protobuf/Value'; /* eslint-disable @typescript-eslint/no-explicit-any */ +export interface Locality { + region?: string; + zone?: string; + sub_zone?: string; +} + +export interface Node { + id: string, + locality: Locality; + cluster?: string; + metadata?: Struct; +} + export interface ChannelCredsConfig { type: string; config?: object; @@ -30,6 +42,7 @@ export interface ChannelCredsConfig { export interface XdsServerConfig { serverUri: string; channelCreds: ChannelCredsConfig[]; + serverFeatures: string[]; } export interface BootstrapInfo { @@ -81,9 +94,22 @@ function validateXdsServerConfig(obj: any): XdsServerConfig { 'xds_servers.channel_creds field: at least one entry is required' ); } + if ('server_features' in obj) { + if (!Array.isArray(obj.server_features)) { + throw new Error( + `xds_servers.server_features field: expected array, got ${typeof obj.server_features}` + ); + } + for (const feature of obj.server_features) { + if (typeof feature !== 'string') { + `xds_servers.server_features field element: expected string, got ${typeof feature}` + } + } + } return { serverUri: obj.server_uri, channelCreds: obj.channel_creds.map(validateChannelCredsConfig), + serverFeatures: obj.server_features }; } @@ -149,7 +175,10 @@ function getStructFromJson(obj: any): Struct { * @param obj */ function validateNode(obj: any): Node { - const result: Node = {}; + const result: Node = { + id: '', + locality: {} + }; if (!('id' in obj)) { throw new Error('id field missing in node element'); } diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 22d816a03..adc54ed45 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -16,35 +16,26 @@ */ import * as protoLoader from '@grpc/proto-loader'; -import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials } from '@grpc/grpc-js'; +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel } from '@grpc/grpc-js'; import * as adsTypes from './generated/ads'; import * as lrsTypes from './generated/lrs'; import { loadBootstrapInfo } from './xds-bootstrap'; -import { isIPv4, isIPv6 } from 'net'; -import { Node } from './generated/envoy/api/v2/core/Node'; -import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v2/AggregatedDiscoveryService'; -import { DiscoveryRequest } from './generated/envoy/api/v2/DiscoveryRequest'; -import { DiscoveryResponse__Output } from './generated/envoy/api/v2/DiscoveryResponse'; -import { - ClusterLoadAssignment__Output, - ClusterLoadAssignment, -} from './generated/envoy/api/v2/ClusterLoadAssignment'; -import { Cluster__Output } from './generated/envoy/api/v2/Cluster'; -import { LoadReportingServiceClient } from './generated/envoy/service/load_stats/v2/LoadReportingService'; -import { LoadStatsRequest } from './generated/envoy/service/load_stats/v2/LoadStatsRequest'; -import { LoadStatsResponse__Output } from './generated/envoy/service/load_stats/v2/LoadStatsResponse'; -import { - Locality__Output, - Locality, -} from './generated/envoy/api/v2/core/Locality'; -import { - ClusterStats, - _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'; +import { Node as NodeV2 } from './generated/envoy/api/v2/core/Node'; +import { Node as NodeV3 } from './generated/envoy/config/core/v3/Node'; +import { AggregatedDiscoveryServiceClient as AggregatedDiscoveryServiceClientV2 } from './generated/envoy/service/discovery/v2/AggregatedDiscoveryService'; +import { AggregatedDiscoveryServiceClient as AggregatedDiscoveryServiceClientV3 } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; +import { DiscoveryRequest as DiscoveryRequestV2 } from './generated/envoy/api/v2/DiscoveryRequest'; +import { DiscoveryRequest as DiscoveryRequestV3 } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; +import { DiscoveryResponse__Output } from './generated/envoy/service/discovery/v3/DiscoveryResponse'; +import { LoadReportingServiceClient as LoadReportingServiceClientV2 } from './generated/envoy/service/load_stats/v2/LoadReportingService'; +import { LoadReportingServiceClient as LoadReportingServiceClientV3 } from './generated/envoy/service/load_stats/v3/LoadReportingService'; +import { LoadStatsRequest as LoadStatsRequestV2 } from './generated/envoy/service/load_stats/v2/LoadStatsRequest'; +import { LoadStatsRequest as LoadStatsRequestV3 } from './generated/envoy/service/load_stats/v3/LoadStatsRequest'; +import { LoadStatsResponse__Output } from './generated/envoy/service/load_stats/v3/LoadStatsResponse'; +import { Locality, Locality__Output } from './generated/envoy/config/core/v3/Locality'; +import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; import { Any__Output } from './generated/google/protobuf/Any'; import BackoffTimeout = experimental.BackoffTimeout; import ServiceConfig = experimental.ServiceConfig; @@ -55,6 +46,11 @@ import { CdsState } from './xds-stream-state/cds-state'; import { RdsState } from './xds-stream-state/rds-state'; import { LdsState } from './xds-stream-state/lds-state'; import { Watcher } from './xds-stream-state/xds-stream-state'; +import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; +import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; +import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; +import { Duration } from './generated/google/protobuf/Duration'; +import { AdsOutputType, AdsTypeUrl, CDS_TYPE_URL_V2, CDS_TYPE_URL_V3, decodeSingleResource, EDS_TYPE_URL_V2, EDS_TYPE_URL_V3, LDS_TYPE_URL_V2, LDS_TYPE_URL_V3, RDS_TYPE_URL_V2, RDS_TYPE_URL_V3 } from './resources'; const TRACER_NAME = 'xds_client'; @@ -64,21 +60,6 @@ function trace(text: string): void { 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'; - -type EdsTypeUrl = 'type.googleapis.com/envoy.api.v2.ClusterLoadAssignment'; -type CdsTypeUrl = 'type.googleapis.com/envoy.api.v2.Cluster'; -type LdsTypeUrl = 'type.googleapis.com/envoy.api.v2.Listener'; -type RdsTypeUrl = 'type.googleapis.com/envoy.api.v2.RouteConfiguration'; - -type AdsTypeUrl = EdsTypeUrl | CdsTypeUrl | RdsTypeUrl | LdsTypeUrl; - -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 > | null = null; @@ -94,11 +75,8 @@ function loadAdsProtos(): Promise< [ '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', + 'envoy/service/discovery/v3/ads.proto', + 'envoy/service/load_stats/v3/lrs.proto', ], { keepCase: true, @@ -106,7 +84,6 @@ function loadAdsProtos(): Promise< enums: String, defaults: true, oneofs: true, - json: true, includeDirs: [ // Paths are relative to src/build __dirname + '/../../deps/envoy-api/', @@ -145,6 +122,32 @@ export interface XdsClusterLocalityStats { addCallFinished(fail: boolean): void; } +interface DroppedRequests { + category: string; + dropped_count: number; +} + +interface UpstreamLocalityStats { + locality: Locality; + total_issued_requests: number; + total_successful_requests: number; + total_error_requests: number; + total_requests_in_progress: number; +} + +/** + * An interface representing the ClusterStats message type, restricted to the + * fields used in this module to ensure compatibility with both v2 and v3 APIs. + */ +interface ClusterStats { + cluster_name: string; + cluster_service_name: string; + dropped_requests: DroppedRequests[]; + total_dropped_requests: number; + upstream_locality_stats: UpstreamLocalityStats[]; + load_report_interval: Duration +} + interface ClusterLocalityStats { locality: Locality__Output; callsStarted: number; @@ -218,39 +221,32 @@ class ClusterLoadReportMap { } } +type AdsServiceKind = 'eds' | 'cds' | 'rds' | 'lds'; + interface AdsState { - [EDS_TYPE_URL]: EdsState; - [CDS_TYPE_URL]: CdsState; - [RDS_TYPE_URL]: RdsState; - [LDS_TYPE_URL]: LdsState; + eds: EdsState; + cds: CdsState; + rds: RdsState; + lds: LdsState; } -/** - * Map type URLs to their corresponding message types - */ -type OutputType = T extends EdsTypeUrl - ? ClusterLoadAssignment__Output - : T extends CdsTypeUrl - ? Cluster__Output - : T extends RdsTypeUrl - ? RouteConfiguration__Output - : Listener__Output; +enum XdsApiVersion { + V2, + V3 +} function getResponseMessages( - typeUrl: T, + targetTypeUrl: T, + allowedTypeUrls: string[], resources: Any__Output[] -): OutputType[] { - const result: OutputType[] = []; +): AdsOutputType[] { + const result: AdsOutputType[] = []; for (const resource of resources) { - if (protoLoader.isAnyExtension(resource) && resource['@type'] === typeUrl) { - result.push(resource as protoLoader.AnyExtension & OutputType); + if (allowedTypeUrls.includes(resource.type_url)) { + result.push(decodeSingleResource(targetTypeUrl, resource.value)); } else { throw new Error( - `ADS Error: Invalid resource type ${ - protoLoader.isAnyExtension(resource) - ? resource['@type'] - : resource.type_url - }, expected ${typeUrl}` + `ADS Error: Invalid resource type ${resource.type_url}, expected ${allowedTypeUrls}` ); } } @@ -258,20 +254,39 @@ function getResponseMessages( } export class XdsClient { - private adsNode: Node | null = null; - private adsClient: AggregatedDiscoveryServiceClient | null = null; - private adsCall: ClientDuplexStream< - DiscoveryRequest, + private apiVersion: XdsApiVersion = XdsApiVersion.V2; + + private adsNodeV2: NodeV2 | null = null; + private adsNodeV3: NodeV3 | null = null; + /* A client initiates connections lazily, so the client we don't use won't + * use significant extra resources. */ + private adsClientV2: AggregatedDiscoveryServiceClientV2 | null = null; + private adsClientV3: AggregatedDiscoveryServiceClientV3 | null = null; + /* TypeScript typing is structural, so we can take advantage of the fact that + * the output structures for the two call types are identical. */ + private adsCallV2: ClientDuplexStream< + DiscoveryRequestV2, + DiscoveryResponse__Output + > | null = null; + private adsCallV3: ClientDuplexStream< + DiscoveryRequestV3, DiscoveryResponse__Output > | null = null; - private lrsNode: Node | null = null; - private lrsClient: LoadReportingServiceClient | null = null; - private lrsCall: ClientDuplexStream< - LoadStatsRequest, + private lrsNodeV2: NodeV2 | null = null; + private lrsNodeV3: NodeV3 | null = null; + private lrsClientV2: LoadReportingServiceClientV2 | null = null; + private lrsClientV3: LoadReportingServiceClientV3 | null = null; + private lrsCallV2: ClientDuplexStream< + LoadStatsRequestV2, + LoadStatsResponse__Output + > | null = null; + private lrsCallV3: ClientDuplexStream< + LoadStatsRequestV3, LoadStatsResponse__Output > | null = null; private latestLrsSettings: LoadStatsResponse__Output | null = null; + private receivedLrsSettingsForCurrentStream = false; private clusterStatsMap: ClusterLoadReportMap = new ClusterLoadReportMap(); private statsTimer: NodeJS.Timer; @@ -285,22 +300,22 @@ export class XdsClient { constructor() { const edsState = new EdsState(() => { - this.updateNames(EDS_TYPE_URL); + this.updateNames('eds'); }); const cdsState = new CdsState(edsState, () => { - this.updateNames(CDS_TYPE_URL); + this.updateNames('cds'); }); const rdsState = new RdsState(() => { - this.updateNames(RDS_TYPE_URL); + this.updateNames('rds'); }); const ldsState = new LdsState(rdsState, () => { - this.updateNames(LDS_TYPE_URL); + this.updateNames('lds'); }); this.adsState = { - [EDS_TYPE_URL]: edsState, - [CDS_TYPE_URL]: cdsState, - [RDS_TYPE_URL]: rdsState, - [LDS_TYPE_URL]: ldsState, + eds: edsState, + cds: cdsState, + rds: rdsState, + lds: ldsState, }; const channelArgs = { @@ -322,17 +337,34 @@ export class XdsClient { if (this.hasShutdown) { return; } - const node: Node = { + if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('xds_v3') >= 0) { + this.apiVersion = XdsApiVersion.V3; + } else { + this.apiVersion = XdsApiVersion.V2; + } + const nodeV2: NodeV2 = { ...bootstrapInfo.node, build_version: `gRPC Node Pure JS ${clientVersion}`, user_agent_name: 'gRPC Node Pure JS', }; - this.adsNode = { - ...node, + const nodeV3: NodeV3 = { + ...bootstrapInfo.node, + user_agent_name: 'gRPC Node Pure JS', + }; + this.adsNodeV2 = { + ...nodeV2, client_features: ['envoy.lb.does_not_support_overprovisioning'], }; - this.lrsNode = { - ...node, + this.adsNodeV3 = { + ...nodeV3, + client_features: ['envoy.lb.does_not_support_overprovisioning'], + }; + this.lrsNodeV2 = { + ...nodeV2, + client_features: ['envoy.lrs.supports_send_all_clusters'], + }; + this.lrsNodeV3 = { + ...nodeV3, client_features: ['envoy.lrs.supports_send_all_clusters'], }; const credentialsConfigs = bootstrapInfo.xdsServers[0].channelCreds; @@ -356,18 +388,30 @@ export class XdsClient { }); return; } + const serverUri = bootstrapInfo.xdsServers[0].serverUri trace('Starting xDS client connected to server URI ' + bootstrapInfo.xdsServers[0].serverUri); - this.adsClient = new protoDefinitions.envoy.service.discovery.v2.AggregatedDiscoveryService( - bootstrapInfo.xdsServers[0].serverUri, + const channel = new Channel(serverUri, channelCreds, channelArgs); + this.adsClientV2 = new protoDefinitions.envoy.service.discovery.v2.AggregatedDiscoveryService( + serverUri, channelCreds, - channelArgs + {channelOverride: channel} + ); + this.adsClientV3 = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( + serverUri, + channelCreds, + {channelOverride: channel} ); this.maybeStartAdsStream(); - this.lrsClient = new protoDefinitions.envoy.service.load_stats.v2.LoadReportingService( - bootstrapInfo.xdsServers[0].serverUri, + this.lrsClientV2 = new protoDefinitions.envoy.service.load_stats.v2.LoadReportingService( + serverUri, + channelCreds, + {channelOverride: channel} + ); + this.lrsClientV3 = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( + serverUri, channelCreds, - {channelOverride: this.adsClient.getChannel()} + {channelOverride: channel} ); this.maybeStartLrsStream(); }, @@ -387,32 +431,40 @@ export class XdsClient { private handleAdsResponse(message: DiscoveryResponse__Output) { let errorString: string | null; - /* The cases in this switch statement look redundant but separating them - * out like this is necessary for the typechecker to validate the types - * as narrowly as we need it to. */ + let serviceKind: AdsServiceKind; switch (message.type_url) { - case EDS_TYPE_URL: - errorString = this.adsState[message.type_url].handleResponses( - getResponseMessages(message.type_url, message.resources) + case EDS_TYPE_URL_V2: + case EDS_TYPE_URL_V3: + errorString = this.adsState.eds.handleResponses( + getResponseMessages(EDS_TYPE_URL_V3, [EDS_TYPE_URL_V2, EDS_TYPE_URL_V3], message.resources) ); + serviceKind = 'eds'; break; - case CDS_TYPE_URL: - errorString = this.adsState[message.type_url].handleResponses( - getResponseMessages(message.type_url, message.resources) + case CDS_TYPE_URL_V2: + case CDS_TYPE_URL_V3: + errorString = this.adsState.cds.handleResponses( + getResponseMessages(CDS_TYPE_URL_V3, [CDS_TYPE_URL_V2, CDS_TYPE_URL_V3], message.resources) ); + serviceKind = 'cds'; break; - case RDS_TYPE_URL: - errorString = this.adsState[message.type_url].handleResponses( - getResponseMessages(message.type_url, message.resources) + case RDS_TYPE_URL_V2: + case RDS_TYPE_URL_V3: + errorString = this.adsState.rds.handleResponses( + getResponseMessages(RDS_TYPE_URL_V3, [RDS_TYPE_URL_V2, RDS_TYPE_URL_V3], message.resources) ); + serviceKind = 'rds'; break; - case LDS_TYPE_URL: - errorString = this.adsState[message.type_url].handleResponses( - getResponseMessages(message.type_url, message.resources) + case LDS_TYPE_URL_V2: + case LDS_TYPE_URL_V3: + errorString = this.adsState.lds.handleResponses( + getResponseMessages(LDS_TYPE_URL_V3, [LDS_TYPE_URL_V2, LDS_TYPE_URL_V3], message.resources) ); + serviceKind = 'lds'; break; default: errorString = `Unknown type_url ${message.type_url}`; + // This is not used in this branch, but setting it makes the types easier to handle + serviceKind = 'eds'; } if (errorString === null) { trace('Acking message with type URL ' + message.type_url); @@ -420,65 +472,148 @@ export class XdsClient { * implies that message.type_url is one of the 4 known type URLs, which * means that this type assertion is valid. */ const typeUrl = message.type_url as AdsTypeUrl; - this.adsState[typeUrl].nonce = message.nonce; - this.adsState[typeUrl].versionInfo = message.version_info; - this.ack(typeUrl); + this.adsState[serviceKind].nonce = message.nonce; + this.adsState[serviceKind].versionInfo = message.version_info; + this.ack(serviceKind); } else { trace('Nacking message with type URL ' + message.type_url + ': "' + errorString + '"'); this.nack(message.type_url, errorString); } } + private handleAdsCallError(error: ServiceError) { + trace( + 'ADS stream ended. code=' + error.code + ' details= ' + error.details + ); + this.adsCallV2 = null; + this.adsCallV3 = null; + this.reportStreamError(error); + /* If the backoff timer is no longer running, we do not need to wait any + * more to start the new call. */ + if (!this.adsBackoff.isRunning()) { + this.maybeStartAdsStream(); + } + } + + private maybeStartAdsStreamV2(): boolean { + if (this.apiVersion !== XdsApiVersion.V2) { + return false; + } + if (this.adsClientV2 === null) { + return false; + } + if (this.adsCallV2 !== null) { + return false; + } + this.adsCallV2 = this.adsClientV2.StreamAggregatedResources(); + this.adsCallV2.on('data', (message: DiscoveryResponse__Output) => { + this.handleAdsResponse(message); + }); + this.adsCallV2.on('error', (error: ServiceError) => { + this.handleAdsCallError(error); + }); + return true; + } + + private maybeStartAdsStreamV3(): boolean { + if (this.apiVersion !== XdsApiVersion.V3) { + return false; + } + if (this.adsClientV3 === null) { + return false; + } + if (this.adsCallV3 !== null) { + return false; + } + this.adsCallV3 = this.adsClientV3.StreamAggregatedResources(); + this.adsCallV3.on('data', (message: DiscoveryResponse__Output) => { + this.handleAdsResponse(message); + }); + this.adsCallV3.on('error', (error: ServiceError) => { + this.handleAdsCallError(error); + }); + return true; + } + /** * Start the ADS stream if the client exists and there is not already an - * existing stream, and there + * existing stream, and there are resources to request. */ private maybeStartAdsStream() { - if (this.adsClient === null) { - return; - } - if (this.adsCall !== null) { - return; - } if (this.hasShutdown) { return; } - if (this.adsState[EDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[CDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[RDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[LDS_TYPE_URL].getResourceNames().length === 0) { + if (this.adsState.eds.getResourceNames().length === 0 && + this.adsState.cds.getResourceNames().length === 0 && + this.adsState.rds.getResourceNames().length === 0 && + this.adsState.lds.getResourceNames().length === 0) { return; } - trace('Starting ADS stream'); - // Backoff relative to when we start the request - this.adsBackoff.runOnce(); - this.adsCall = this.adsClient.StreamAggregatedResources(); - this.adsCall.on('data', (message: DiscoveryResponse__Output) => { - this.handleAdsResponse(message); - }); - this.adsCall.on('error', (error: ServiceError) => { - trace( - 'ADS stream ended. code=' + error.code + ' details= ' + error.details - ); - this.adsCall = null; - this.reportStreamError(error); - /* If the backoff timer is no longer running, we do not need to wait any - * more to start the new call. */ - if (!this.adsBackoff.isRunning()) { - this.maybeStartAdsStream(); + let streamStarted: boolean; + if (this.apiVersion === XdsApiVersion.V2) { + streamStarted = this.maybeStartAdsStreamV2(); + } else { + streamStarted = this.maybeStartAdsStreamV3(); + } + if (streamStarted) { + trace('Started ADS stream'); + // Backoff relative to when we start the request + this.adsBackoff.runOnce(); + + const allServiceKinds: AdsServiceKind[] = ['eds', 'cds', 'rds', 'lds']; + for (const service of allServiceKinds) { + const state = this.adsState[service]; + if (state.getResourceNames().length > 0) { + this.updateNames(service); + } } - }); + } + } + + private maybeSendAdsMessage(typeUrl: string, resourceNames: string[], responseNonce: string, versionInfo: string, errorMessage?: string) { + if (this.apiVersion === XdsApiVersion.V2) { + this.adsCallV2?.write({ + node: this.adsNodeV2!, + type_url: typeUrl, + resource_names: resourceNames, + response_nonce: responseNonce, + version_info: versionInfo, + error_detail: errorMessage ? { message: errorMessage } : undefined + }); + } else { + this.adsCallV3?.write({ + node: this.adsNodeV3!, + type_url: typeUrl, + resource_names: resourceNames, + response_nonce: responseNonce, + version_info: versionInfo, + error_detail: errorMessage ? { message: errorMessage } : undefined + }); + } + } - const allTypeUrls: AdsTypeUrl[] = [ - EDS_TYPE_URL, - CDS_TYPE_URL, - RDS_TYPE_URL, - LDS_TYPE_URL, - ]; - for (const typeUrl of allTypeUrls) { - const state = this.adsState[typeUrl]; - if (state.getResourceNames().length > 0) { - this.updateNames(typeUrl); + private getTypeUrl(serviceKind: AdsServiceKind): AdsTypeUrl { + if (this.apiVersion === XdsApiVersion.V2) { + switch (serviceKind) { + case 'eds': + return EDS_TYPE_URL_V2; + case 'cds': + return CDS_TYPE_URL_V2; + case 'rds': + return RDS_TYPE_URL_V2; + case 'lds': + return LDS_TYPE_URL_V2; + } + } else { + switch (serviceKind) { + case 'eds': + return EDS_TYPE_URL_V3; + case 'cds': + return CDS_TYPE_URL_V3; + case 'rds': + return RDS_TYPE_URL_V3; + case 'lds': + return LDS_TYPE_URL_V3; } } } @@ -487,13 +622,13 @@ export class XdsClient { * Acknowledge an update. This should be called after the local nonce and * version info are updated so that it sends the post-update values. */ - ack(typeUrl: AdsTypeUrl) { + ack(serviceKind: AdsServiceKind) { /* An ack is the best indication of a successful interaction between the * client and the server, so we can reset the backoff timer here. */ this.adsBackoff.stop(); this.adsBackoff.reset(); - this.updateNames(typeUrl); + this.updateNames(serviceKind); } /** @@ -504,135 +639,196 @@ export class XdsClient { let resourceNames: string[]; let nonce: string; let versionInfo: string; + let serviceKind: AdsServiceKind | null; switch (typeUrl) { - case EDS_TYPE_URL: - case CDS_TYPE_URL: - case RDS_TYPE_URL: - case LDS_TYPE_URL: - resourceNames = this.adsState[typeUrl].getResourceNames(); - nonce = this.adsState[typeUrl].nonce; - versionInfo = this.adsState[typeUrl].versionInfo; + case EDS_TYPE_URL_V2: + case EDS_TYPE_URL_V3: + serviceKind = 'eds'; + break; + case CDS_TYPE_URL_V2: + case CDS_TYPE_URL_V3: + serviceKind = 'cds'; + break; + case RDS_TYPE_URL_V2: + case RDS_TYPE_URL_V3: + serviceKind = 'rds'; + break; + case LDS_TYPE_URL_V2: + case LDS_TYPE_URL_V3: + serviceKind = 'lds'; break; default: - resourceNames = []; - nonce = ''; - versionInfo = ''; - } - this.adsCall?.write({ - node: this.adsNode!, - type_url: typeUrl, - resource_names: resourceNames, - response_nonce: nonce, - version_info: versionInfo, - error_detail: { - message: message, - }, - }); + serviceKind = null; + break; + } + if (serviceKind) { + resourceNames = this.adsState[serviceKind].getResourceNames(); + nonce = this.adsState[serviceKind].nonce; + versionInfo = this.adsState[serviceKind].versionInfo; + } else { + resourceNames = []; + nonce = ''; + versionInfo = ''; + } + this.maybeSendAdsMessage(typeUrl, resourceNames, nonce, versionInfo, message); } - private updateNames(typeUrl: AdsTypeUrl) { - if (this.adsState[EDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[CDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[RDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[LDS_TYPE_URL].getResourceNames().length === 0) { - this.adsCall?.end(); - this.lrsCall?.end(); + private updateNames(serviceKind: AdsServiceKind) { + if (this.adsState.eds.getResourceNames().length === 0 && + this.adsState.cds.getResourceNames().length === 0 && + this.adsState.rds.getResourceNames().length === 0 && + this.adsState.lds.getResourceNames().length === 0) { + this.adsCallV2?.end(); + this.adsCallV2 = null; + this.adsCallV3?.end(); + this.adsCallV3 = null; + this.lrsCallV2?.end(); + this.lrsCallV2 = null; + this.lrsCallV3?.end(); + this.lrsCallV3 = null; return; } this.maybeStartAdsStream(); this.maybeStartLrsStream(); - trace('Sending update for type URL ' + typeUrl + ' with names ' + this.adsState[typeUrl].getResourceNames()); - this.adsCall?.write({ - node: this.adsNode!, - type_url: typeUrl, - resource_names: this.adsState[typeUrl].getResourceNames(), - response_nonce: this.adsState[typeUrl].nonce, - version_info: this.adsState[typeUrl].versionInfo, - }); + trace('Sending update for ' + serviceKind + ' with names ' + this.adsState[serviceKind].getResourceNames()); + const typeUrl = this.getTypeUrl(serviceKind); + this.maybeSendAdsMessage(typeUrl, this.adsState[serviceKind].getResourceNames(), this.adsState[serviceKind].nonce, this.adsState[serviceKind].versionInfo); } private reportStreamError(status: StatusObject) { - this.adsState[EDS_TYPE_URL].reportStreamError(status); - this.adsState[CDS_TYPE_URL].reportStreamError(status); - this.adsState[RDS_TYPE_URL].reportStreamError(status); - this.adsState[LDS_TYPE_URL].reportStreamError(status); + this.adsState.eds.reportStreamError(status); + this.adsState.cds.reportStreamError(status); + this.adsState.rds.reportStreamError(status); + this.adsState.lds.reportStreamError(status); } - private maybeStartLrsStream() { - if (!this.lrsClient) { - return; + private handleLrsResponse(message: LoadStatsResponse__Output) { + trace('Received LRS response'); + /* Once we get any response from the server, we assume that the stream is + * in a good state, so we can reset the backoff timer. */ + this.lrsBackoff.stop(); + this.lrsBackoff.reset(); + if ( + !this.receivedLrsSettingsForCurrentStream || + message.load_reporting_interval?.seconds !== + this.latestLrsSettings?.load_reporting_interval?.seconds || + message.load_reporting_interval?.nanos !== + this.latestLrsSettings?.load_reporting_interval?.nanos + ) { + /* Only reset the timer if the interval has changed or was not set + * before. */ + clearInterval(this.statsTimer); + /* Convert a google.protobuf.Duration to a number of milliseconds for + * use with setInterval. */ + const loadReportingIntervalMs = + Number.parseInt(message.load_reporting_interval!.seconds) * 1000 + + message.load_reporting_interval!.nanos / 1_000_000; + trace('Received LRS response with load reporting interval ' + loadReportingIntervalMs + ' ms'); + this.statsTimer = setInterval(() => { + this.sendStats(); + }, loadReportingIntervalMs); } - if (this.lrsCall) { - return; + this.latestLrsSettings = message; + this.receivedLrsSettingsForCurrentStream = true; + } + + private handleLrsCallError(error: ServiceError) { + trace( + 'LRS stream ended. code=' + error.code + ' details= ' + error.details + ); + this.lrsCallV2 = null; + this.lrsCallV3 = null; + clearInterval(this.statsTimer); + /* If the backoff timer is no longer running, we do not need to wait any + * more to start the new call. */ + if (!this.lrsBackoff.isRunning()) { + this.maybeStartLrsStream(); + } + } + + private maybeStartLrsStreamV2(): boolean { + if (!this.lrsClientV2) { + return false; + } + if (this.lrsCallV2) { + return false; } + this.lrsCallV2 = this.lrsClientV2.streamLoadStats(); + this.receivedLrsSettingsForCurrentStream = false; + this.lrsCallV2.on('data', (message: LoadStatsResponse__Output) => { + this.handleLrsResponse(message); + }); + this.lrsCallV2.on('error', (error: ServiceError) => { + this.handleLrsCallError(error); + }); + return true; + } + + private maybeStartLrsStreamV3(): boolean { + if (!this.lrsClientV3) { + return false; + } + if (this.lrsCallV3) { + return false; + } + this.lrsCallV3 = this.lrsClientV3.streamLoadStats(); + this.receivedLrsSettingsForCurrentStream = false; + this.lrsCallV3.on('data', (message: LoadStatsResponse__Output) => { + this.handleLrsResponse(message); + }); + this.lrsCallV3.on('error', (error: ServiceError) => { + this.handleLrsCallError(error); + }); + return true; + } + + private maybeStartLrsStream() { if (this.hasShutdown) { return; } - if (this.adsState[EDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[CDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[RDS_TYPE_URL].getResourceNames().length === 0 && - this.adsState[LDS_TYPE_URL].getResourceNames().length === 0) { + if (this.adsState.eds.getResourceNames().length === 0 && + this.adsState.cds.getResourceNames().length === 0 && + this.adsState.rds.getResourceNames().length === 0 && + this.adsState.lds.getResourceNames().length === 0) { return; } - - trace('Starting LRS stream'); - - this.lrsBackoff.runOnce(); - this.lrsCall = this.lrsClient.streamLoadStats(); - let receivedSettingsForThisStream = false; - this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { - /* Once we get any response from the server, we assume that the stream is - * in a good state, so we can reset the backoff timer. */ - this.lrsBackoff.stop(); - this.lrsBackoff.reset(); - if ( - !receivedSettingsForThisStream || - message.load_reporting_interval?.seconds !== - this.latestLrsSettings?.load_reporting_interval?.seconds || - message.load_reporting_interval?.nanos !== - this.latestLrsSettings?.load_reporting_interval?.nanos - ) { - /* Only reset the timer if the interval has changed or was not set - * before. */ - clearInterval(this.statsTimer); - /* Convert a google.protobuf.Duration to a number of milliseconds for - * use with setInterval. */ - const loadReportingIntervalMs = - Number.parseInt(message.load_reporting_interval!.seconds) * 1000 + - message.load_reporting_interval!.nanos / 1_000_000; - trace('Received LRS request with load reporting interval ' + loadReportingIntervalMs + ' ms'); - this.statsTimer = setInterval(() => { - this.sendStats(); - }, loadReportingIntervalMs); - } - this.latestLrsSettings = message; - receivedSettingsForThisStream = true; - }); - this.lrsCall.on('error', (error: ServiceError) => { - trace( - 'LRS stream ended. code=' + error.code + ' details= ' + error.details - ); - this.lrsCall = null; - clearInterval(this.statsTimer); - /* If the backoff timer is no longer running, we do not need to wait any - * more to start the new call. */ - if (!this.lrsBackoff.isRunning()) { - this.maybeStartLrsStream(); - } - }); - /* Send buffered stats information when starting LRS stream. If there is no - * buffered stats information, it will still send the node field. */ - this.sendStats(); + + let streamStarted: boolean; + if (this.apiVersion === XdsApiVersion.V2) { + streamStarted = this.maybeStartLrsStreamV2(); + } else { + streamStarted = this.maybeStartLrsStreamV3(); + } + + if (streamStarted) { + trace('Starting LRS stream'); + this.lrsBackoff.runOnce(); + /* Send buffered stats information when starting LRS stream. If there is no + * buffered stats information, it will still send the node field. */ + this.sendStats(); + } + } + + private maybeSendLrsMessage(clusterStats: ClusterStats[]) { + if (this.apiVersion === XdsApiVersion.V2) { + this.lrsCallV2?.write({ + node: this.lrsNodeV2!, + cluster_stats: clusterStats + }); + } else { + this.lrsCallV3?.write({ + node: this.lrsNodeV3!, + cluster_stats: clusterStats + }); + } } private sendStats() { - if (!this.lrsCall) { + if (this.lrsCallV2 === null && this.lrsCallV3 === null) { return; } if (!this.latestLrsSettings) { - this.lrsCall.write({ - node: this.lrsNode!, - }); + this.maybeSendLrsMessage([]); return; } const clusterStats: ClusterStats[] = []; @@ -664,7 +860,7 @@ export class XdsClient { localityStats.callsFailed = 0; } } - const droppedRequests: _envoy_api_v2_endpoint_ClusterStats_DroppedRequests[] = []; + const droppedRequests: DroppedRequests[] = []; let totalDroppedRequests = 0; for (const [category, count] of stats.callsDropped.entries()) { if (count > 0) { @@ -696,10 +892,7 @@ export class XdsClient { } } trace('Sending LRS stats ' + JSON.stringify(clusterStats, undefined, 2)); - this.lrsCall.write({ - node: this.lrsNode!, - cluster_stats: clusterStats, - }); + this.maybeSendLrsMessage(clusterStats); } addEndpointWatcher( @@ -707,7 +900,7 @@ export class XdsClient { watcher: Watcher ) { trace('Watcher added for endpoint ' + edsServiceName); - this.adsState[EDS_TYPE_URL].addWatcher(edsServiceName, watcher); + this.adsState.eds.addWatcher(edsServiceName, watcher); } removeEndpointWatcher( @@ -715,37 +908,37 @@ export class XdsClient { watcher: Watcher ) { trace('Watcher removed for endpoint ' + edsServiceName); - this.adsState[EDS_TYPE_URL].removeWatcher(edsServiceName, watcher); + this.adsState.eds.removeWatcher(edsServiceName, watcher); } addClusterWatcher(clusterName: string, watcher: Watcher) { trace('Watcher added for cluster ' + clusterName); - this.adsState[CDS_TYPE_URL].addWatcher(clusterName, watcher); + this.adsState.cds.addWatcher(clusterName, watcher); } removeClusterWatcher(clusterName: string, watcher: Watcher) { trace('Watcher removed for cluster ' + clusterName); - this.adsState[CDS_TYPE_URL].removeWatcher(clusterName, watcher); + this.adsState.cds.removeWatcher(clusterName, watcher); } addRouteWatcher(routeConfigName: string, watcher: Watcher) { trace('Watcher added for route ' + routeConfigName); - this.adsState[RDS_TYPE_URL].addWatcher(routeConfigName, watcher); + this.adsState.rds.addWatcher(routeConfigName, watcher); } removeRouteWatcher(routeConfigName: string, watcher: Watcher) { trace('Watcher removed for route ' + routeConfigName); - this.adsState[RDS_TYPE_URL].removeWatcher(routeConfigName, watcher); + this.adsState.rds.removeWatcher(routeConfigName, watcher); } addListenerWatcher(targetName: string, watcher: Watcher) { trace('Watcher added for listener ' + targetName); - this.adsState[LDS_TYPE_URL].addWatcher(targetName, watcher); + this.adsState.lds.addWatcher(targetName, watcher); } removeListenerWatcher(targetName: string, watcher: Watcher) { trace('Watcher removed for listener ' + targetName); - this.adsState[LDS_TYPE_URL].removeWatcher(targetName, watcher); + this.adsState.lds.removeWatcher(targetName, watcher); } /** @@ -833,10 +1026,14 @@ export class XdsClient { } private shutdown(): void { - this.adsCall?.cancel(); - this.adsClient?.close(); - this.lrsCall?.cancel(); - this.lrsClient?.close(); + this.adsCallV2?.cancel(); + this.adsCallV3?.cancel(); + this.adsClientV2?.close(); + this.adsClientV3?.close(); + this.lrsCallV2?.cancel(); + this.lrsCallV3?.cancel(); + this.lrsClientV2?.close(); + this.lrsClientV3?.close(); this.hasShutdown = true; } } diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 343089958..a6e89805d 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -16,7 +16,7 @@ */ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; -import { Cluster__Output } from "../generated/envoy/api/v2/Cluster"; +import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; import { EdsState } from "./eds-state"; import { Watcher, XdsStreamState } from "./xds-stream-state"; diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index c9beef292..a0fb5f4dc 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -17,7 +17,7 @@ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { isIPv4, isIPv6 } from "net"; -import { ClusterLoadAssignment__Output } from "../generated/envoy/api/v2/ClusterLoadAssignment"; +import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; import { Watcher, XdsStreamState } from "./xds-stream-state"; const TRACER_NAME = 'xds_client'; diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 554712727..eb76f7994 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -17,10 +17,11 @@ import * as protoLoader from '@grpc/proto-loader'; import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; -import { Listener__Output } from "../generated/envoy/api/v2/Listener"; +import { Listener__Output } from '../generated/envoy/config/listener/v3/Listener'; import { RdsState } from "./rds-state"; import { Watcher, XdsStreamState } from "./xds-stream-state"; -import { HttpConnectionManager__Output } from '../generated/envoy/config/filter/network/http_connection_manager/v2/HttpConnectionManager'; +import { HttpConnectionManager__Output } from '../generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; +import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V2, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from '../resources'; const TRACER_NAME = 'xds_client'; @@ -28,9 +29,6 @@ function trace(text: string): void { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); } -const HTTP_CONNECTION_MANGER_TYPE_URL = - 'type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager'; - export class LdsState implements XdsStreamState { versionInfo = ''; nonce = ''; @@ -95,16 +93,13 @@ export class LdsState implements XdsStreamState { if ( !( message.api_listener?.api_listener && - protoLoader.isAnyExtension(message.api_listener.api_listener) && - message.api_listener?.api_listener['@type'] === - HTTP_CONNECTION_MANGER_TYPE_URL + (message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL_V2 || + message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL_V3) ) ) { return false; } - const httpConnectionManager = message.api_listener - ?.api_listener as protoLoader.AnyExtension & - HttpConnectionManager__Output; + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener.value); switch (httpConnectionManager.route_specifier) { case 'rds': return !!httpConnectionManager.rds?.config_source?.ads; diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 8f795e0f5..1e50f22e7 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -16,7 +16,7 @@ */ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; -import { RouteConfiguration__Output } from "../generated/envoy/api/v2/RouteConfiguration"; +import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; import { CdsLoadBalancingConfig } from "../load-balancer-cds"; import { Watcher, XdsStreamState } from "./xds-stream-state"; import ServiceConfig = experimental.ServiceConfig; diff --git a/test/kokoro/linux.cfg b/test/kokoro/linux.cfg index f40e6db43..63f88d399 100644 --- a/test/kokoro/linux.cfg +++ b/test/kokoro/linux.cfg @@ -11,7 +11,6 @@ # 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. - # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. @@ -21,4 +20,4 @@ action { define_artifacts { regex: "github/grpc-node/reports/**/sponge_log.xml" } -} +} \ No newline at end of file diff --git a/test/kokoro/xds-v3-interop.cfg b/test/kokoro/xds-v3-interop.cfg new file mode 100644 index 000000000..d7ffa8fab --- /dev/null +++ b/test/kokoro/xds-v3-interop.cfg @@ -0,0 +1,24 @@ +# Copyright 2021 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/xds-v3.sh" +timeout_mins: 120 +action { + define_artifacts { + regex: "github/grpc/reports/**" + } +} From 4c767ca94624f938aa86f8c53994fa88c9ad61c3 Mon Sep 17 00:00:00 2001 From: "@EduardoLaranjo" Date: Tue, 4 May 2021 19:27:24 +0100 Subject: [PATCH 003/694] Fix auto-generated service definition relate to issue #1766 --- packages/proto-loader/bin/proto-loader-gen-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 04717f3fb..82a13a123 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -542,7 +542,7 @@ function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: } function generateServiceDefinitionInterface(formatter: TextFormatter, serviceType: Protobuf.Service) { - formatter.writeLine(`export interface ${serviceType.name}Definition {`); + formatter.writeLine(`export interface ${serviceType.name}Definition extends grpc.ServiceDefinition {`); formatter.indent(); for (const methodName of Object.keys(serviceType.methods).sort()) { const method = serviceType.methods[methodName]; From a5fb029e7054a5be4b8791144139fb0b8631abbf Mon Sep 17 00:00:00 2001 From: Eduardo Laranjo Date: Wed, 5 May 2021 13:41:45 +0100 Subject: [PATCH 004/694] Add new generated golden files --- .../golden-generated/google/longrunning/Operations.ts | 2 +- .../golden-generated/google/showcase/v1beta1/Echo.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts index 8e5684ada..7644f974d 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts @@ -232,7 +232,7 @@ export interface OperationsHandlers extends grpc.UntypedServiceImplementation { } -export interface OperationsDefinition { +export interface OperationsDefinition extends grpc.ServiceDefinition { CancelOperation: MethodDefinition<_google_longrunning_CancelOperationRequest, _google_protobuf_Empty, _google_longrunning_CancelOperationRequest__Output, _google_protobuf_Empty__Output> DeleteOperation: MethodDefinition<_google_longrunning_DeleteOperationRequest, _google_protobuf_Empty, _google_longrunning_DeleteOperationRequest__Output, _google_protobuf_Empty__Output> GetOperation: MethodDefinition<_google_longrunning_GetOperationRequest, _google_longrunning_Operation, _google_longrunning_GetOperationRequest__Output, _google_longrunning_Operation__Output> diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts index acb911270..8e75c6c1a 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts @@ -191,7 +191,7 @@ export interface EchoHandlers extends grpc.UntypedServiceImplementation { } -export interface EchoDefinition { +export interface EchoDefinition extends grpc.ServiceDefinition { Block: MethodDefinition<_google_showcase_v1beta1_BlockRequest, _google_showcase_v1beta1_BlockResponse, _google_showcase_v1beta1_BlockRequest__Output, _google_showcase_v1beta1_BlockResponse__Output> Chat: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> Collect: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> From 7c2acccff51a2b6cf6cd8911b3902112a449a030 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 6 May 2021 14:28:15 -0700 Subject: [PATCH 005/694] proto-loader: Bump to 0.6.2 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index ee49abc40..0d9ff4437 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.1", + "version": "0.6.2", "author": "Google Inc.", "contributors": [ { From 03a72a1d2e8f1b9bb70e9a40e5c72d4b232b61bf Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Fri, 7 May 2021 09:36:10 +0200 Subject: [PATCH 006/694] add emulated aarch64 linux tests --- run-tests.sh | 3 ++- test/aarch64/prepare_qemu.sh | 26 ++++++++++++++++++++++ test/kokoro/linux_aarch64.cfg | 24 ++++++++++++++++++++ test/kokoro_linux_aarch64.sh | 42 +++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100755 test/aarch64/prepare_qemu.sh create mode 100644 test/kokoro/linux_aarch64.cfg create mode 100755 test/kokoro_linux_aarch64.sh diff --git a/run-tests.sh b/run-tests.sh index c4bffacda..2808e2af6 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -46,6 +46,7 @@ export JOBS=8 export JUNIT_REPORT_STACK=1 OS=$(uname) +ARCH=$(uname -m) # TODO(mlumish): Add electron tests @@ -87,7 +88,7 @@ if [ "$FAILED" = "true" ] then exit 1 else - if [ "$OS" = "Linux" ] + if [ "$OS" = "Linux" ] && [ "$ARCH" != "aarch64"] then # If we can't download the token file, just skip reporting coverage gsutil cp gs://grpc-testing-secrets/coveralls_credentials/grpc-node.rc /tmp || exit 0 diff --git a/test/aarch64/prepare_qemu.sh b/test/aarch64/prepare_qemu.sh new file mode 100755 index 000000000..f61320222 --- /dev/null +++ b/test/aarch64/prepare_qemu.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Setup and configure qemu userspace emulator on kokoro worker so that we can seamlessly emulate processes running +# inside docker containers. + +set -ex + +# show pre-existing qemu registration +cat /proc/sys/fs/binfmt_misc/qemu-aarch64 + +# Kokoro ubuntu1604 workers have already qemu-user and qemu-user-static packages installed, but it's and old version that: +# * prints warning about some syscalls (e.g "qemu: Unsupported syscall: 278") +# * doesn't register with binfmt_misc with the persistent ("F") flag we need (see below) +# +# To overcome the above limitations, we use the https://github.com/multiarch/qemu-user-static +# docker image to provide a new enough version of qemu-user-static and register it with +# the desired binfmt_misc flags. The most important flag we need is "F" (set by "--persistent yes"), +# which allows the qemu-aarch64-static binary to be loaded eagerly at the time of registration with binfmt_misc. +# That way, we can emulate aarch64 binaries running inside docker containers transparently, without needing the emulator +# binary to be accessible from the docker image we're emulating. +# Note that on newer distributions (such as glinux), simply "apt install qemu-user-static" is sufficient +# to install qemu-user-static with the right flags. +docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset --credential yes --persistent yes + +# Print current qemu reqistration to make sure everything is setup correctly. +cat /proc/sys/fs/binfmt_misc/qemu-aarch64 diff --git a/test/kokoro/linux_aarch64.cfg b/test/kokoro/linux_aarch64.cfg new file mode 100644 index 000000000..638748ab8 --- /dev/null +++ b/test/kokoro/linux_aarch64.cfg @@ -0,0 +1,24 @@ +# Copyright 2017 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/test/kokoro_linux_aarch64.sh" +timeout_mins: 60 +action { + define_artifacts { + regex: "github/grpc-node/reports/**/sponge_log.xml" + } +} diff --git a/test/kokoro_linux_aarch64.sh b/test/kokoro_linux_aarch64.sh new file mode 100755 index 000000000..8a4752c41 --- /dev/null +++ b/test/kokoro_linux_aarch64.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Copyright 2021 The 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. + +set -ex +cd $(dirname $0)/.. + +test/aarch64/prepare_qemu.sh + +# better update submodules here. We could update the submodule when running +# under an emulator as well, but it comes with performance penalty. +git submodule update --init --recursive + +if [[ -t 0 ]]; then + DOCKER_TTY_ARGS="-it" +else + # The input device on kokoro is not a TTY, so -it does not work. + DOCKER_TTY_ARGS= +fi + +# the test command to run under an emulated aarch64 docker container. +# we only run tests for a single version of node, since tests under an emulator are significantly slower. +TEST_NODE_COMMAND="node_versions='8' ./run-tests.sh" + +# use an actual aarch64 docker image (with a real aarch64 node) to run build & test grpc-js under an emulator +# * mount the protobuf root as /work to be able to access the crosscompiled files +# * to avoid running the process inside docker as root (which can pollute the workspace with files owned by root), we force +# running under current user's UID and GID. To be able to do that, we need to provide a home directory for the user +# otherwise the UID would be homeless under the docker container (which can lead to various issues). For simplicity, +# we just run map the user's home to a throwaway temporary directory. +docker run $DOCKER_TTY_ARGS --rm --user "$(id -u):$(id -g)" -e "HOME=/home/fake-user" -v "$(mktemp -d):/home/fake-user" -v "$(pwd)":/work -w /work arm64v8/node:8-buster bash -c "$TEST_NODE_COMMAND" From 2b8322ef90dd3e51159c7e1ededc0be19cb9d25d Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 10 May 2021 11:54:10 +0200 Subject: [PATCH 007/694] update aarch64 tests to test with node12 --- test/kokoro_linux_aarch64.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/kokoro_linux_aarch64.sh b/test/kokoro_linux_aarch64.sh index 8a4752c41..9dd6ad86a 100755 --- a/test/kokoro_linux_aarch64.sh +++ b/test/kokoro_linux_aarch64.sh @@ -31,7 +31,7 @@ fi # the test command to run under an emulated aarch64 docker container. # we only run tests for a single version of node, since tests under an emulator are significantly slower. -TEST_NODE_COMMAND="node_versions='8' ./run-tests.sh" +TEST_NODE_COMMAND="node_versions='12' ./run-tests.sh" # use an actual aarch64 docker image (with a real aarch64 node) to run build & test grpc-js under an emulator # * mount the protobuf root as /work to be able to access the crosscompiled files @@ -39,4 +39,4 @@ TEST_NODE_COMMAND="node_versions='8' ./run-tests.sh" # running under current user's UID and GID. To be able to do that, we need to provide a home directory for the user # otherwise the UID would be homeless under the docker container (which can lead to various issues). For simplicity, # we just run map the user's home to a throwaway temporary directory. -docker run $DOCKER_TTY_ARGS --rm --user "$(id -u):$(id -g)" -e "HOME=/home/fake-user" -v "$(mktemp -d):/home/fake-user" -v "$(pwd)":/work -w /work arm64v8/node:8-buster bash -c "$TEST_NODE_COMMAND" +docker run $DOCKER_TTY_ARGS --rm --user "$(id -u):$(id -g)" -e "HOME=/home/fake-user" -v "$(mktemp -d):/home/fake-user" -v "$(pwd)":/work -w /work arm64v8/node:12-buster bash -c "$TEST_NODE_COMMAND" From 7de0d08e299a339a8b156ed56f508e76de35077a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 May 2021 14:30:25 -0700 Subject: [PATCH 008/694] grpc-js: Apply timeouts from service configs --- packages/grpc-js/src/call-stream.ts | 30 +++++++++++++++++++------ packages/grpc-js/src/channel.ts | 8 +++++++ packages/grpc-js/src/deadline-filter.ts | 24 ++++++++++++++++---- packages/grpc-js/src/filter-stack.ts | 6 +++++ packages/grpc-js/src/filter.ts | 5 +++++ packages/grpc-js/src/service-config.ts | 27 +++++++++++++++++----- 6 files changed, 84 insertions(+), 16 deletions(-) diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 8fcc7065e..3a81cbe94 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -70,6 +70,17 @@ function getSystemErrorName(errno: number): string { export type Deadline = Date | number; +function getMinDeadline(deadlineList: Deadline[]): Deadline { + let minValue: number = Infinity; + for (const deadline of deadlineList) { + const deadlineMsecs = deadline instanceof Date ? deadline.getTime() : deadline; + if (deadlineMsecs < minValue) { + minValue = deadlineMsecs; + } + } + return minValue; +} + export interface CallStreamOptions { deadline: Deadline; flags: number; @@ -235,6 +246,8 @@ export class Http2CallStream implements Call { private internalError: SystemError | null = null; + private configDeadline: Deadline = Infinity; + constructor( private readonly methodName: string, private readonly channel: ChannelImplementation, @@ -675,15 +688,14 @@ export class Http2CallStream implements Call { } getDeadline(): Deadline { + const deadlineList = [this.options.deadline]; if (this.options.parentCall && this.options.flags & Propagate.DEADLINE) { - const parentDeadline = this.options.parentCall.getDeadline(); - const selfDeadline = this.options.deadline; - const parentDeadlineMsecs = parentDeadline instanceof Date ? parentDeadline.getTime() : parentDeadline; - const selfDeadlineMsecs = selfDeadline instanceof Date ? selfDeadline.getTime() : selfDeadline; - return Math.min(parentDeadlineMsecs, selfDeadlineMsecs); - } else { - return this.options.deadline; + deadlineList.push(this.options.parentCall.getDeadline()); + } + if (this.configDeadline) { + deadlineList.push(this.configDeadline); } + return getMinDeadline(deadlineList); } getCredentials(): CallCredentials { @@ -710,6 +722,10 @@ export class Http2CallStream implements Call { return this.options.host; } + setConfigDeadline(configDeadline: Deadline) { + this.configDeadline = configDeadline; + } + startRead() { /* If the stream has ended with an error, we should not emit any more * messages and we should communicate that the stream has ended */ diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 41715c41e..88b1d4998 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -523,6 +523,14 @@ export class ChannelImplementation implements Channel { } else { const callConfig = this.configSelector(stream.getMethod(), metadata); if (callConfig.status === Status.OK) { + if (callConfig.methodConfig.timeout) { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + callConfig.methodConfig.timeout.seconds); + deadline.setMilliseconds(deadline.getMilliseconds() + callConfig.methodConfig.timeout.nanos / 1_000_000); + stream.setConfigDeadline(deadline); + // Refreshing the filters makes the deadline filter pick up the new deadline + stream.filterStack.refresh(); + } this.tryPick(stream, metadata, callConfig); } else { stream.cancelWithStatus(callConfig.status, "Failed to route call to method " + stream.getMethod()); diff --git a/packages/grpc-js/src/deadline-filter.ts b/packages/grpc-js/src/deadline-filter.ts index 99bfa2bec..afc015546 100644 --- a/packages/grpc-js/src/deadline-filter.ts +++ b/packages/grpc-js/src/deadline-filter.ts @@ -42,30 +42,41 @@ function getDeadline(deadline: number) { export class DeadlineFilter extends BaseFilter implements Filter { private timer: NodeJS.Timer | null = null; - private deadline: number; + private deadline: number = Infinity; constructor( private readonly channel: Channel, private readonly callStream: Call ) { super(); - const callDeadline = callStream.getDeadline(); + this.retreiveDeadline(); + this.runTimer(); + } + + private retreiveDeadline() { + const callDeadline = this.callStream.getDeadline(); if (callDeadline instanceof Date) { this.deadline = callDeadline.getTime(); } else { this.deadline = callDeadline; } + } + + private runTimer() { + if (this.timer) { + clearTimeout(this.timer); + } const now: number = new Date().getTime(); let timeout = this.deadline - now; if (timeout <= 0) { process.nextTick(() => { - callStream.cancelWithStatus( + this.callStream.cancelWithStatus( Status.DEADLINE_EXCEEDED, 'Deadline exceeded' ); }); } else if (this.deadline !== Infinity) { this.timer = setTimeout(() => { - callStream.cancelWithStatus( + this.callStream.cancelWithStatus( Status.DEADLINE_EXCEEDED, 'Deadline exceeded' ); @@ -74,6 +85,11 @@ export class DeadlineFilter extends BaseFilter implements Filter { } } + refresh() { + this.retreiveDeadline(); + this.runTimer(); + } + async sendMetadata(metadata: Promise) { if (this.deadline === Infinity) { return metadata; diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts index a656a4099..4fd888549 100644 --- a/packages/grpc-js/src/filter-stack.ts +++ b/packages/grpc-js/src/filter-stack.ts @@ -71,6 +71,12 @@ export class FilterStack implements Filter { return result; } + + refresh(): void { + for (const filter of this.filters) { + filter.refresh(); + } + } } export class FilterStackFactory implements FilterFactory { diff --git a/packages/grpc-js/src/filter.ts b/packages/grpc-js/src/filter.ts index c1e412ae5..eb67bd32e 100644 --- a/packages/grpc-js/src/filter.ts +++ b/packages/grpc-js/src/filter.ts @@ -32,6 +32,8 @@ export interface Filter { receiveMessage(message: Promise): Promise; receiveTrailers(status: StatusObject): StatusObject; + + refresh(): void; } export abstract class BaseFilter implements Filter { @@ -54,6 +56,9 @@ export abstract class BaseFilter implements Filter { receiveTrailers(status: StatusObject): StatusObject { return status; } + + refresh(): void { + } } export interface FilterFactory { diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index ed225e087..efc09f9c4 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -34,10 +34,15 @@ export interface MethodConfigName { method?: string; } +export interface Duration { + seconds: number; + nanos: number; +} + export interface MethodConfig { name: MethodConfigName[]; waitForReady?: boolean; - timeout?: string; + timeout?: Duration; maxRequestBytes?: number; maxResponseBytes?: number; } @@ -101,13 +106,25 @@ function validateMethodConfig(obj: any): MethodConfig { result.waitForReady = obj.waitForReady; } if ('timeout' in obj) { - if ( - !(typeof obj.timeout === 'string') || - !TIMEOUT_REGEX.test(obj.timeout) + if (typeof obj.timeout === 'object') { + if (!('seconds' in obj.timeout) || !(typeof obj.timeout.seconds === 'number')) { + throw new Error('Invalid method config: invalid timeout.seconds'); + } + if (!('nanos' in obj.timeout) || !(typeof obj.timeout.nanos === 'number')) { + throw new Error('Invalid method config: invalid timeout.nanos'); + } + result.timeout = obj.timeout; + } else if ( + (typeof obj.timeout === 'string') && TIMEOUT_REGEX.test(obj.timeout) ) { + const timeoutParts = obj.timeout.substring(0, obj.timeout.length - 1).split('.'); + result.timeout = { + seconds: timeoutParts[0] | 0, + nanos: (timeoutParts[1] ?? 0) | 0 + } + } else { throw new Error('Invalid method config: invalid timeout'); } - result.timeout = obj.timeout; } if ('maxRequestBytes' in obj) { if (typeof obj.maxRequestBytes !== 'number') { From e3106b99ca9fcebf8db9eedf9cc2a044d82f551c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 May 2021 14:38:02 -0700 Subject: [PATCH 009/694] Don't query the config selector for calls that have ended --- packages/grpc-js/src/channel.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 88b1d4998..8b2658f37 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -509,6 +509,11 @@ export class ChannelImplementation implements Channel { } private tryGetConfig(stream: Http2CallStream, metadata: Metadata) { + if (stream.getStatus() !== null) { + /* If the stream has a status, it has already finished and we don't need + * to take any more actions on it. */ + return; + } if (this.configSelector === null) { /* This branch will only be taken at the beginning of the channel's life, * before the resolver ever returns a result. So, the From f4f1d54031b3788687b21901f92547f42a993866 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 17 May 2021 11:07:45 -0700 Subject: [PATCH 010/694] grpc-js-xds: Propagate timeouts from xDS responses to method config --- packages/grpc-js-xds/src/resolver-xds.ts | 45 ++++++++++++++++++++++-- packages/grpc-js-xds/src/route-action.ts | 16 +++++++-- packages/grpc-js/src/experimental.ts | 2 +- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index a9ee1af7d..892cf5711 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -40,6 +40,8 @@ import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluste import { ExactValueMatcher, Fraction, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from './resources'; +import Duration = experimental.Duration; +import { Duration__Output } from './generated/google/protobuf/Duration'; const TRACER_NAME = 'xds_resolver'; @@ -187,6 +189,20 @@ function getPredicateForMatcher(routeMatch: RouteMatch__Output): Matcher { return new FullMatcher(pathMatcher, headerMatchers, runtimeFraction); } +/** + * Convert a Duration protobuf message object to a Duration object as used in + * the ServiceConfig definition. The difference is that the protobuf message + * defines seconds as a long, which is represented as a string in JavaScript, + * and the one used in the service config defines it as a number. + * @param duration + */ +function protoDurationToDuration(duration: Duration__Output): Duration { + return { + seconds: Number.parseInt(duration.seconds), + nanos: duration.nanos + } +} + class XdsResolver implements Resolver { private hasReportedSuccess = false; @@ -203,6 +219,8 @@ class XdsResolver implements Resolver { private clusterRefcounts = new Map(); + private latestDefaultTimeout: Duration | undefined = undefined; + constructor( private target: GrpcUri, private listener: ResolverListener, @@ -211,6 +229,12 @@ class XdsResolver implements Resolver { this.ldsWatcher = { onValidUpdate: (update: Listener__Output) => { const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, update.api_listener!.api_listener!.value); + const defaultTimeout = httpConnectionManager.common_http_protocol_options?.idle_timeout; + if (defaultTimeout === undefined) { + this.latestDefaultTimeout = undefined; + } else { + this.latestDefaultTimeout = protoDurationToDuration(defaultTimeout); + } switch (httpConnectionManager.route_specifier) { case 'rds': { const routeConfigName = httpConnectionManager.rds!.route_config_name; @@ -295,13 +319,28 @@ class XdsResolver implements Resolver { const matchList: {matcher: Matcher, action: RouteAction}[] = []; for (const route of virtualHost.routes) { let routeAction: RouteAction; + let timeout: Duration | undefined; + /* For field prioritization see + * https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md#supported-fields + */ + if (route.route?.max_stream_duration?.grpc_timeout_header_max) { + timeout = protoDurationToDuration(route.route.max_stream_duration.grpc_timeout_header_max); + } else if (route.route?.max_stream_duration?.max_stream_duration) { + timeout = protoDurationToDuration(route.route.max_stream_duration.max_stream_duration); + } else { + timeout = this.latestDefaultTimeout; + } + // "A value of 0 indicates the application's deadline is used without modification." + if (timeout?.seconds === 0 && timeout.nanos === 0) { + timeout = undefined; + } switch (route.route!.cluster_specifier) { case 'cluster_header': continue; case 'cluster':{ const cluster = route.route!.cluster!; allConfigClusters.add(cluster); - routeAction = new SingleClusterRouteAction(cluster); + routeAction = new SingleClusterRouteAction(cluster, timeout); break; } case 'weighted_clusters': { @@ -310,7 +349,7 @@ class XdsResolver implements Resolver { allConfigClusters.add(clusterWeight.name); weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0}); } - routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100); + routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, timeout); } } const routeMatcher = getPredicateForMatcher(route.match!); @@ -343,7 +382,7 @@ class XdsResolver implements Resolver { this.unrefCluster(clusterName); } return { - methodConfig: {name: []}, + methodConfig: {name: [], timeout: action.getTimeout()}, onCommitted: onCommitted, pickInformation: {cluster: clusterName}, status: status.OK diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts index 4ba2b5908..b43874d8f 100644 --- a/packages/grpc-js-xds/src/route-action.ts +++ b/packages/grpc-js-xds/src/route-action.ts @@ -14,13 +14,17 @@ * limitations under the License. */ +import { experimental } from '@grpc/grpc-js'; +import Duration = experimental.Duration; + export interface RouteAction { toString(): string; getCluster(): string; + getTimeout(): Duration | undefined; } export class SingleClusterRouteAction implements RouteAction { - constructor(private cluster: string) {} + constructor(private cluster: string, private timeout: Duration | undefined) {} getCluster() { return this.cluster; @@ -29,6 +33,10 @@ export class SingleClusterRouteAction implements RouteAction { toString() { return 'SingleCluster(' + this.cluster + ')'; } + + getTimeout() { + return this.timeout; + } } export interface WeightedCluster { @@ -46,7 +54,7 @@ export class WeightedClusterRouteAction implements RouteAction { * The weighted cluster choices represented as a CDF */ private clusterChoices: ClusterChoice[]; - constructor(private clusters: WeightedCluster[], private totalWeight: number) { + constructor(private clusters: WeightedCluster[], private totalWeight: number, private timeout: Duration | undefined) { this.clusterChoices = []; let lastNumerator = 0; for (const clusterWeight of clusters) { @@ -69,4 +77,8 @@ export class WeightedClusterRouteAction implements RouteAction { toString() { return 'WeightedCluster(' + this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ') + ')'; } + + getTimeout() { + return this.timeout; + } } \ No newline at end of file diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index f62838c12..cf84ba6b8 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -1,7 +1,7 @@ export { trace } from './logging'; export { Resolver, ResolverListener, registerResolver, ConfigSelector } from './resolver'; export { GrpcUri, uriToString } from './uri-parser'; -export { ServiceConfig } from './service-config'; +export { ServiceConfig, Duration } from './service-config'; export { BackoffTimeout } from './backoff-timeout'; export { LoadBalancer, LoadBalancingConfig, ChannelControlHelper, registerLoadBalancerType, getFirstUsableConfig, validateLoadBalancingConfig } from './load-balancer'; export { SubchannelAddress, subchannelAddressToString } from './subchannel'; From f5b9e7bab15dcd65aff3c66aa8e8ad6f476621bc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 17 May 2021 14:52:45 -0700 Subject: [PATCH 011/694] grpc-js-xds: Add circuit breaking functionality --- packages/grpc-js-xds/src/load-balancer-eds.ts | 70 ++++++++++++++++--- packages/grpc-js-xds/src/xds-client.ts | 9 +++ 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index ae9a781bd..76bdd6655 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -15,7 +15,7 @@ * */ -import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental } from '@grpc/grpc-js'; +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, StatusObject } from '@grpc/grpc-js'; import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from './xds-client'; import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; import { Locality__Output } from './generated/envoy/api/v2/core/Locality'; @@ -34,6 +34,10 @@ import { validateLoadBalancingConfig } from '@grpc/grpc-js/build/src/experimenta import { WeightedTarget, WeightedTargetLoadBalancingConfig } from './load-balancer-weighted-target'; import { LrsLoadBalancingConfig } from './load-balancer-lrs'; import { Watcher } from './xds-stream-state/xds-stream-state'; +import Filter = experimental.Filter; +import BaseFilter = experimental.BaseFilter; +import FilterFactory = experimental.FilterFactory; +import CallStream = experimental.CallStream; const TRACER_NAME = 'eds_balancer'; @@ -47,7 +51,10 @@ function localityToName(locality: Locality__Output) { return `{region=${locality.region},zone=${locality.zone},sub_zone=${locality.sub_zone}}`; } +const DEFAULT_MAX_CONCURRENT_REQUESTS = 1024; + export class EdsLoadBalancingConfig implements LoadBalancingConfig { + private maxConcurrentRequests: number; getLoadBalancerName(): string { return TYPE_NAME; } @@ -55,7 +62,8 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { const jsonObj: {[key: string]: any} = { cluster: this.cluster, locality_picking_policy: this.localityPickingPolicy.map(policy => policy.toJsonObject()), - endpoint_picking_policy: this.endpointPickingPolicy.map(policy => policy.toJsonObject()) + endpoint_picking_policy: this.endpointPickingPolicy.map(policy => policy.toJsonObject()), + max_concurrent_requests: this.maxConcurrentRequests }; if (this.edsServiceName !== undefined) { jsonObj.eds_service_name = this.edsServiceName; @@ -68,8 +76,8 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { }; } - constructor(private cluster: string, private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string) { - + constructor(private cluster: string, private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string, maxConcurrentRequests?: number) { + this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; } getCluster() { @@ -92,6 +100,10 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { return this.lrsLoadReportingServerName; } + getMaxConcurrentRequests() { + return this.maxConcurrentRequests; + } + static createFromJson(obj: any): EdsLoadBalancingConfig { if (!('cluster' in obj && typeof obj.cluster === 'string')) { throw new Error('eds config must have a string field cluster'); @@ -108,7 +120,28 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { if ('lrs_load_reporting_server_name' in obj && (!obj.lrs_load_reporting_server_name === undefined || typeof obj.lrs_load_reporting_server_name === 'string')) { throw new Error('eds config lrs_load_reporting_server_name must be a string if provided'); } - return new EdsLoadBalancingConfig(obj.cluster, obj.locality_picking_policy.map(validateLoadBalancingConfig), obj.endpoint_picking_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name); + if ('max_concurrent_requests' in obj && (!obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { + throw new Error('eds config max_concurrent_requests must be a number if provided'); + } + return new EdsLoadBalancingConfig(obj.cluster, obj.locality_picking_policy.map(validateLoadBalancingConfig), obj.endpoint_picking_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name, obj.max_concurrent_requests); + } +} + +class CallEndTrackingFilter extends BaseFilter implements Filter { + constructor(private onCallEnd: () => void) { + super(); + } + receiveTrailers(status: StatusObject) { + this.onCallEnd(); + return status; + } +} + +class CallTrackingFilterFactory implements FilterFactory { + constructor(private onCallEnd: () => void) {} + + createFilter(callStream: CallStream) { + return new CallEndTrackingFilter(this.onCallEnd); } } @@ -149,6 +182,8 @@ export class EdsLoadBalancer implements LoadBalancer { private clusterDropStats: XdsClusterDropStats | null = null; + private concurrentRequests: number = 0; + constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler({ createSubchannel: (subchannelAddress, subchannelArgs) => @@ -171,7 +206,11 @@ export class EdsLoadBalancer implements LoadBalancer { if (dropCategory === null) { return originalPicker.pick(pickArgs); } else { - this.clusterDropStats?.addCallDropped(dropCategory); + if (dropCategory === true) { + this.clusterDropStats?.addUncategorizedCallDropped(); + } else { + this.clusterDropStats?.addCallDropped(dropCategory); + } return { pickResultType: PickResultType.DROP, status: { @@ -180,8 +219,12 @@ export class EdsLoadBalancer implements LoadBalancer { metadata: new Metadata(), }, subchannel: null, - extraFilterFactory: null, - onCallStarted: null, + extraFilterFactory: new CallTrackingFilterFactory(() => { + this.concurrentRequests -= 1; + }), + onCallStarted: () => { + this.concurrentRequests += 1; + }, }; } }, @@ -218,12 +261,19 @@ export class EdsLoadBalancer implements LoadBalancer { /** * Check whether a single call should be dropped according to the current * policy, based on randomly chosen numbers. Returns the drop category if - * the call should be dropped, and null otherwise. + * the call should be dropped, and null otherwise. true is a valid + * output, as a sentinel value indicating a drop with no category. */ - private checkForDrop(): string | null { + private checkForDrop(): string | true | null { if (!this.latestEdsUpdate?.policy) { return null; } + if (!this.lastestConfig) { + return null; + } + if (this.concurrentRequests >= this.lastestConfig.getMaxConcurrentRequests()) { + return true; + } /* The drop_overloads policy is a list of pairs of category names and * probabilities. For each one, if the random number is within that * probability range, we drop the call citing that category. Otherwise, the diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index adc54ed45..ddca9284f 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -114,6 +114,7 @@ function localityEqual( } export interface XdsClusterDropStats { + addUncategorizedCallDropped(): void; addCallDropped(category: string): void; } @@ -158,6 +159,7 @@ interface ClusterLocalityStats { interface ClusterLoadReport { callsDropped: Map; + uncategorizedCallsDropped: number; localityStats: ClusterLocalityStats[]; intervalStart: [number, number]; } @@ -195,6 +197,7 @@ class ClusterLoadReportMap { } const newStats: ClusterLoadReport = { callsDropped: new Map(), + uncategorizedCallsDropped: 0, localityStats: [], intervalStart: process.hrtime(), }; @@ -871,8 +874,10 @@ export class XdsClient { totalDroppedRequests += count; } } + totalDroppedRequests += stats.uncategorizedCallsDropped; // Clear out dropped call stats after sending them stats.callsDropped.clear(); + stats.uncategorizedCallsDropped = 0; const interval = process.hrtime(stats.intervalStart); stats.intervalStart = process.hrtime(); // Skip clusters with 0 requests @@ -957,6 +962,7 @@ export class XdsClient { trace('addClusterDropStats(lrsServer=' + lrsServer + ', clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ')'); if (lrsServer !== '') { return { + addUncategorizedCallDropped: () => {}, addCallDropped: (category) => {}, }; } @@ -965,6 +971,9 @@ export class XdsClient { edsServiceName ); return { + addUncategorizedCallDropped: () => { + clusterStats.uncategorizedCallsDropped += 1; + }, addCallDropped: (category) => { const prevCount = clusterStats.callsDropped.get(category) ?? 0; clusterStats.callsDropped.set(category, prevCount + 1); From bdc8d2de9db276f2adec7ccf6c432bcf8425215f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 19 May 2021 13:19:36 -0700 Subject: [PATCH 012/694] Don't clean after testing on each Node version --- run-tests.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 2808e2af6..03fd970b5 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -71,7 +71,6 @@ do node -e 'process.exit(process.version.startsWith("v'$version'") ? 0 : -1)' # Install dependencies and link packages together. - ./node_modules/.bin/gulp cleanAll ./node_modules/.bin/gulp setup # npm test calls nyc gulp test From d51551f6d71b05fa6934045022a961bfc1d4d1b4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 17 May 2021 11:08:54 -0700 Subject: [PATCH 013/694] grpc-js-xds: Add support for timeout xDS interop test Add more logging --- .../grpc-js-xds/interop/xds-interop-client.ts | 45 ++++++++-- packages/grpc-js-xds/scripts/xds.sh | 4 +- packages/grpc-js-xds/src/route-action.ts | 22 ++++- packages/grpc-js/src/metadata.ts | 12 +++ packages/grpc-js/test/test-deadline.ts | 90 +++++++++++++++++++ test/kokoro/xds-interop.cfg | 2 +- test/kokoro/xds-v3-interop.cfg | 2 +- 7 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 packages/grpc-js/test/test-deadline.ts diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 5f32831c1..f6beccbf0 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -197,29 +197,44 @@ let anyCallSucceeded = false; const accumulatedStats: LoadBalancerAccumulatedStatsResponse = { stats_per_method: { - EmptyCall: { + EMPTY_CALL: { rpcs_started: 0, result: {} }, - UnaryCall: { + UNARY_CALL: { rpcs_started: 0, result: {} } } }; +const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = { + UnaryCall: {}, + EmptyCall: {} +} + function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { - const callTypeStats = accumulatedStats.stats_per_method![type]; + const callTypeStats = accumulatedStats.stats_per_method![callTypeEnumMapReverse[type]]; callTypeStats.rpcs_started! += 1; const notifier = callStatsTracker.startCall(); let gotMetadata: boolean = false; let hostname: string | null = null; let completed: boolean = false; let completedWithError: boolean = false; + const startTime = process.hrtime(); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + currentConfig.timeoutSec); const callback = (error: grpc.ServiceError | undefined, value: Empty__Output | undefined) => { const statusCode = error?.code ?? grpc.status.OK; + const duration = process.hrtime(startTime); + if (!callTimeHistogram[type][statusCode]) { + callTimeHistogram[type][statusCode] = []; + } + if (callTimeHistogram[type][statusCode][duration[0]]) { + callTimeHistogram[type][statusCode][duration[0]] += 1; + } else { + callTimeHistogram[type][statusCode][duration[0]] = 1; + } callTypeStats.result![statusCode] = (callTypeStats.result![statusCode] ?? 0) + 1; if (error) { if (failOnFailedRpcs && anyCallSucceeded) { @@ -269,18 +284,27 @@ const callTypeEnumMap = { 'UNARY_CALL': 'UnaryCall' as CallType }; +const callTypeEnumMapReverse = { + 'EmptyCall': 'EMPTY_CALL', + 'UnaryCall': 'UNARY_CALL' +} + +const DEFAULT_TIMEOUT_SEC = 20; + function main() { const argv = yargs .string(['fail_on_failed_rpcs', 'server', 'stats_port', 'rpc', 'metadata']) - .number(['num_channels', 'qps']) + .number(['num_channels', 'qps', 'rpc_timeout_sec']) .demandOption(['server', 'stats_port']) .default('num_channels', 1) .default('qps', 1) .default('rpc', 'UnaryCall') .default('metadata', '') + .default('rpc_timeout_sec', DEFAULT_TIMEOUT_SEC) .argv; console.log('Starting xDS interop client. Args: ', argv); currentConfig.callTypes = argv.rpc.split(',').filter(value => value === 'EmptyCall' || value === 'UnaryCall') as CallType[]; + currentConfig.timeoutSec = argv.rpc_timeout_sec; for (const item of argv.metadata.split(',')) { const [method, key, value] = item.split(':'); if (value === undefined) { @@ -316,23 +340,30 @@ function main() { }); }, GetClientAccumulatedStats: (call, callback) => { + console.log(`Sending accumulated stats response: ${JSON.stringify(accumulatedStats)}`); + console.log(`Call durations: ${JSON.stringify(callTimeHistogram, undefined, 2)}`); callback(null, accumulatedStats); } } const xdsUpdateClientConfigureServiceImpl: XdsUpdateClientConfigureServiceHandlers = { Configure: (call, callback) => { + console.log('Received new client configuration: ' + JSON.stringify(call.request, undefined, 2)); const callMetadata = { EmptyCall: new grpc.Metadata(), UnaryCall: new grpc.Metadata() - } + }; for (const metadataItem of call.request.metadata) { callMetadata[callTypeEnumMap[metadataItem.type]].add(metadataItem.key, metadataItem.value); } currentConfig.callTypes = call.request.types.map(value => callTypeEnumMap[value]); currentConfig.metadata = callMetadata; - currentConfig.timeoutSec = call.request.timeout_sec - console.log('Received new client configuration: ' + JSON.stringify(currentConfig, undefined, 2)); + if (call.request.timeout_sec > 0) { + currentConfig.timeoutSec = call.request.timeout_sec; + } else { + currentConfig.timeoutSec = DEFAULT_TIMEOUT_SEC; + } + console.log('Updated to new client configuration: ' + JSON.stringify(currentConfig, undefined, 2)); callback(null, {}); } } diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 9008b9731..68225faef 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -52,9 +52,9 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ - --test_case="all,path_matching,header_matching" \ + --test_case="all,timeout" \ --project_id=grpc-testing \ - --source_image=projects/grpc-testing/global/images/xds-test-server-2 \ + --source_image=projects/grpc-testing/global/images/xds-test-server-4 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts index b43874d8f..5574bcd91 100644 --- a/packages/grpc-js-xds/src/route-action.ts +++ b/packages/grpc-js-xds/src/route-action.ts @@ -23,6 +23,15 @@ export interface RouteAction { getTimeout(): Duration | undefined; } +function durationToLogString(duration: Duration) { + const millis = Math.floor(duration.nanos / 1_000_000); + if (millis > 0) { + return duration.seconds + '.' + millis; + } else { + return '' + duration.seconds; + } +} + export class SingleClusterRouteAction implements RouteAction { constructor(private cluster: string, private timeout: Duration | undefined) {} @@ -31,7 +40,11 @@ export class SingleClusterRouteAction implements RouteAction { } toString() { - return 'SingleCluster(' + this.cluster + ')'; + if (this.timeout) { + return 'SingleCluster(' + this.cluster + ', ' + 'timeout=' + durationToLogString(this.timeout) + 's)'; + } else { + return 'SingleCluster(' + this.cluster + ')'; + } } getTimeout() { @@ -75,7 +88,12 @@ export class WeightedClusterRouteAction implements RouteAction { } toString() { - return 'WeightedCluster(' + this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ') + ')'; + const clusterListString = this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ') + if (this.timeout) { + return 'WeightedCluster(' + clusterListString + ', ' + 'timeout=' + durationToLogString(this.timeout) + 's)'; + } else { + return 'WeightedCluster(' + clusterListString + ')'; + } } getTimeout() { diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts index 4947bf0ad..bc66012ce 100644 --- a/packages/grpc-js/src/metadata.ts +++ b/packages/grpc-js/src/metadata.ts @@ -242,6 +242,18 @@ export class Metadata { return this.internalRepr; } + /** + * This modifies the behavior of JSON.stringify to show an object + * representation of the metadata map. + */ + toJSON() { + const result: {[key: string]: MetadataValue[]} = {}; + for (const [key, values] of this.internalRepr.entries()) { + result[key] = values; + } + return result; + } + /** * Returns a new Metadata object based fields in a given IncomingHttpHeaders * object. diff --git a/packages/grpc-js/test/test-deadline.ts b/packages/grpc-js/test/test-deadline.ts new file mode 100644 index 000000000..bb6b3ba9b --- /dev/null +++ b/packages/grpc-js/test/test-deadline.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2021 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 * as assert from 'assert'; + +import * as grpc from '../src'; +import { experimental } from '../src'; +import { ServerCredentials } from '../src'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { loadProtoFile } from './common'; +import ServiceConfig = experimental.ServiceConfig; + +const clientInsecureCreds = grpc.credentials.createInsecure(); +const serverInsecureCreds = ServerCredentials.createInsecure(); + +const TIMEOUT_SERVICE_CONFIG: ServiceConfig = { + loadBalancingConfig: [], + methodConfig: [{ + name: [ + {service: 'TestService'} + ], + timeout: { + seconds: 1, + nanos: 0 + } + }] +}; + +describe('Client with configured timeout', () => { + let server: grpc.Server; + let Client: ServiceClientConstructor; + let client: ServiceClient; + + before(done => { + Client = loadProtoFile(__dirname + '/fixtures/test_service.proto').TestService as ServiceClientConstructor; + server = new grpc.Server(); + server.addService(Client.service, { + unary: () => {}, + clientStream: () => {}, + serverStream: () => {}, + bidiStream: () => {} + }); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + server.start(); + client = new Client(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(TIMEOUT_SERVICE_CONFIG)}); + done(); + }); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('Should end calls without explicit deadline with DEADLINE_EXCEEDED', done => { + client.unary({}, (error: grpc.ServiceError, value: unknown) =>{ + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + + it('Should end calls with a long explicit deadline with DEADLINE_EXCEEDED', done => { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 20); + client.unary({}, (error: grpc.ServiceError, value: unknown) =>{ + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test/kokoro/xds-interop.cfg b/test/kokoro/xds-interop.cfg index 16a7dc7dc..a615d0530 100644 --- a/test/kokoro/xds-interop.cfg +++ b/test/kokoro/xds-interop.cfg @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc-node/packages/grpc-js-xds/scripts/xds.sh" -timeout_mins: 120 +timeout_mins: 180 action { define_artifacts { regex: "github/grpc/reports/**" diff --git a/test/kokoro/xds-v3-interop.cfg b/test/kokoro/xds-v3-interop.cfg index d7ffa8fab..4480f1590 100644 --- a/test/kokoro/xds-v3-interop.cfg +++ b/test/kokoro/xds-v3-interop.cfg @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc-node/packages/grpc-js-xds/scripts/xds-v3.sh" -timeout_mins: 120 +timeout_mins: 180 action { define_artifacts { regex: "github/grpc/reports/**" From ec7c819181bf24770e010e81378293dea6d799e5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 May 2021 10:30:28 -0700 Subject: [PATCH 014/694] grpc-js-xds: Enable circuit breaking test --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 68225faef..66fe4d568 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -52,7 +52,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ - --test_case="all,timeout" \ + --test_case="all,timeout,circuit_breaking" \ --project_id=grpc-testing \ --source_image=projects/grpc-testing/global/images/xds-test-server-4 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ From 42d016d979c28ea288923c2b4a6a9426b8d89fd9 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Fri, 21 May 2021 10:22:14 +0200 Subject: [PATCH 015/694] workaround "key too small" problem in aarch64 tests --- test/kokoro_linux_aarch64.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/kokoro_linux_aarch64.sh b/test/kokoro_linux_aarch64.sh index 9dd6ad86a..508881e5f 100755 --- a/test/kokoro_linux_aarch64.sh +++ b/test/kokoro_linux_aarch64.sh @@ -39,4 +39,8 @@ TEST_NODE_COMMAND="node_versions='12' ./run-tests.sh" # running under current user's UID and GID. To be able to do that, we need to provide a home directory for the user # otherwise the UID would be homeless under the docker container (which can lead to various issues). For simplicity, # we just run map the user's home to a throwaway temporary directory. -docker run $DOCKER_TTY_ARGS --rm --user "$(id -u):$(id -g)" -e "HOME=/home/fake-user" -v "$(mktemp -d):/home/fake-user" -v "$(pwd)":/work -w /work arm64v8/node:12-buster bash -c "$TEST_NODE_COMMAND" +# TODO(jtattermusch): we're using arm64v8/node:12-stretch instead of arm64v8/node:12-buster because the buster-based image +# has a newer version of ssl that considers some of the ssl keys used for testing too short, making the tests +# fails with "error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small". +# See https://github.com/grpc/grpc-node/issues/1795 +docker run $DOCKER_TTY_ARGS --rm --user "$(id -u):$(id -g)" -e "HOME=/home/fake-user" -v "$(mktemp -d):/home/fake-user" -v "$(pwd)":/work -w /work arm64v8/node:12-stretch bash -c "$TEST_NODE_COMMAND" From accc82fd111e75685b0631f0aa411490032608ca Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 1 Jun 2021 14:56:54 +0200 Subject: [PATCH 016/694] increase timeout for tests that timeout under emulator --- test/api/interop_extra_test.js | 6 ++++++ test/api/interop_sanity_test.js | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/test/api/interop_extra_test.js b/test/api/interop_extra_test.js index f8db35b38..319ccb536 100644 --- a/test/api/interop_extra_test.js +++ b/test/api/interop_extra_test.js @@ -197,6 +197,8 @@ describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, functio }); }); describe('max message size', function() { + // with the default timeout the test times out under aarch64 emulator + this.timeout(6000); // A size that is larger than the default limit const largeMessageSize = 8 * 1024 * 1024; const largeMessage = Buffer.alloc(largeMessageSize); @@ -238,6 +240,8 @@ describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, functio }); }); describe('with a client with no message size limits', function() { + // with the default timeout the test times out under aarch64 emulator + this.timeout(6000); let unrestrictedClient; before(function() { const ca_path = path.join(__dirname, '../data/ca.pem'); @@ -283,6 +287,8 @@ describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, functio }); }); describe('with a server with message size limits and a client without limits', function() { + // with the default timeout the test times out under aarch64 emulator + this.timeout(6000); let restrictedServer; let restrictedServerClient; let restrictedServerClient2; diff --git a/test/api/interop_sanity_test.js b/test/api/interop_sanity_test.js index 995650e20..c1e5d92b5 100644 --- a/test/api/interop_sanity_test.js +++ b/test/api/interop_sanity_test.js @@ -48,7 +48,8 @@ var childExecArgv = []; describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { describe('Interop tests', function() { - this.timeout(4000); + // with the default timeout the test times out under aarch64 emulator + this.timeout(10000); before(function(done) { for (let arg of process.argv) { if (arg.startsWith('--require=')) { From 43a3bad54903bf85dd44ad2bc2e985c97942fa34 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 May 2021 10:31:00 -0700 Subject: [PATCH 017/694] Fix circuit breaking functionality --- .../grpc-js-xds/interop/xds-interop-client.ts | 32 +++++++++++++-- packages/grpc-js-xds/src/load-balancer-cds.ts | 8 +++- packages/grpc-js-xds/src/load-balancer-eds.ts | 41 ++++++++++++------- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index f6beccbf0..a414ffe7f 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -196,6 +196,18 @@ const currentConfig: ClientConfiguration = { let anyCallSucceeded = false; const accumulatedStats: LoadBalancerAccumulatedStatsResponse = { + num_rpcs_started_by_method: { + EMPTY_CALL: 0, + UNARY_CALL: 0 + }, + num_rpcs_succeeded_by_method: { + EMPTY_CALL: 0, + UNARY_CALL: 0 + }, + num_rpcs_failed_by_method: { + EMPTY_CALL: 0, + UNARY_CALL: 0 + }, stats_per_method: { EMPTY_CALL: { rpcs_started: 0, @@ -208,14 +220,28 @@ const accumulatedStats: LoadBalancerAccumulatedStatsResponse = { } }; +function addAccumulatedCallStarted(callName: string) { + accumulatedStats.stats_per_method![callName].rpcs_started! += 1; + accumulatedStats.num_rpcs_started_by_method![callName] += 1; +} + +function addAccumulatedCallEnded(callName: string, result: grpc.status) { + accumulatedStats.stats_per_method![callName].result![result] = (accumulatedStats.stats_per_method![callName].result![result] ?? 0) + 1; + if (result === grpc.status.OK) { + accumulatedStats.num_rpcs_succeeded_by_method![callName] += 1; + } else { + accumulatedStats.num_rpcs_failed_by_method![callName] += 1; + } +} + const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = { UnaryCall: {}, EmptyCall: {} } function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { - const callTypeStats = accumulatedStats.stats_per_method![callTypeEnumMapReverse[type]]; - callTypeStats.rpcs_started! += 1; + const callEnumName = callTypeEnumMapReverse[type]; + addAccumulatedCallStarted(callEnumName); const notifier = callStatsTracker.startCall(); let gotMetadata: boolean = false; let hostname: string | null = null; @@ -235,7 +261,7 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail } else { callTimeHistogram[type][statusCode][duration[0]] = 1; } - callTypeStats.result![statusCode] = (callTypeStats.result![statusCode] ?? 0) + 1; + addAccumulatedCallEnded(callEnumName, statusCode); if (error) { if (failOnFailedRpcs && anyCallSucceeded) { console.error('A call failed after a call succeeded'); diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 6c57a410f..4829edd44 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -80,11 +80,17 @@ export class CdsLoadBalancer implements LoadBalancer { this.watcher = { onValidUpdate: (update) => { this.latestCdsUpdate = update; + let maxConcurrentRequests: number | undefined = undefined; + for (const threshold of update.circuit_breakers?.thresholds ?? []) { + if (threshold.priority === 'DEFAULT') { + maxConcurrentRequests = threshold.max_requests?.value; + } + } /* the lrs_server.self field indicates that the same server should be * used for load reporting as for other xDS operations. Setting * lrsLoadReportingServerName to the empty string sets that behavior. * Otherwise, if the field is omitted, load reporting is disabled. */ - const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig(update.name, [], [], update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, update.lrs_server?.self ? '' : undefined); + const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig(update.name, [], [], update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, update.lrs_server?.self ? '' : undefined, maxConcurrentRequests); trace('Child update EDS config: ' + JSON.stringify(edsConfig)); this.childBalancer.updateAddressList( [], diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index 76bdd6655..183e0d670 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -37,6 +37,7 @@ import { Watcher } from './xds-stream-state/xds-stream-state'; import Filter = experimental.Filter; import BaseFilter = experimental.BaseFilter; import FilterFactory = experimental.FilterFactory; +import FilterStackFactory = experimental.FilterStackFactory; import CallStream = experimental.CallStream; const TRACER_NAME = 'eds_balancer'; @@ -204,27 +205,42 @@ export class EdsLoadBalancer implements LoadBalancer { * Otherwise, delegate picking the subchannel to the child * balancer. */ if (dropCategory === null) { - return originalPicker.pick(pickArgs); + const originalPick = originalPicker.pick(pickArgs); + let extraFilterFactory: FilterFactory = new CallTrackingFilterFactory(() => { + this.concurrentRequests -= 1; + }); + if (originalPick.extraFilterFactory) { + extraFilterFactory = new FilterStackFactory([originalPick.extraFilterFactory, extraFilterFactory]); + } + return { + pickResultType: originalPick.pickResultType, + status: originalPick.status, + subchannel: originalPick.subchannel, + onCallStarted: () => { + originalPick.onCallStarted?.(); + this.concurrentRequests += 1; + }, + extraFilterFactory: extraFilterFactory + }; } else { + let details: string; if (dropCategory === true) { + details = 'Call dropped by load balancing policy.'; this.clusterDropStats?.addUncategorizedCallDropped(); } else { + details = `Call dropped by load balancing policy. Category: ${dropCategory}`; this.clusterDropStats?.addCallDropped(dropCategory); } return { pickResultType: PickResultType.DROP, status: { code: Status.UNAVAILABLE, - details: `Call dropped by load balancing policy. Category: ${dropCategory}`, + details: details, metadata: new Metadata(), }, subchannel: null, - extraFilterFactory: new CallTrackingFilterFactory(() => { - this.concurrentRequests -= 1; - }), - onCallStarted: () => { - this.concurrentRequests += 1; - }, + extraFilterFactory: null, + onCallStarted: null }; } }, @@ -265,15 +281,12 @@ export class EdsLoadBalancer implements LoadBalancer { * output, as a sentinel value indicating a drop with no category. */ private checkForDrop(): string | true | null { - if (!this.latestEdsUpdate?.policy) { - return null; + if (this.lastestConfig && this.concurrentRequests >= this.lastestConfig.getMaxConcurrentRequests()) { + return true; } - if (!this.lastestConfig) { + if (!this.latestEdsUpdate?.policy) { return null; } - if (this.concurrentRequests >= this.lastestConfig.getMaxConcurrentRequests()) { - return true; - } /* The drop_overloads policy is a list of pairs of category names and * probabilities. For each one, if the random number is within that * probability range, we drop the call citing that category. Otherwise, the From 8bc20b28fd1191902dac745665f3dc661ddded54 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Wed, 2 Jun 2021 16:02:42 +0200 Subject: [PATCH 018/694] increase timeout for make emulated aarch64 tests green --- test/api/interop_extra_test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/api/interop_extra_test.js b/test/api/interop_extra_test.js index 319ccb536..fa7e03276 100644 --- a/test/api/interop_extra_test.js +++ b/test/api/interop_extra_test.js @@ -147,7 +147,8 @@ describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, functio }); }); it('should receive all messages in a long stream', function(done) { - this.timeout(20000); + // the test is slow under aarch64 emulator + this.timeout(80000); var arg = { response_type: 'COMPRESSABLE', response_parameters: [ From f01b6d9fcad9fcd199dc13ddbea220fd0917faf9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 2 Jun 2021 13:42:41 -0700 Subject: [PATCH 019/694] grpc-js: Export ServerErrorResponse type, which is used in public APIs --- packages/grpc-js/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 308074640..05a82e94d 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -62,6 +62,7 @@ import { ServerReadableStream, ServerWritableStream, ServerDuplexStream, + ServerErrorResponse, } from './server-call'; export { OAuth2Client }; @@ -173,6 +174,7 @@ export { ServerReadableStream, ServerWritableStream, ServerDuplexStream, + ServerErrorResponse, ServiceDefinition, UntypedHandleCall, UntypedServiceImplementation, From 8a38cd8549dd804b2bf070e3ec4620c446d411e8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 10 Jun 2021 14:48:33 -0700 Subject: [PATCH 020/694] grpc-js: Refactor FilterStack usage --- packages/grpc-js-xds/src/load-balancer-eds.ts | 10 +++------- packages/grpc-js-xds/src/load-balancer-lrs.ts | 11 +---------- .../src/load-balancer-xds-cluster-manager.ts | 2 +- packages/grpc-js/src/call-stream.ts | 11 +++-------- packages/grpc-js/src/channel.ts | 2 +- packages/grpc-js/src/filter-stack.ts | 8 ++++++++ packages/grpc-js/src/load-balancer-pick-first.ts | 2 +- packages/grpc-js/src/load-balancer-round-robin.ts | 2 +- packages/grpc-js/src/picker.ts | 14 +++++++------- packages/grpc-js/src/subchannel.ts | 4 ++-- 10 files changed, 28 insertions(+), 38 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index 183e0d670..efb05eb78 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -37,7 +37,6 @@ import { Watcher } from './xds-stream-state/xds-stream-state'; import Filter = experimental.Filter; import BaseFilter = experimental.BaseFilter; import FilterFactory = experimental.FilterFactory; -import FilterStackFactory = experimental.FilterStackFactory; import CallStream = experimental.CallStream; const TRACER_NAME = 'eds_balancer'; @@ -206,12 +205,9 @@ export class EdsLoadBalancer implements LoadBalancer { * balancer. */ if (dropCategory === null) { const originalPick = originalPicker.pick(pickArgs); - let extraFilterFactory: FilterFactory = new CallTrackingFilterFactory(() => { + const trackingFilterFactory: FilterFactory = new CallTrackingFilterFactory(() => { this.concurrentRequests -= 1; }); - if (originalPick.extraFilterFactory) { - extraFilterFactory = new FilterStackFactory([originalPick.extraFilterFactory, extraFilterFactory]); - } return { pickResultType: originalPick.pickResultType, status: originalPick.status, @@ -220,7 +216,7 @@ export class EdsLoadBalancer implements LoadBalancer { originalPick.onCallStarted?.(); this.concurrentRequests += 1; }, - extraFilterFactory: extraFilterFactory + extraFilterFactories: originalPick.extraFilterFactories.concat(trackingFilterFactory) }; } else { let details: string; @@ -239,7 +235,7 @@ export class EdsLoadBalancer implements LoadBalancer { metadata: new Metadata(), }, subchannel: null, - extraFilterFactory: null, + extraFilterFactories: [], onCallStarted: null }; } diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 0792b11c2..566aa04c5 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -32,7 +32,6 @@ import PickResult = experimental.PickResult; import Filter = experimental.Filter; import BaseFilter = experimental.BaseFilter; import FilterFactory = experimental.FilterFactory; -import FilterStackFactory = experimental.FilterStackFactory; import Call = experimental.CallStream; import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig @@ -148,14 +147,6 @@ class LoadReportingPicker implements Picker { const trackingFilterFactory = new CallEndTrackingFilterFactory( this.localityStatsReporter ); - /* In the unlikely event that the wrappedPick already has an - * extraFilterFactory, preserve it in a FilterStackFactory. */ - const extraFilterFactory = wrappedPick.extraFilterFactory - ? new FilterStackFactory([ - wrappedPick.extraFilterFactory, - trackingFilterFactory, - ]) - : trackingFilterFactory; return { pickResultType: PickResultType.COMPLETE, subchannel: wrappedPick.subchannel, @@ -164,7 +155,7 @@ class LoadReportingPicker implements Picker { wrappedPick.onCallStarted?.(); this.localityStatsReporter.addCallStarted(); }, - extraFilterFactory: extraFilterFactory, + extraFilterFactories: wrappedPick.extraFilterFactories.concat(trackingFilterFactory), }; } else { return wrappedPick; diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index 920a43db9..7df79e26b 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -107,7 +107,7 @@ class XdsClusterManagerPicker implements Picker { metadata: new Metadata(), }, subchannel: null, - extraFilterFactory: null, + extraFilterFactories: [], onCallStarted: null }; } diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 3a81cbe94..ae6342604 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -210,7 +210,7 @@ export interface Call { export class Http2CallStream implements Call { credentials: CallCredentials; - filterStack: Filter; + filterStack: FilterStack; private http2Stream: http2.ClientHttp2Stream | null = null; private pendingRead = false; private isWriteFilterPending = false; @@ -462,14 +462,9 @@ export class Http2CallStream implements Call { attachHttp2Stream( stream: http2.ClientHttp2Stream, subchannel: Subchannel, - extraFilterFactory?: FilterFactory + extraFilters: FilterFactory[] ): void { - if (extraFilterFactory !== undefined) { - this.filterStack = new FilterStack([ - this.filterStack, - extraFilterFactory.createFilter(this), - ]); - } + this.filterStack.push(extraFilters.map(filterFactory => filterFactory.createFilter(this))); if (this.finalStatus !== null) { stream.close(NGHTTP2_CANCEL); } else { diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 8b2658f37..cd2fc94a2 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -373,7 +373,7 @@ export class ChannelImplementation implements Channel { pickResult.subchannel!.startCallStream( finalMetadata, callStream, - pickResult.extraFilterFactory ?? undefined + pickResult.extraFilterFactories ); /* If we reach this point, the call stream has started * successfully */ diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts index 4fd888549..4e0ccf07f 100644 --- a/packages/grpc-js/src/filter-stack.ts +++ b/packages/grpc-js/src/filter-stack.ts @@ -77,11 +77,19 @@ export class FilterStack implements Filter { filter.refresh(); } } + + push(filters: Filter[]) { + this.filters.unshift(...filters); + } } export class FilterStackFactory implements FilterFactory { constructor(private readonly factories: Array>) {} + push(filterFactories: FilterFactory[]) { + this.factories.unshift(...filterFactories); + } + createFilter(callStream: Call): FilterStack { return new FilterStack( this.factories.map((factory) => factory.createFilter(callStream)) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 31dc17847..688f95562 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -83,7 +83,7 @@ class PickFirstPicker implements Picker { pickResultType: PickResultType.COMPLETE, subchannel: this.subchannel, status: null, - extraFilterFactory: null, + extraFilterFactories: [], onCallStarted: null, }; } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index daba45941..2159e64c7 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -78,7 +78,7 @@ class RoundRobinPicker implements Picker { pickResultType: PickResultType.COMPLETE, subchannel: pickedSubchannel, status: null, - extraFilterFactory: null, + extraFilterFactories: [], onCallStarted: null, }; } diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index 6df61b59a..25929884b 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -47,7 +47,7 @@ export interface PickResult { * provided by the load balancer to be used with the call. For technical * reasons filters from this factory will not see sendMetadata events. */ - extraFilterFactory: FilterFactory | null; + extraFilterFactories: FilterFactory[]; onCallStarted: (() => void) | null; } @@ -55,7 +55,7 @@ export interface CompletePickResult extends PickResult { pickResultType: PickResultType.COMPLETE; subchannel: Subchannel | null; status: null; - extraFilterFactory: FilterFactory | null; + extraFilterFactories: FilterFactory[]; onCallStarted: (() => void) | null; } @@ -63,7 +63,7 @@ export interface QueuePickResult extends PickResult { pickResultType: PickResultType.QUEUE; subchannel: null; status: null; - extraFilterFactory: null; + extraFilterFactories: []; onCallStarted: null; } @@ -71,7 +71,7 @@ export interface TransientFailurePickResult extends PickResult { pickResultType: PickResultType.TRANSIENT_FAILURE; subchannel: null; status: StatusObject; - extraFilterFactory: null; + extraFilterFactories: []; onCallStarted: null; } @@ -79,7 +79,7 @@ export interface DropCallPickResult extends PickResult { pickResultType: PickResultType.DROP; subchannel: null; status: StatusObject; - extraFilterFactory: null; + extraFilterFactories: []; onCallStarted: null; } @@ -119,7 +119,7 @@ export class UnavailablePicker implements Picker { pickResultType: PickResultType.TRANSIENT_FAILURE, subchannel: null, status: this.status, - extraFilterFactory: null, + extraFilterFactories: [], onCallStarted: null, }; } @@ -148,7 +148,7 @@ export class QueuePicker { pickResultType: PickResultType.QUEUE, subchannel: null, status: null, - extraFilterFactory: null, + extraFilterFactories: [], onCallStarted: null, }; } diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 772bdb228..c50a68e77 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -688,7 +688,7 @@ export class Subchannel { startCallStream( metadata: Metadata, callStream: Http2CallStream, - extraFilterFactory?: FilterFactory + extraFilterFactories: FilterFactory[] ) { const headers = metadata.toHttp2Headers(); headers[HTTP2_HEADER_AUTHORITY] = callStream.getHost(); @@ -720,7 +720,7 @@ export class Subchannel { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; } logging.trace(LogVerbosity.DEBUG, 'call_stream', 'Starting stream on subchannel ' + this.subchannelAddressString + ' with headers\n' + headersString); - callStream.attachHttp2Stream(http2Stream, this, extraFilterFactory); + callStream.attachHttp2Stream(http2Stream, this, extraFilterFactories); } /** From acfb3c28298742a105e653c5235501a80879c459 Mon Sep 17 00:00:00 2001 From: David Goitia Date: Thu, 17 Jun 2021 14:35:29 +0200 Subject: [PATCH 021/694] Update node-pre-gyp dependency --- packages/grpc-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index b1f83ce45..25ba1da73 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -24,7 +24,7 @@ "prepublishOnly": "git submodule update --init --recursive && node copy_well_known_protos.js" }, "dependencies": { - "node-pre-gyp": "^0.15.0" + "@mapbox/node-pre-gyp": "^1.0.5" }, "binary": { "module_name": "grpc_tools", From 948fc6c1223c6de9d159c9ef24fce4b94c09ff12 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 17 Jun 2021 09:49:42 -0700 Subject: [PATCH 022/694] Don't install docker in the docker image --- tools/release/native/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/release/native/Dockerfile b/tools/release/native/Dockerfile index bc03bf178..d065bddbe 100644 --- a/tools/release/native/Dockerfile +++ b/tools/release/native/Dockerfile @@ -6,7 +6,6 @@ RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt RUN apt-get update RUN apt-get -t jessie-backports install -y cmake RUN apt-get install -y curl build-essential python libc6-dev-i386 lib32stdc++-4.9-dev jq -RUN curl -fsSL get.docker.com | bash RUN mkdir /usr/local/nvm ENV NVM_DIR /usr/local/nvm From cb505b455646e2a9ba0e853ca1b61d161fe638e6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 17 Jun 2021 11:12:35 -0700 Subject: [PATCH 023/694] grpc-tools: Bump to version 1.11.2 --- packages/grpc-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index 25ba1da73..95faa84c4 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.11.1", + "version": "1.11.2", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", From 237ea8308a0298aeded01de83a225eda092e9cf4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 18 Jun 2021 14:37:03 -0700 Subject: [PATCH 024/694] grpc-js: Make logging behavior more similar to core --- packages/grpc-js/src/constants.ts | 1 + packages/grpc-js/src/logging.ts | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/constants.ts b/packages/grpc-js/src/constants.ts index d30b78f08..94763cfe1 100644 --- a/packages/grpc-js/src/constants.ts +++ b/packages/grpc-js/src/constants.ts @@ -39,6 +39,7 @@ export enum LogVerbosity { DEBUG = 0, INFO, ERROR, + NONE, } /** diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts index 1140e8d8a..de8d2d056 100644 --- a/packages/grpc-js/src/logging.ts +++ b/packages/grpc-js/src/logging.ts @@ -32,6 +32,9 @@ switch (verbosityString) { case 'ERROR': _logVerbosity = LogVerbosity.ERROR; break; + case 'NONE': + _logVerbosity = LogVerbosity.NONE; + break; default: // Ignore any other values } @@ -56,15 +59,23 @@ export const log = (severity: LogVerbosity, ...args: any[]): void => { }; const tracersString = process.env.GRPC_NODE_TRACE ?? process.env.GRPC_TRACE ?? ''; -const enabledTracers = tracersString.split(','); -const allEnabled = enabledTracers.includes('all'); +const enabledTracers = new Set(); +const disabledTracers = new Set(); +for (const tracerName of tracersString.split(',')) { + if (tracerName.startsWith('-')) { + disabledTracers.add(tracerName.substring(1)); + } else { + enabledTracers.add(tracerName) + } +} +const allEnabled = enabledTracers.has('all'); export function trace( severity: LogVerbosity, tracer: string, text: string ): void { - if (allEnabled || enabledTracers.includes(tracer)) { + if (!disabledTracers.has(tracer) && (allEnabled || enabledTracers.has(tracer))) { log(severity, new Date().toISOString() + ' | ' + tracer + ' | ' + text); } } From a214aca7686bb8ba0a104885c4e6ed869932e38f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 18 Jun 2021 14:37:26 -0700 Subject: [PATCH 025/694] Document environment variables, particularly for logging --- TROUBLESHOOTING.md | 38 +++++++++++++++++++++++ doc/environment_variables.md | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 TROUBLESHOOTING.md create mode 100644 doc/environment_variables.md diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 000000000..dd5bce7af --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,38 @@ +# Troubleshooting grpc-js + +This guide is for troubleshooting the `grpc-js` library for Node.js + +## Enabling extra logging and tracing + +Extra logging can be very useful for diagnosing problems. `grpc-js` supporst +the `GRPC_VERBOSITY` and `GRPC_TRACE` environment variables that can be used to increase the amount of information +that gets printed to stderr. + +## GRPC_VERBOSITY + +`GRPC_VERBOSITY` is used to set the minimum level of log messages printed by gRPC (supported values are `DEBUG`, `INFO` and `ERROR`). If this environment variable is unset, only `ERROR` logs will be printed. + +## GRPC_TRACE + +`GRPC_TRACE` can be used to enable extra logging for some internal gRPC components. Enabling the right traces can be invaluable +for diagnosing for what is going wrong when things aren't working as intended. Possible values for `GRPC_TRACE` are listed in [Environment Variables Overview](doc/environment_variables.md). +Multiple traces can be enabled at once (use comma as separator). + +``` +# Enable debug logs for an application +GRPC_VERBOSITY=debug ./helloworld_application_using_grpc +``` + +``` +# Print information about channel state changes +GRPC_VERBOSITY=debug GRPC_TRACE=connectivity_state ./helloworld_application_using_grpc +``` + +``` +# Print info from 3 different tracers, including tracing logs with log level DEBUG +GRPC_VERBOSITY=debug GRPC_TRACE=channel,subchannel,call_stream ./helloworld_application_using_grpc +``` + +Please note that the `GRPC_TRACE` environment variable has nothing to do with gRPC's "tracing" feature (= tracing RPCs in +microservice environment to gain insight about how requests are processed by deployment), it is merely used to enable printing +of extra logs. diff --git a/doc/environment_variables.md b/doc/environment_variables.md new file mode 100644 index 000000000..f2a32294c --- /dev/null +++ b/doc/environment_variables.md @@ -0,0 +1,58 @@ +# grpc-js environment variables + +`@grpc/grpc-js` exposes some configuration as environment variables that +can be set. + +*For the legacy `grpc` library, the environment variables are documented +[in the main gRPC repository](https://github.com/grpc/grpc/blob/master/doc/environment_variables.md)* + +* grpc_proxy, https_proxy, http_proxy + The URI of the proxy to use for HTTP CONNECT support. These variables are + checked in order, and the first one that has a value is used. + +* no_grpc_proxy, no_proxy + A comma separated list of hostnames to connect to without using a proxy even + if a proxy is set. These variables are checked in order, and the first one + that has a value is used. + +* GRPC_SSL_CIPHER_SUITES + A colon separated list of cipher suites to use with OpenSSL + Defaults to the defaults for Node.js + +* GRPC_DEFAULT_SSL_ROOTS_FILE_PATH + PEM file to load SSL roots from + +* GRPC_NODE_TRACE, GRPC_TRACE + A comma separated list of tracers that provide additional insight into how + grpc-js is processing requests via debug logs. Available tracers include: + - `call_stream` - Traces client request internals + - `channel` - Traces channel events + - `connectivity_state` - Traces channel connectivity state changes + - `dns_resolver` - Traces DNS resolution + - `pick_first` - Traces the pick first load balancing policy + - `proxy` - Traces proxy operations + - `resolving_load_balancer` - Traces the resolving load balancer + - `round_robin` - Traces the round robin load balancing policy + - `server` - Traces high-level server events + - `server_call` - Traces server handling of individual requests + - `subchannel` - Traces subchannel connectivity state and errors + - `subchannel_refcount` - Traces subchannel refcount changes + + The following tracers are added by the `@grpc/grpc-js-xds` library: + - `cds_balancer` - Traces the CDS load balancing policy + - `eds_balancer` - Traces the EDS load balancing policy + - `priority` - Traces the priority load balancing policy + - `weighted_target` - Traces the weighted target load balancing policy + - `xds_client` - Traces the xDS Client + - `xds_cluster_manager` - Traces the xDS cluster manager load balancing policy + - `xds_resolver` - Traces the xDS name resolver + + 'all' can additionally be used to turn all traces on. + Individual traces can be disabled by prefixing them with '-'. + +* GRPC_NODE_VERBOSITY, GRPC_VERBOSITY + Default gRPC logging verbosity - one of: + - DEBUG - log all gRPC messages + - INFO - log INFO and ERROR message + - ERROR - log only errors (default) + - NONE - won't log any \ No newline at end of file From f289c343b3393aad73795c0657415f4160bcdcb5 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Wed, 23 Jun 2021 16:42:39 +0100 Subject: [PATCH 026/694] Avoid unused definition imports from proto-loader Since proto files don't always contain all types of definition, it was possible to get into a state where generated code contained unused imports which caused TS errors. This change makes those imports conditional on the existence of the corresponding definitions in the proto file. Co-authored-by: Austin Puri Co-authored-by: Joe Porpeglia Signed-off-by: Mike Lewis --- .../bin/proto-loader-gen-types.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 82a13a123..fedaadc38 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -579,6 +579,34 @@ function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protob generateServiceDefinitionInterface(formatter, serviceType); } +function containsDefinition(definitionType: typeof Protobuf.Type | typeof Protobuf.Enum, namespace: Protobuf.NamespaceBase): boolean { + for (const nested of namespace.nestedArray.sort(compareName)) { + if (nested instanceof definitionType) { + return true; + } else if (isNamespaceBase(nested) && !(nested instanceof Protobuf.Type) && !(nested instanceof Protobuf.Enum)) { + return containsDefinition(definitionType, nested); + } + } + + return false; +} + +function generateDefinitionImports(formatter: TextFormatter, namespace: Protobuf.NamespaceBase, options: GeneratorOptions) { + const imports = []; + + if (containsDefinition(Protobuf.Enum, namespace)) { + imports.push('EnumTypeDefinition'); + } + + if (containsDefinition(Protobuf.Type, namespace)) { + imports.push('MessageTypeDefinition'); + } + + if (imports.length) { + formatter.writeLine(`import type { ${imports.join(', ')} } from '@grpc/proto-loader';`); + } +} + function generateServiceImports(formatter: TextFormatter, namespace: Protobuf.NamespaceBase, options: GeneratorOptions) { for (const nested of namespace.nestedArray.sort(compareName)) { if (nested instanceof Protobuf.Service) { @@ -617,7 +645,7 @@ function generateLoadedDefinitionTypes(formatter: TextFormatter, namespace: Prot function generateRootFile(formatter: TextFormatter, root: Protobuf.Root, options: GeneratorOptions) { formatter.writeLine(`import type * as grpc from '${options.grpcLib}';`); - formatter.writeLine("import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader';"); + generateDefinitionImports(formatter, root, options); formatter.writeLine(''); generateServiceImports(formatter, root, options); From 61e64a3c4f684e1671fd27b9e47ca9755e042f22 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Wed, 23 Jun 2021 17:55:38 +0100 Subject: [PATCH 027/694] Update golden-generated in proto-loader Signed-off-by: Mike Lewis --- packages/proto-loader/golden-generated/echo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/golden-generated/echo.ts b/packages/proto-loader/golden-generated/echo.ts index f257a40e4..600e2864c 100644 --- a/packages/proto-loader/golden-generated/echo.ts +++ b/packages/proto-loader/golden-generated/echo.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; import type { OperationsClient as _google_longrunning_OperationsClient, OperationsDefinition as _google_longrunning_OperationsDefinition } from './google/longrunning/Operations'; import type { EchoClient as _google_showcase_v1beta1_EchoClient, EchoDefinition as _google_showcase_v1beta1_EchoDefinition } from './google/showcase/v1beta1/Echo'; From 12b2412356e287700c5e38e0cb0d120a820f2c19 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 23 Jun 2021 12:46:53 -0700 Subject: [PATCH 028/694] proto-loader: Bump to version 0.6.3 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 0d9ff4437..cbda6680d 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.2", + "version": "0.6.3", "author": "Google Inc.", "contributors": [ { From 031ae9472e7ce0592dd34f344a50c6cded861203 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 24 Jun 2021 09:40:30 -0700 Subject: [PATCH 029/694] grpc-js: Refactor code to eliminate runtime dependency cycles --- packages/grpc-js/package.json | 3 +- packages/grpc-js/src/channel.ts | 11 +--- packages/grpc-js/src/client.ts | 3 +- packages/grpc-js/src/connectivity-state.ts | 24 +++++++ packages/grpc-js/src/experimental.ts | 2 +- packages/grpc-js/src/http_proxy.ts | 4 +- packages/grpc-js/src/index.ts | 15 +++-- .../src/load-balancer-child-handler.ts | 9 +-- .../grpc-js/src/load-balancer-pick-first.ts | 14 +++-- .../grpc-js/src/load-balancer-round-robin.ts | 12 ++-- packages/grpc-js/src/load-balancer.ts | 36 ++++++----- packages/grpc-js/src/resolver-dns.ts | 2 +- packages/grpc-js/src/resolver-uds.ts | 2 +- packages/grpc-js/src/resolver.ts | 9 +-- .../grpc-js/src/resolving-load-balancer.ts | 8 +-- packages/grpc-js/src/server.ts | 4 +- packages/grpc-js/src/subchannel-address.ts | 62 +++++++++++++++++++ packages/grpc-js/src/subchannel-pool.ts | 6 +- packages/grpc-js/src/subchannel.ts | 49 +-------------- packages/grpc-js/test/test-client.ts | 2 +- packages/grpc-js/test/test-resolver.ts | 7 ++- 21 files changed, 167 insertions(+), 117 deletions(-) create mode 100644 packages/grpc-js/src/connectivity-state.ts create mode 100644 packages/grpc-js/src/subchannel-address.ts diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 6a027d391..3733e06fc 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -29,6 +29,7 @@ "gulp": "^4.0.2", "gulp-mocha": "^6.0.0", "lodash": "^4.17.4", + "madge": "^5.0.1", "mocha-jenkins-reporter": "^0.4.1", "ncp": "^2.0.0", "pify": "^4.0.1", @@ -53,7 +54,7 @@ "check": "gts check src/**/*.ts", "fix": "gts fix src/*.ts", "pretest": "npm run compile", - "posttest": "npm run check" + "posttest": "npm run check && madge -c ./build/src" }, "dependencies": { "@types/node": ">=12.12.47" diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index cd2fc94a2..58726ccc7 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -35,20 +35,13 @@ import { DeadlineFilterFactory } from './deadline-filter'; import { CompressionFilterFactory } from './compression-filter'; import { CallConfig, ConfigSelector, getDefaultAuthority, mapUriDefaultScheme } from './resolver'; import { trace, log } from './logging'; -import { SubchannelAddress } from './subchannel'; +import { SubchannelAddress } from "./subchannel-address"; import { MaxMessageSizeFilterFactory } from './max-message-size-filter'; import { mapProxyName } from './http_proxy'; import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; import { SurfaceCall } from './call'; - -export enum ConnectivityState { - IDLE, - CONNECTING, - READY, - TRANSIENT_FAILURE, - SHUTDOWN, -} +import { ConnectivityState } from './connectivity-state'; /** * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index 5f78ffe59..7a2199b02 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -30,7 +30,8 @@ import { } from './call'; import { CallCredentials } from './call-credentials'; import { Deadline, StatusObject } from './call-stream'; -import { Channel, ConnectivityState, ChannelImplementation } from './channel'; +import { Channel, ChannelImplementation } from './channel'; +import { ConnectivityState } from "./connectivity-state"; import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; import { Status } from './constants'; diff --git a/packages/grpc-js/src/connectivity-state.ts b/packages/grpc-js/src/connectivity-state.ts new file mode 100644 index 000000000..8e1812274 --- /dev/null +++ b/packages/grpc-js/src/connectivity-state.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2021 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. + * + */ + +export enum ConnectivityState { + IDLE, + CONNECTING, + READY, + TRANSIENT_FAILURE, + SHUTDOWN +} diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index cf84ba6b8..438ca71a8 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -4,7 +4,7 @@ export { GrpcUri, uriToString } from './uri-parser'; export { ServiceConfig, Duration } from './service-config'; export { BackoffTimeout } from './backoff-timeout'; export { LoadBalancer, LoadBalancingConfig, ChannelControlHelper, registerLoadBalancerType, getFirstUsableConfig, validateLoadBalancingConfig } from './load-balancer'; -export { SubchannelAddress, subchannelAddressToString } from './subchannel'; +export { SubchannelAddress, subchannelAddressToString } from './subchannel-address'; export { ChildLoadBalancerHandler } from './load-balancer-child-handler'; export { Picker, UnavailablePicker, QueuePicker, PickResult, PickArgs, PickResultType } from './picker'; export { Call as CallStream } from './call-stream'; diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index f6921378b..d62be597c 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -25,8 +25,8 @@ import * as logging from './logging'; import { SubchannelAddress, isTcpSubchannelAddress, - subchannelAddressToString, -} from './subchannel'; + subchannelAddressToString +} from "./subchannel-address"; import { ChannelOptions } from './channel-options'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { URL } from 'url'; diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 05a82e94d..d0eb2a37c 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -24,7 +24,8 @@ import { } from './call'; import { CallCredentials, OAuth2Client } from './call-credentials'; import { Deadline, StatusObject } from './call-stream'; -import { Channel, ConnectivityState, ChannelImplementation } from './channel'; +import { Channel, ChannelImplementation } from './channel'; +import { ConnectivityState } from "./connectivity-state"; import { ChannelCredentials } from './channel-credentials'; import { CallOptions, @@ -246,10 +247,14 @@ export { ChannelOptions } from './channel-options'; import * as experimental from './experimental'; export { experimental }; -import * as resolver from './resolver'; -import * as load_balancer from './load-balancer'; +import * as resolver_dns from './resolver-dns'; +import * as resolver_uds from './resolver-uds'; +import * as load_balancer_pick_first from './load-balancer-pick-first'; +import * as load_balancer_round_robin from './load-balancer-round-robin'; (() => { - resolver.registerAll(); - load_balancer.registerAll(); + resolver_dns.setup(); + resolver_uds.setup(); + load_balancer_pick_first.setup(); + load_balancer_round_robin.setup(); })(); diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 337174c0d..25cba4528 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -18,12 +18,13 @@ import { LoadBalancer, ChannelControlHelper, - createLoadBalancer, - LoadBalancingConfig + LoadBalancingConfig, + createLoadBalancer } from './load-balancer'; -import { SubchannelAddress, Subchannel } from './subchannel'; +import { Subchannel } from './subchannel'; +import { SubchannelAddress } from "./subchannel-address"; import { ChannelOptions } from './channel-options'; -import { ConnectivityState } from './channel'; +import { ConnectivityState } from "./connectivity-state"; import { Picker } from './picker'; const TYPE_NAME = 'child_load_balancer_helper'; diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 688f95562..f7e9dd917 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -18,10 +18,11 @@ import { LoadBalancer, ChannelControlHelper, - registerLoadBalancerType, - LoadBalancingConfig + LoadBalancingConfig, + registerDefaultLoadBalancerType, + registerLoadBalancerType } from './load-balancer'; -import { ConnectivityState } from './channel'; +import { ConnectivityState } from "./connectivity-state"; import { QueuePicker, Picker, @@ -33,9 +34,11 @@ import { import { Subchannel, ConnectivityStateListener, - SubchannelAddress, - subchannelAddressToString, } from './subchannel'; +import { + SubchannelAddress, + subchannelAddressToString +} from "./subchannel-address"; import * as logging from './logging'; import { LogVerbosity } from './constants'; @@ -458,4 +461,5 @@ export class PickFirstLoadBalancer implements LoadBalancer { export function setup(): void { registerLoadBalancerType(TYPE_NAME, PickFirstLoadBalancer, PickFirstLoadBalancingConfig); + registerDefaultLoadBalancerType(TYPE_NAME); } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 2159e64c7..fabd334e3 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -18,10 +18,10 @@ import { LoadBalancer, ChannelControlHelper, - registerLoadBalancerType, - LoadBalancingConfig + LoadBalancingConfig, + registerLoadBalancerType } from './load-balancer'; -import { ConnectivityState } from './channel'; +import { ConnectivityState } from "./connectivity-state"; import { QueuePicker, Picker, @@ -33,9 +33,11 @@ import { import { Subchannel, ConnectivityStateListener, - SubchannelAddress, - subchannelAddressToString, } from './subchannel'; +import { + SubchannelAddress, + subchannelAddressToString +} from "./subchannel-address"; import * as logging from './logging'; import { LogVerbosity } from './constants'; diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index 8d5c7c837..7fe0b9b3d 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -16,11 +16,10 @@ */ import { ChannelOptions } from './channel-options'; -import { Subchannel, SubchannelAddress } from './subchannel'; -import { ConnectivityState } from './channel'; +import { Subchannel } from './subchannel'; +import { SubchannelAddress } from "./subchannel-address"; +import { ConnectivityState } from "./connectivity-state"; import { Picker } from './picker'; -import * as load_balancer_pick_first from './load-balancer-pick-first'; -import * as load_balancer_round_robin from './load-balancer-round-robin'; /** * A collection of functions associated with a channel that a load balancer @@ -108,11 +107,13 @@ export interface LoadBalancingConfigConstructor { const registeredLoadBalancerTypes: { [name: string]: { - LoadBalancer: LoadBalancerConstructor, - LoadBalancingConfig: LoadBalancingConfigConstructor + LoadBalancer: LoadBalancerConstructor; + LoadBalancingConfig: LoadBalancingConfigConstructor; }; } = {}; +let defaultLoadBalancerType: string | null = null; + export function registerLoadBalancerType( typeName: string, loadBalancerType: LoadBalancerConstructor, @@ -124,6 +125,10 @@ export function registerLoadBalancerType( }; } +export function registerDefaultLoadBalancerType(typeName: string) { + defaultLoadBalancerType = typeName; +} + export function createLoadBalancer( config: LoadBalancingConfig, channelControlHelper: ChannelControlHelper @@ -140,25 +145,29 @@ export function isLoadBalancerNameRegistered(typeName: string): boolean { return typeName in registeredLoadBalancerTypes; } -export function getFirstUsableConfig(configs: LoadBalancingConfig[], defaultPickFirst?: true): LoadBalancingConfig; +export function getFirstUsableConfig(configs: LoadBalancingConfig[], fallbackTodefault?: true): LoadBalancingConfig; export function getFirstUsableConfig( configs: LoadBalancingConfig[], - defaultPickFirst: boolean = false + fallbackTodefault: boolean = false ): LoadBalancingConfig | null { for (const config of configs) { if (config.getLoadBalancerName() in registeredLoadBalancerTypes) { return config; } } - if (defaultPickFirst) { - return new load_balancer_pick_first.PickFirstLoadBalancingConfig() + if (fallbackTodefault) { + if (defaultLoadBalancerType) { + return new registeredLoadBalancerTypes[defaultLoadBalancerType]!.LoadBalancingConfig(); + } else { + return null; + } } else { return null; } } export function validateLoadBalancingConfig(obj: any): LoadBalancingConfig { - if (!(obj !== null && (typeof obj === 'object'))) { + if (!(obj !== null && (typeof obj === 'object'))) { throw new Error('Load balancing config must be an object'); } const keys = Object.keys(obj); @@ -172,8 +181,3 @@ export function validateLoadBalancingConfig(obj: any): LoadBalancingConfig { throw new Error(`Unrecognized load balancing config name ${typeName}`); } } - -export function registerAll() { - load_balancer_pick_first.setup(); - load_balancer_round_robin.setup(); -} diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 67f1f8c45..44246c779 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -28,7 +28,7 @@ import { StatusObject } from './call-stream'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { SubchannelAddress, TcpSubchannelAddress } from './subchannel'; +import { SubchannelAddress, TcpSubchannelAddress } from "./subchannel-address"; import { GrpcUri, uriToString, splitHostPort } from './uri-parser'; import { isIPv6, isIPv4 } from 'net'; import { ChannelOptions } from './channel-options'; diff --git a/packages/grpc-js/src/resolver-uds.ts b/packages/grpc-js/src/resolver-uds.ts index 40502f113..1c5a7a64c 100644 --- a/packages/grpc-js/src/resolver-uds.ts +++ b/packages/grpc-js/src/resolver-uds.ts @@ -15,7 +15,7 @@ */ import { Resolver, ResolverListener, registerResolver } from './resolver'; -import { SubchannelAddress } from './subchannel'; +import { SubchannelAddress } from "./subchannel-address"; import { GrpcUri } from './uri-parser'; import { ChannelOptions } from './channel-options'; diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index 147ace30d..5bf7df2e7 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -16,10 +16,8 @@ */ import { MethodConfig, ServiceConfig } from './service-config'; -import * as resolver_dns from './resolver-dns'; -import * as resolver_uds from './resolver-uds'; import { StatusObject } from './call-stream'; -import { SubchannelAddress } from './subchannel'; +import { SubchannelAddress } from "./subchannel-address"; import { GrpcUri, uriToString } from './uri-parser'; import { ChannelOptions } from './channel-options'; import { Metadata } from './metadata'; @@ -175,8 +173,3 @@ export function mapUriDefaultScheme(target: GrpcUri): GrpcUri | null { } return target; } - -export function registerAll() { - resolver_dns.setup(); - resolver_uds.setup(); -} diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 94dd8c4a9..9825086c1 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -18,11 +18,11 @@ import { ChannelControlHelper, LoadBalancer, - getFirstUsableConfig, - LoadBalancingConfig + LoadBalancingConfig, + getFirstUsableConfig } from './load-balancer'; import { ServiceConfig, validateServiceConfig } from './service-config'; -import { ConnectivityState } from './channel'; +import { ConnectivityState } from "./connectivity-state"; import { ConfigSelector, createResolver, Resolver } from './resolver'; import { ServiceError } from './call'; import { Picker, UnavailablePicker, QueuePicker } from './picker'; @@ -32,7 +32,7 @@ import { StatusObject } from './call-stream'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { SubchannelAddress } from './subchannel'; +import { SubchannelAddress } from "./subchannel-address"; import { GrpcUri, uriToString } from './uri-parser'; import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; import { ChannelOptions } from './channel-options'; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 255e210b6..aec149556 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -55,8 +55,8 @@ import { SubchannelAddress, TcpSubchannelAddress, isTcpSubchannelAddress, - subchannelAddressToString, -} from './subchannel'; + subchannelAddressToString +} from "./subchannel-address"; import { parseUri } from './uri-parser'; const TRACER_NAME = 'server'; diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts new file mode 100644 index 000000000..4b08d8ba1 --- /dev/null +++ b/packages/grpc-js/src/subchannel-address.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2021 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. + * + */ + +export interface TcpSubchannelAddress { + port: number; + host: string; +} + +export interface IpcSubchannelAddress { + path: string; +} +/** + * This represents a single backend address to connect to. This interface is a + * subset of net.SocketConnectOpts, i.e. the options described at + * https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener. + * Those are in turn a subset of the options that can be passed to http2.connect. + */ + +export type SubchannelAddress = TcpSubchannelAddress | IpcSubchannelAddress; + +export function isTcpSubchannelAddress( + address: SubchannelAddress +): address is TcpSubchannelAddress { + return 'port' in address; +} + +export function subchannelAddressEqual( + address1: SubchannelAddress, + address2: SubchannelAddress +): boolean { + if (isTcpSubchannelAddress(address1)) { + return ( + isTcpSubchannelAddress(address2) && + address1.host === address2.host && + address1.port === address2.port + ); + } else { + return !isTcpSubchannelAddress(address2) && address1.path === address2.path; + } +} + +export function subchannelAddressToString(address: SubchannelAddress): string { + if (isTcpSubchannelAddress(address)) { + return address.host + ':' + address.port; + } else { + return address.path; + } +} diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts index d28e3eac8..65c5f2177 100644 --- a/packages/grpc-js/src/subchannel-pool.ts +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -18,9 +18,11 @@ import { ChannelOptions, channelOptionsEqual } from './channel-options'; import { Subchannel, - SubchannelAddress, - subchannelAddressEqual, } from './subchannel'; +import { + SubchannelAddress, + subchannelAddressEqual +} from "./subchannel-address"; import { ChannelCredentials } from './channel-credentials'; import { GrpcUri, uriToString } from './uri-parser'; diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index c50a68e77..a76ea963c 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -21,7 +21,7 @@ import { Metadata } from './metadata'; import { Http2CallStream } from './call-stream'; import { ChannelOptions } from './channel-options'; import { PeerCertificate, checkServerIdentity } from 'tls'; -import { ConnectivityState } from './channel'; +import { ConnectivityState } from "./connectivity-state"; import { BackoffTimeout, BackoffOptions } from './backoff-timeout'; import { getDefaultAuthority } from './resolver'; import * as logging from './logging'; @@ -31,6 +31,7 @@ import * as net from 'net'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { ConnectionOptions } from 'tls'; import { FilterFactory, Filter } from './filter'; +import { SubchannelAddress, subchannelAddressToString } from './subchannel-address'; const clientVersion = require('../../package.json').version; @@ -82,52 +83,6 @@ function uniformRandom(min: number, max: number) { const tooManyPingsData: Buffer = Buffer.from('too_many_pings', 'ascii'); -export interface TcpSubchannelAddress { - port: number; - host: string; -} - -export interface IpcSubchannelAddress { - path: string; -} - -/** - * This represents a single backend address to connect to. This interface is a - * subset of net.SocketConnectOpts, i.e. the options described at - * https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener. - * Those are in turn a subset of the options that can be passed to http2.connect. - */ -export type SubchannelAddress = TcpSubchannelAddress | IpcSubchannelAddress; - -export function isTcpSubchannelAddress( - address: SubchannelAddress -): address is TcpSubchannelAddress { - return 'port' in address; -} - -export function subchannelAddressEqual( - address1: SubchannelAddress, - address2: SubchannelAddress -): boolean { - if (isTcpSubchannelAddress(address1)) { - return ( - isTcpSubchannelAddress(address2) && - address1.host === address2.host && - address1.port === address2.port - ); - } else { - return !isTcpSubchannelAddress(address2) && address1.path === address2.path; - } -} - -export function subchannelAddressToString(address: SubchannelAddress): string { - if (isTcpSubchannelAddress(address)) { - return address.host + ':' + address.port; - } else { - return address.path; - } -} - export class Subchannel { /** * The subchannel's current connectivity state. Invariant: `session` === `null` diff --git a/packages/grpc-js/test/test-client.ts b/packages/grpc-js/test/test-client.ts index 0d2878cbc..51af05416 100644 --- a/packages/grpc-js/test/test-client.ts +++ b/packages/grpc-js/test/test-client.ts @@ -20,7 +20,7 @@ import * as assert from 'assert'; import * as grpc from '../src'; import { Server, ServerCredentials } from '../src'; import { Client } from '../src'; -import { ConnectivityState } from '../src/channel'; +import { ConnectivityState } from "../src/connectivity-state"; const clientInsecureCreds = grpc.credentials.createInsecure(); const serverInsecureCreds = ServerCredentials.createInsecure(); diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index c4f42f6eb..976e64895 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -19,9 +19,11 @@ // tslint:disable no-any import * as assert from 'assert'; import * as resolverManager from '../src/resolver'; +import * as load_balancer_pick_first from '../src/load-balancer-pick-first'; +import * as load_balancer_round_robin from '../src/load-balancer-round-robin'; import { ServiceConfig } from '../src/service-config'; import { StatusObject } from '../src/call-stream'; -import { SubchannelAddress, isTcpSubchannelAddress } from '../src/subchannel'; +import { SubchannelAddress, isTcpSubchannelAddress } from "../src/subchannel-address"; import { parseUri, GrpcUri } from '../src/uri-parser'; describe('Name Resolver', () => { @@ -29,7 +31,8 @@ describe('Name Resolver', () => { // For some reason DNS queries sometimes take a long time on Windows this.timeout(4000); before(() => { - resolverManager.registerAll(); + load_balancer_pick_first.setup(); + load_balancer_round_robin.setup(); }); it('Should resolve localhost properly', done => { const target = resolverManager.mapUriDefaultScheme(parseUri('localhost:50051')!)!; From 32cd3504cccec9fcfee67dc87a6a9e8f66fa40f3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 28 Jun 2021 11:24:43 -0700 Subject: [PATCH 030/694] grpc-js: Add note in README about feature parity requests --- packages/grpc-js/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 4bb4da024..df44264ce 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -25,6 +25,8 @@ Documentation specifically for the `@grpc/grpc-js` package is currently not avai - Connection Keepalives - HTTP Connect support (proxies) +If you need a feature from the `grpc` package that is not provided by the `@grpc/grpc-js`, please file a feature request with that information. + This library does not directly handle `.proto` files. To use `.proto` files with this library we recommend using the `@grpc/proto-loader` package. ## Migrating from [`grpc`](https://www.npmjs.com/package/grpc) From 41e09f7d12b05d74f37e3fd30bf08a7398ad12d2 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Mon, 28 Jun 2021 19:04:58 +0100 Subject: [PATCH 031/694] Prevent early return in proto-loader containsDefinition f289c343b3393aad73795c0657415f4160bcdcb5 introduced a bug - the recursive for-loop descended into the first elements nested array and returned that value without iterating over the other members of the array. This means that the code would only work correctly when the protofile contained a definition whose name was alphabetically first amongst its siblings. This commit fixes the issue by moving the call to containsDefinition into the if statement to allow iteration to continue if containsDefinition returns false. --- packages/proto-loader/bin/proto-loader-gen-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index fedaadc38..b441d425e 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -583,8 +583,8 @@ function containsDefinition(definitionType: typeof Protobuf.Type | typeof Protob for (const nested of namespace.nestedArray.sort(compareName)) { if (nested instanceof definitionType) { return true; - } else if (isNamespaceBase(nested) && !(nested instanceof Protobuf.Type) && !(nested instanceof Protobuf.Enum)) { - return containsDefinition(definitionType, nested); + } else if (isNamespaceBase(nested) && !(nested instanceof Protobuf.Type) && !(nested instanceof Protobuf.Enum) && containsDefinition(definitionType, nested)) { + return true; } } From 6ba982548e58a89641f3377c863b263ab094101f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 28 Jun 2021 13:17:16 -0700 Subject: [PATCH 032/694] proto-loader: bump to 0.6.4 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index cbda6680d..824fa6f5e 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.3", + "version": "0.6.4", "author": "Google Inc.", "contributors": [ { From 1452ed93aa86df36e2d18093455be8bb20637a56 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 29 Jun 2021 10:04:32 -0700 Subject: [PATCH 033/694] grpc-js: Register IP resolver in index --- packages/grpc-js/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index d0eb2a37c..52c122b12 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -249,12 +249,14 @@ export { experimental }; import * as resolver_dns from './resolver-dns'; import * as resolver_uds from './resolver-uds'; +import * as resolver_ip from './resolver-ip'; import * as load_balancer_pick_first from './load-balancer-pick-first'; import * as load_balancer_round_robin from './load-balancer-round-robin'; (() => { resolver_dns.setup(); resolver_uds.setup(); + resolver_ip.setup(); load_balancer_pick_first.setup(); load_balancer_round_robin.setup(); })(); From 8605ef2ded12f4eff3dded61736f1741e075add0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 29 Jun 2021 10:43:45 -0700 Subject: [PATCH 034/694] Fix subchannel address import, resolver test setup --- packages/grpc-js/src/resolver-ip.ts | 2 +- packages/grpc-js/test/test-resolver.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/resolver-ip.ts b/packages/grpc-js/src/resolver-ip.ts index 5c9e29c46..18bbe54aa 100644 --- a/packages/grpc-js/src/resolver-ip.ts +++ b/packages/grpc-js/src/resolver-ip.ts @@ -20,7 +20,7 @@ import { ChannelOptions } from "./channel-options"; import { LogVerbosity, Status } from "./constants"; import { Metadata } from "./metadata"; import { registerResolver, Resolver, ResolverListener } from "./resolver"; -import { SubchannelAddress } from "./subchannel"; +import { SubchannelAddress } from "./subchannel-address"; import { GrpcUri, splitHostPort, uriToString } from "./uri-parser"; import * as logging from './logging'; diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index b5a4c1640..7addfa232 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -19,21 +19,23 @@ // tslint:disable no-any import * as assert from 'assert'; import * as resolverManager from '../src/resolver'; -import * as load_balancer_pick_first from '../src/load-balancer-pick-first'; -import * as load_balancer_round_robin from '../src/load-balancer-round-robin'; +import * as resolver_dns from '../src/resolver-dns'; +import * as resolver_uds from '../src/resolver-uds'; +import * as resolver_ip from '../src/resolver-ip'; import { ServiceConfig } from '../src/service-config'; import { StatusObject } from '../src/call-stream'; import { SubchannelAddress, isTcpSubchannelAddress } from "../src/subchannel-address"; import { parseUri, GrpcUri } from '../src/uri-parser'; describe('Name Resolver', () => { + before(() => { + resolver_dns.setup(); + resolver_uds.setup(); + resolver_ip.setup(); + }); describe('DNS Names', function() { // For some reason DNS queries sometimes take a long time on Windows this.timeout(4000); - before(() => { - load_balancer_pick_first.setup(); - load_balancer_round_robin.setup(); - }); it('Should resolve localhost properly', done => { const target = resolverManager.mapUriDefaultScheme(parseUri('localhost:50051')!)!; const listener: resolverManager.ResolverListener = { From 1037b23ba62b4d1205f3dd57f50e553b804d7a04 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 29 Jun 2021 14:40:24 -0700 Subject: [PATCH 035/694] grpc-js: Format source files and fix lint errors --- .../grpc-js/src/call-credentials-filter.ts | 4 +- packages/grpc-js/src/call-stream.ts | 29 ++++-- packages/grpc-js/src/call.ts | 12 ++- packages/grpc-js/src/channel-options.ts | 1 + packages/grpc-js/src/channel.ts | 94 ++++++++++++++----- packages/grpc-js/src/client-interceptors.ts | 11 ++- packages/grpc-js/src/client.ts | 33 +++---- packages/grpc-js/src/connectivity-state.ts | 2 +- packages/grpc-js/src/constants.ts | 6 +- packages/grpc-js/src/deadline-filter.ts | 4 +- packages/grpc-js/src/experimental.ts | 46 ++++++--- packages/grpc-js/src/filter.ts | 3 +- packages/grpc-js/src/http_proxy.ts | 12 ++- packages/grpc-js/src/index.ts | 9 +- .../src/load-balancer-child-handler.ts | 6 +- .../grpc-js/src/load-balancer-pick-first.ts | 24 ++--- .../grpc-js/src/load-balancer-round-robin.ts | 24 ++--- packages/grpc-js/src/load-balancer.ts | 36 ++++--- packages/grpc-js/src/logging.ts | 13 ++- packages/grpc-js/src/make-client.ts | 2 +- .../grpc-js/src/max-message-size-filter.ts | 25 +++-- packages/grpc-js/src/metadata.ts | 2 +- packages/grpc-js/src/picker.ts | 2 +- packages/grpc-js/src/resolver-dns.ts | 10 +- packages/grpc-js/src/resolver-ip.ts | 41 ++++---- packages/grpc-js/src/resolver-uds.ts | 2 +- packages/grpc-js/src/resolver.ts | 6 +- .../grpc-js/src/resolving-load-balancer.ts | 42 ++++++--- packages/grpc-js/src/server-call.ts | 6 +- packages/grpc-js/src/server.ts | 15 ++- packages/grpc-js/src/service-config.ts | 26 +++-- packages/grpc-js/src/subchannel-pool.ts | 8 +- packages/grpc-js/src/subchannel.ts | 44 ++++++--- 33 files changed, 399 insertions(+), 201 deletions(-) diff --git a/packages/grpc-js/src/call-credentials-filter.ts b/packages/grpc-js/src/call-credentials-filter.ts index 53bdba2f1..5c746297e 100644 --- a/packages/grpc-js/src/call-credentials-filter.ts +++ b/packages/grpc-js/src/call-credentials-filter.ts @@ -66,7 +66,9 @@ export class CallCredentialsFilter extends BaseFilter implements Filter { Status.INTERNAL, '"authorization" metadata cannot have multiple values' ); - return Promise.reject('"authorization" metadata cannot have multiple values'); + return Promise.reject( + '"authorization" metadata cannot have multiple values' + ); } return resultMetadata; } diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 083ebe851..87121154d 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -57,7 +57,7 @@ interface SystemError extends Error { * Should do approximately the same thing as util.getSystemErrorName but the * TypeScript types don't have that function for some reason so I just made my * own. - * @param errno + * @param errno */ function getSystemErrorName(errno: number): string { for (const [name, num] of Object.entries(os.constants.errno)) { @@ -71,9 +71,10 @@ function getSystemErrorName(errno: number): string { export type Deadline = Date | number; function getMinDeadline(deadlineList: Deadline[]): Deadline { - let minValue: number = Infinity; + let minValue = Infinity; for (const deadline of deadlineList) { - const deadlineMsecs = deadline instanceof Date ? deadline.getTime() : deadline; + const deadlineMsecs = + deadline instanceof Date ? deadline.getTime() : deadline; if (deadlineMsecs < minValue) { minValue = deadlineMsecs; } @@ -265,7 +266,10 @@ export class Http2CallStream implements Call { metadata: new Metadata(), }); }; - if (this.options.parentCall && this.options.flags & Propagate.CANCELLATION) { + if ( + this.options.parentCall && + this.options.flags & Propagate.CANCELLATION + ) { this.options.parentCall.on('cancelled', () => { this.cancelWithStatus(Status.CANCELLED, 'Cancelled by parent call'); }); @@ -464,7 +468,9 @@ export class Http2CallStream implements Call { subchannel: Subchannel, extraFilters: FilterFactory[] ): void { - this.filterStack.push(extraFilters.map(filterFactory => filterFactory.createFilter(this))); + this.filterStack.push( + extraFilters.map((filterFactory) => filterFactory.createFilter(this)) + ); if (this.finalStatus !== null) { stream.close(NGHTTP2_CANCEL); } else { @@ -548,7 +554,7 @@ export class Http2CallStream implements Call { stream.on('close', () => { /* Use process.next tick to ensure that this code happens after any * "error" event that may be emitted at about the same time, so that - * we can bubble up the error message from that event. */ + * we can bubble up the error message from that event. */ process.nextTick(() => { this.trace('HTTP/2 stream closed with code ' + stream.rstCode); /* If we have a final status with an OK status code, that means that @@ -629,7 +635,16 @@ export class Http2CallStream implements Call { * https://github.com/nodejs/node/blob/8b8620d580314050175983402dfddf2674e8e22a/lib/internal/http2/core.js#L2267 */ if (err.code !== 'ERR_HTTP2_STREAM_ERROR') { - this.trace('Node error event: message=' + err.message + ' code=' + err.code + ' errno=' + getSystemErrorName(err.errno) + ' syscall=' + err.syscall); + this.trace( + 'Node error event: message=' + + err.message + + ' code=' + + err.code + + ' errno=' + + getSystemErrorName(err.errno) + + ' syscall=' + + err.syscall + ); this.internalError = err; } }); diff --git a/packages/grpc-js/src/call.ts b/packages/grpc-js/src/call.ts index cfe37ecfb..fcc3159db 100644 --- a/packages/grpc-js/src/call.ts +++ b/packages/grpc-js/src/call.ts @@ -81,7 +81,8 @@ export function callErrorFromStatus(status: StatusObject): ServiceError { return Object.assign(new Error(message), status); } -export class ClientUnaryCallImpl extends EventEmitter +export class ClientUnaryCallImpl + extends EventEmitter implements ClientUnaryCall { public call?: InterceptingCallInterface; constructor() { @@ -97,7 +98,8 @@ export class ClientUnaryCallImpl extends EventEmitter } } -export class ClientReadableStreamImpl extends Readable +export class ClientReadableStreamImpl + extends Readable implements ClientReadableStream { public call?: InterceptingCallInterface; constructor(readonly deserialize: (chunk: Buffer) => ResponseType) { @@ -117,7 +119,8 @@ export class ClientReadableStreamImpl extends Readable } } -export class ClientWritableStreamImpl extends Writable +export class ClientWritableStreamImpl + extends Writable implements ClientWritableStream { public call?: InterceptingCallInterface; constructor(readonly serialize: (value: RequestType) => Buffer) { @@ -149,7 +152,8 @@ export class ClientWritableStreamImpl extends Writable } } -export class ClientDuplexStreamImpl extends Duplex +export class ClientDuplexStreamImpl + extends Duplex implements ClientDuplexStream { public call?: InterceptingCallInterface; constructor( diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index ebb724b0e..604fd8685 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -37,6 +37,7 @@ export interface ChannelOptions { 'grpc.http_connect_target'?: string; 'grpc.http_connect_creds'?: string; 'grpc-node.max_session_memory'?: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 58726ccc7..9200043e4 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -33,9 +33,14 @@ import { FilterStackFactory } from './filter-stack'; import { CallCredentialsFilterFactory } from './call-credentials-filter'; import { DeadlineFilterFactory } from './deadline-filter'; import { CompressionFilterFactory } from './compression-filter'; -import { CallConfig, ConfigSelector, getDefaultAuthority, mapUriDefaultScheme } from './resolver'; +import { + CallConfig, + ConfigSelector, + getDefaultAuthority, + mapUriDefaultScheme, +} from './resolver'; import { trace, log } from './logging'; -import { SubchannelAddress } from "./subchannel-address"; +import { SubchannelAddress } from './subchannel-address'; import { MaxMessageSizeFilterFactory } from './max-message-size-filter'; import { mapProxyName } from './http_proxy'; import { GrpcUri, parseUri, uriToString } from './uri-parser'; @@ -253,8 +258,8 @@ export class ChannelImplementation implements Channel { process.nextTick(() => { const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; - this.callRefTimerUnref() - for (const {callStream, callMetadata} of localQueue) { + this.callRefTimerUnref(); + for (const { callStream, callMetadata } of localQueue) { this.tryGetConfig(callStream, callMetadata); } this.configSelectionQueue = []; @@ -262,15 +267,21 @@ export class ChannelImplementation implements Channel { }, (status) => { if (this.configSelectionQueue.length > 0) { - trace(LogVerbosity.DEBUG, 'channel', 'Name resolution failed for target ' + uriToString(this.target) + ' with calls queued for config selection'); + trace( + LogVerbosity.DEBUG, + 'channel', + 'Name resolution failed for target ' + + uriToString(this.target) + + ' with calls queued for config selection' + ); } const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; this.callRefTimerUnref(); - for (const {callStream, callMetadata} of localQueue) { + for (const { callStream, callMetadata } of localQueue) { if (callMetadata.getOptions().waitForReady) { this.callRefTimerRef(); - this.configSelectionQueue.push({callStream, callMetadata}); + this.configSelectionQueue.push({ callStream, callMetadata }); } else { callStream.cancelWithStatus(status.code, status.details); } @@ -288,20 +299,38 @@ export class ChannelImplementation implements Channel { private callRefTimerRef() { // If the hasRef function does not exist, always run the code if (!this.callRefTimer.hasRef?.()) { - trace(LogVerbosity.DEBUG, 'channel', 'callRefTimer.ref | configSelectionQueue.length=' + this.configSelectionQueue.length + ' pickQueue.length=' + this.pickQueue.length); + trace( + LogVerbosity.DEBUG, + 'channel', + 'callRefTimer.ref | configSelectionQueue.length=' + + this.configSelectionQueue.length + + ' pickQueue.length=' + + this.pickQueue.length + ); this.callRefTimer.ref?.(); } } private callRefTimerUnref() { // If the hasRef function does not exist, always run the code - if ((!this.callRefTimer.hasRef) || (this.callRefTimer.hasRef())) { - trace(LogVerbosity.DEBUG, 'channel', 'callRefTimer.unref | configSelectionQueue.length=' + this.configSelectionQueue.length + ' pickQueue.length=' + this.pickQueue.length); + if (!this.callRefTimer.hasRef || this.callRefTimer.hasRef()) { + trace( + LogVerbosity.DEBUG, + 'channel', + 'callRefTimer.unref | configSelectionQueue.length=' + + this.configSelectionQueue.length + + ' pickQueue.length=' + + this.pickQueue.length + ); this.callRefTimer.unref?.(); } } - private pushPick(callStream: Http2CallStream, callMetadata: Metadata, callConfig: CallConfig) { + private pushPick( + callStream: Http2CallStream, + callMetadata: Metadata, + callConfig: CallConfig + ) { this.pickQueue.push({ callStream, callMetadata, callConfig }); this.callRefTimerRef(); } @@ -313,8 +342,15 @@ export class ChannelImplementation implements Channel { * @param callStream * @param callMetadata */ - private tryPick(callStream: Http2CallStream, callMetadata: Metadata, callConfig: CallConfig) { - const pickResult = this.currentPicker.pick({ metadata: callMetadata, extraPickInfo: callConfig.pickInformation }); + private tryPick( + callStream: Http2CallStream, + callMetadata: Metadata, + callConfig: CallConfig + ) { + const pickResult = this.currentPicker.pick({ + metadata: callMetadata, + extraPickInfo: callConfig.pickInformation, + }); trace( LogVerbosity.DEBUG, 'channel', @@ -412,7 +448,9 @@ export class ChannelImplementation implements Channel { ); callStream.cancelWithStatus( Status.INTERNAL, - `Failed to start HTTP/2 stream with error: ${(error as Error).message}` + `Failed to start HTTP/2 stream with error: ${ + (error as Error).message + }` ); } } @@ -434,7 +472,7 @@ export class ChannelImplementation implements Channel { (error: Error & { code: number }) => { // We assume the error code isn't 0 (Status.OK) callStream.cancelWithStatus( - (typeof error.code === 'number') ? error.code : Status.UNKNOWN, + typeof error.code === 'number' ? error.code : Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}` ); } @@ -492,7 +530,7 @@ export class ChannelImplementation implements Channel { const watchersCopy = this.connectivityStateWatchers.slice(); for (const watcherObject of watchersCopy) { if (newState !== watcherObject.currentState) { - if(watcherObject.timer) { + if (watcherObject.timer) { clearTimeout(watcherObject.timer); } this.removeConnectivityStateWatcher(watcherObject); @@ -513,9 +551,9 @@ export class ChannelImplementation implements Channel { * ResolvingLoadBalancer may be idle and if so it needs to be kicked * because it now has a pending request. */ this.resolvingLoadBalancer.exitIdle(); - this.configSelectionQueue.push({ + this.configSelectionQueue.push({ callStream: stream, - callMetadata: metadata + callMetadata: metadata, }); this.callRefTimerRef(); } else { @@ -523,15 +561,23 @@ export class ChannelImplementation implements Channel { if (callConfig.status === Status.OK) { if (callConfig.methodConfig.timeout) { const deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + callConfig.methodConfig.timeout.seconds); - deadline.setMilliseconds(deadline.getMilliseconds() + callConfig.methodConfig.timeout.nanos / 1_000_000); + deadline.setSeconds( + deadline.getSeconds() + callConfig.methodConfig.timeout.seconds + ); + deadline.setMilliseconds( + deadline.getMilliseconds() + + callConfig.methodConfig.timeout.nanos / 1_000_000 + ); stream.setConfigDeadline(deadline); // Refreshing the filters makes the deadline filter pick up the new deadline stream.filterStack.refresh(); } this.tryPick(stream, metadata, callConfig); } else { - stream.cancelWithStatus(callConfig.status, "Failed to route call to method " + stream.getMethod()); + stream.cancelWithStatus( + callConfig.status, + 'Failed to route call to method ' + stream.getMethod() + ); } } } @@ -569,7 +615,7 @@ export class ChannelImplementation implements Channel { throw new Error('Channel has been shut down'); } let timer = null; - if(deadline !== Infinity) { + if (deadline !== Infinity) { const deadlineDate: Date = deadline instanceof Date ? deadline : new Date(deadline); const now = new Date(); @@ -585,12 +631,12 @@ export class ChannelImplementation implements Channel { callback( new Error('Deadline passed without connectivity state change') ); - }, deadlineDate.getTime() - now.getTime()) + }, deadlineDate.getTime() - now.getTime()); } const watcherObject = { currentState, callback, - timer + timer, }; this.connectivityStateWatchers.push(watcherObject); } diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index a7cc2f878..5dfbeba14 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -348,7 +348,10 @@ class BaseInterceptingCall implements InterceptingCallInterface { try { serialized = this.methodDefinition.requestSerialize(message); } catch (e) { - this.call.cancelWithStatus(Status.INTERNAL, `Request message serialization failure: ${e.message}`); + this.call.cancelWithStatus( + Status.INTERNAL, + `Request message serialization failure: ${e.message}` + ); return; } this.call.sendMessageWithContext(context, serialized); @@ -403,7 +406,8 @@ class BaseInterceptingCall implements InterceptingCallInterface { * BaseInterceptingCall with special-cased behavior for methods with unary * responses. */ -class BaseUnaryInterceptingCall extends BaseInterceptingCall +class BaseUnaryInterceptingCall + extends BaseInterceptingCall implements InterceptingCallInterface { // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(call: Call, methodDefinition: ClientMethodDefinition) { @@ -435,7 +439,8 @@ class BaseUnaryInterceptingCall extends BaseInterceptingCall * BaseInterceptingCall with special-cased behavior for methods with streaming * responses. */ -class BaseStreamingInterceptingCall extends BaseInterceptingCall +class BaseStreamingInterceptingCall + extends BaseInterceptingCall implements InterceptingCallInterface {} function getBottomInterceptingCall( diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index d19fe7d07..ed9407cd8 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -31,7 +31,7 @@ import { import { CallCredentials } from './call-credentials'; import { Deadline, StatusObject } from './call-stream'; import { Channel, ChannelImplementation } from './channel'; -import { ConnectivityState } from "./connectivity-state"; +import { ConnectivityState } from './connectivity-state'; import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; import { Status } from './constants'; @@ -56,7 +56,9 @@ const INTERCEPTOR_SYMBOL = Symbol(); const INTERCEPTOR_PROVIDER_SYMBOL = Symbol(); const CALL_INVOCATION_TRANSFORMER_SYMBOL = Symbol(); -function isFunction(arg: Metadata | CallOptions | UnaryCallback | undefined): arg is UnaryCallback{ +function isFunction( + arg: Metadata | CallOptions | UnaryCallback | undefined +): arg is UnaryCallback { return typeof arg === 'function'; } @@ -266,9 +268,11 @@ export class Client { options?: CallOptions | UnaryCallback, callback?: UnaryCallback ): ClientUnaryCall { - const checkedArguments = this.checkOptionalUnaryResponseArguments< - ResponseType - >(metadata, options, callback); + const checkedArguments = this.checkOptionalUnaryResponseArguments( + metadata, + options, + callback + ); const methodDefinition: ClientMethodDefinition< RequestType, ResponseType @@ -382,9 +386,11 @@ export class Client { options?: CallOptions | UnaryCallback, callback?: UnaryCallback ): ClientWritableStream { - const checkedArguments = this.checkOptionalUnaryResponseArguments< - ResponseType - >(metadata, options, callback); + const checkedArguments = this.checkOptionalUnaryResponseArguments( + metadata, + options, + callback + ); const methodDefinition: ClientMethodDefinition< RequestType, ResponseType @@ -408,9 +414,7 @@ export class Client { callProperties ) as CallProperties; } - const emitter: ClientWritableStream = callProperties.call as ClientWritableStream< - RequestType - >; + const emitter: ClientWritableStream = callProperties.call as ClientWritableStream; const interceptorArgs: InterceptorArguments = { clientInterceptors: this[INTERCEPTOR_SYMBOL], clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], @@ -532,9 +536,7 @@ export class Client { callProperties ) as CallProperties; } - const stream: ClientReadableStream = callProperties.call as ClientReadableStream< - ResponseType - >; + const stream: ClientReadableStream = callProperties.call as ClientReadableStream; const interceptorArgs: InterceptorArguments = { clientInterceptors: this[INTERCEPTOR_SYMBOL], clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], @@ -659,7 +661,7 @@ export class Client { stream.emit('metadata', metadata); }, onReceiveMessage(message: Buffer) { - stream.push(message) + stream.push(message); }, onReceiveStatus(status: StatusObject) { if (receivedStatus) { @@ -676,4 +678,3 @@ export class Client { return stream; } } - diff --git a/packages/grpc-js/src/connectivity-state.ts b/packages/grpc-js/src/connectivity-state.ts index 8e1812274..560ab9c39 100644 --- a/packages/grpc-js/src/connectivity-state.ts +++ b/packages/grpc-js/src/connectivity-state.ts @@ -20,5 +20,5 @@ export enum ConnectivityState { CONNECTING, READY, TRANSIENT_FAILURE, - SHUTDOWN + SHUTDOWN, } diff --git a/packages/grpc-js/src/constants.ts b/packages/grpc-js/src/constants.ts index 94763cfe1..865b24c94 100644 --- a/packages/grpc-js/src/constants.ts +++ b/packages/grpc-js/src/constants.ts @@ -52,7 +52,11 @@ export enum Propagate { CENSUS_TRACING_CONTEXT = 4, CANCELLATION = 8, // https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/propagation_bits.h#L43 - DEFAULTS = 0xffff | Propagate.DEADLINE | Propagate.CENSUS_STATS_CONTEXT | Propagate.CENSUS_TRACING_CONTEXT | Propagate.CANCELLATION, + DEFAULTS = 0xffff | + Propagate.DEADLINE | + Propagate.CENSUS_STATS_CONTEXT | + Propagate.CENSUS_TRACING_CONTEXT | + Propagate.CANCELLATION, } // -1 means unlimited diff --git a/packages/grpc-js/src/deadline-filter.ts b/packages/grpc-js/src/deadline-filter.ts index afc015546..7bdd764f2 100644 --- a/packages/grpc-js/src/deadline-filter.ts +++ b/packages/grpc-js/src/deadline-filter.ts @@ -42,7 +42,7 @@ function getDeadline(deadline: number) { export class DeadlineFilter extends BaseFilter implements Filter { private timer: NodeJS.Timer | null = null; - private deadline: number = Infinity; + private deadline = Infinity; constructor( private readonly channel: Channel, private readonly callStream: Call @@ -66,7 +66,7 @@ export class DeadlineFilter extends BaseFilter implements Filter { clearTimeout(this.timer); } const now: number = new Date().getTime(); - let timeout = this.deadline - now; + const timeout = this.deadline - now; if (timeout <= 0) { process.nextTick(() => { this.callStream.cancelWithStatus( diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 438ca71a8..24b795f06 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -1,12 +1,34 @@ -export { trace } from './logging'; -export { Resolver, ResolverListener, registerResolver, ConfigSelector } from './resolver'; -export { GrpcUri, uriToString } from './uri-parser'; -export { ServiceConfig, Duration } from './service-config'; -export { BackoffTimeout } from './backoff-timeout'; -export { LoadBalancer, LoadBalancingConfig, ChannelControlHelper, registerLoadBalancerType, getFirstUsableConfig, validateLoadBalancingConfig } from './load-balancer'; -export { SubchannelAddress, subchannelAddressToString } from './subchannel-address'; -export { ChildLoadBalancerHandler } from './load-balancer-child-handler'; -export { Picker, UnavailablePicker, QueuePicker, PickResult, PickArgs, PickResultType } from './picker'; -export { Call as CallStream } from './call-stream'; -export { Filter, BaseFilter, FilterFactory } from './filter'; -export { FilterStackFactory } from './filter-stack'; \ No newline at end of file +export { trace } from './logging'; +export { + Resolver, + ResolverListener, + registerResolver, + ConfigSelector, +} from './resolver'; +export { GrpcUri, uriToString } from './uri-parser'; +export { ServiceConfig, Duration } from './service-config'; +export { BackoffTimeout } from './backoff-timeout'; +export { + LoadBalancer, + LoadBalancingConfig, + ChannelControlHelper, + registerLoadBalancerType, + getFirstUsableConfig, + validateLoadBalancingConfig, +} from './load-balancer'; +export { + SubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; +export { ChildLoadBalancerHandler } from './load-balancer-child-handler'; +export { + Picker, + UnavailablePicker, + QueuePicker, + PickResult, + PickArgs, + PickResultType, +} from './picker'; +export { Call as CallStream } from './call-stream'; +export { Filter, BaseFilter, FilterFactory } from './filter'; +export { FilterStackFactory } from './filter-stack'; diff --git a/packages/grpc-js/src/filter.ts b/packages/grpc-js/src/filter.ts index eb67bd32e..8475a0a5a 100644 --- a/packages/grpc-js/src/filter.ts +++ b/packages/grpc-js/src/filter.ts @@ -57,8 +57,7 @@ export abstract class BaseFilter implements Filter { return status; } - refresh(): void { - } + refresh(): void {} } export interface FilterFactory { diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index d62be597c..149aa3648 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -25,8 +25,8 @@ import * as logging from './logging'; import { SubchannelAddress, isTcpSubchannelAddress, - subchannelAddressToString -} from "./subchannel-address"; + subchannelAddressToString, +} from './subchannel-address'; import { ChannelOptions } from './channel-options'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { URL } from 'url'; @@ -93,7 +93,7 @@ function getProxyInfo(): ProxyInfo { port = '80'; } const result: ProxyInfo = { - address: `${hostname}:${port}` + address: `${hostname}:${port}`, }; if (userCred) { result.creds = userCred; @@ -147,7 +147,9 @@ export function mapProxyName( const serverHost = hostPort.host; for (const host of getNoProxyHostList()) { if (host === serverHost) { - trace('Not using proxy for target in no_proxy list: ' + uriToString(target)); + trace( + 'Not using proxy for target in no_proxy list: ' + uriToString(target) + ); return noProxyResult; } } @@ -226,7 +228,7 @@ export function getProxiedConnection( const targetPath = getDefaultAuthority(parsedTarget); const hostPort = splitHostPort(targetPath); const remoteHost = hostPort?.host ?? targetPath; - + const cts = tls.connect( { host: remoteHost, diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 52c122b12..0730a26eb 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -25,7 +25,7 @@ import { import { CallCredentials, OAuth2Client } from './call-credentials'; import { Deadline, StatusObject } from './call-stream'; import { Channel, ChannelImplementation } from './channel'; -import { ConnectivityState } from "./connectivity-state"; +import { ConnectivityState } from './connectivity-state'; import { ChannelCredentials } from './channel-credentials'; import { CallOptions, @@ -183,7 +183,12 @@ export { /**** Server ****/ -export { handleBidiStreamingCall, handleServerStreamingCall, handleUnaryCall, handleClientStreamingCall }; +export { + handleBidiStreamingCall, + handleServerStreamingCall, + handleUnaryCall, + handleClientStreamingCall, +}; /* eslint-disable @typescript-eslint/no-explicit-any */ export type Call = diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 25cba4528..8fae7b8a6 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -19,12 +19,12 @@ import { LoadBalancer, ChannelControlHelper, LoadBalancingConfig, - createLoadBalancer + createLoadBalancer, } from './load-balancer'; import { Subchannel } from './subchannel'; -import { SubchannelAddress } from "./subchannel-address"; +import { SubchannelAddress } from './subchannel-address'; import { ChannelOptions } from './channel-options'; -import { ConnectivityState } from "./connectivity-state"; +import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; const TYPE_NAME = 'child_load_balancer_helper'; diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index f7e9dd917..65179b163 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -19,10 +19,10 @@ import { LoadBalancer, ChannelControlHelper, LoadBalancingConfig, - registerDefaultLoadBalancerType, - registerLoadBalancerType + registerDefaultLoadBalancerType, + registerLoadBalancerType, } from './load-balancer'; -import { ConnectivityState } from "./connectivity-state"; +import { ConnectivityState } from './connectivity-state'; import { QueuePicker, Picker, @@ -31,14 +31,11 @@ import { PickResultType, UnavailablePicker, } from './picker'; -import { - Subchannel, - ConnectivityStateListener, -} from './subchannel'; +import { Subchannel, ConnectivityStateListener } from './subchannel'; import { SubchannelAddress, - subchannelAddressToString -} from "./subchannel-address"; + subchannelAddressToString, +} from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; @@ -65,10 +62,11 @@ export class PickFirstLoadBalancingConfig implements LoadBalancingConfig { toJsonObject(): object { return { - [TYPE_NAME]: {} + [TYPE_NAME]: {}, }; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any static createFromJson(obj: any) { return new PickFirstLoadBalancingConfig(); } @@ -460,6 +458,10 @@ export class PickFirstLoadBalancer implements LoadBalancer { } export function setup(): void { - registerLoadBalancerType(TYPE_NAME, PickFirstLoadBalancer, PickFirstLoadBalancingConfig); + registerLoadBalancerType( + TYPE_NAME, + PickFirstLoadBalancer, + PickFirstLoadBalancingConfig + ); registerDefaultLoadBalancerType(TYPE_NAME); } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index fabd334e3..56703397f 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -19,9 +19,9 @@ import { LoadBalancer, ChannelControlHelper, LoadBalancingConfig, - registerLoadBalancerType + registerLoadBalancerType, } from './load-balancer'; -import { ConnectivityState } from "./connectivity-state"; +import { ConnectivityState } from './connectivity-state'; import { QueuePicker, Picker, @@ -30,14 +30,11 @@ import { PickResultType, UnavailablePicker, } from './picker'; -import { - Subchannel, - ConnectivityStateListener, -} from './subchannel'; +import { Subchannel, ConnectivityStateListener } from './subchannel'; import { SubchannelAddress, - subchannelAddressToString -} from "./subchannel-address"; + subchannelAddressToString, +} from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; @@ -58,10 +55,11 @@ class RoundRobinLoadBalancingConfig implements LoadBalancingConfig { toJsonObject(): object { return { - [TYPE_NAME]: {} + [TYPE_NAME]: {}, }; } + // eslint-disable-next-line @typescript-eslint/no-explicit-any static createFromJson(obj: any) { return new RoundRobinLoadBalancingConfig(); } @@ -130,7 +128,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer { this.subchannelStateCounts[previousState] -= 1; this.subchannelStateCounts[newState] += 1; this.calculateAndUpdateState(); - + if ( newState === ConnectivityState.TRANSIENT_FAILURE || newState === ConnectivityState.IDLE @@ -249,5 +247,9 @@ export class RoundRobinLoadBalancer implements LoadBalancer { } export function setup() { - registerLoadBalancerType(TYPE_NAME, RoundRobinLoadBalancer, RoundRobinLoadBalancingConfig); + registerLoadBalancerType( + TYPE_NAME, + RoundRobinLoadBalancer, + RoundRobinLoadBalancingConfig + ); } diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index 7fe0b9b3d..f509f73eb 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -17,8 +17,8 @@ import { ChannelOptions } from './channel-options'; import { Subchannel } from './subchannel'; -import { SubchannelAddress } from "./subchannel-address"; -import { ConnectivityState } from "./connectivity-state"; +import { SubchannelAddress } from './subchannel-address'; +import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; /** @@ -101,7 +101,9 @@ export interface LoadBalancingConfig { } export interface LoadBalancingConfigConstructor { - new(...args: any): LoadBalancingConfig; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (...args: any): LoadBalancingConfig; + // eslint-disable-next-line @typescript-eslint/no-explicit-any createFromJson(obj: any): LoadBalancingConfig; } @@ -121,7 +123,7 @@ export function registerLoadBalancerType( ) { registeredLoadBalancerTypes[typeName] = { LoadBalancer: loadBalancerType, - LoadBalancingConfig: loadBalancingConfigType + LoadBalancingConfig: loadBalancingConfigType, }; } @@ -135,7 +137,9 @@ export function createLoadBalancer( ): LoadBalancer | null { const typeName = config.getLoadBalancerName(); if (typeName in registeredLoadBalancerTypes) { - return new registeredLoadBalancerTypes[typeName].LoadBalancer(channelControlHelper); + return new registeredLoadBalancerTypes[typeName].LoadBalancer( + channelControlHelper + ); } else { return null; } @@ -145,10 +149,13 @@ export function isLoadBalancerNameRegistered(typeName: string): boolean { return typeName in registeredLoadBalancerTypes; } -export function getFirstUsableConfig(configs: LoadBalancingConfig[], fallbackTodefault?: true): LoadBalancingConfig; export function getFirstUsableConfig( configs: LoadBalancingConfig[], - fallbackTodefault: boolean = false + fallbackTodefault?: true +): LoadBalancingConfig; +export function getFirstUsableConfig( + configs: LoadBalancingConfig[], + fallbackTodefault = false ): LoadBalancingConfig | null { for (const config of configs) { if (config.getLoadBalancerName() in registeredLoadBalancerTypes) { @@ -157,7 +164,9 @@ export function getFirstUsableConfig( } if (fallbackTodefault) { if (defaultLoadBalancerType) { - return new registeredLoadBalancerTypes[defaultLoadBalancerType]!.LoadBalancingConfig(); + return new registeredLoadBalancerTypes[ + defaultLoadBalancerType + ]!.LoadBalancingConfig(); } else { return null; } @@ -166,17 +175,22 @@ export function getFirstUsableConfig( } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function validateLoadBalancingConfig(obj: any): LoadBalancingConfig { - if (!(obj !== null && (typeof obj === 'object'))) { + if (!(obj !== null && typeof obj === 'object')) { throw new Error('Load balancing config must be an object'); } const keys = Object.keys(obj); if (keys.length !== 1) { - throw new Error('Provided load balancing config has multiple conflicting entries'); + throw new Error( + 'Provided load balancing config has multiple conflicting entries' + ); } const typeName = keys[0]; if (typeName in registeredLoadBalancerTypes) { - return registeredLoadBalancerTypes[typeName].LoadBalancingConfig.createFromJson(obj[typeName]); + return registeredLoadBalancerTypes[ + typeName + ].LoadBalancingConfig.createFromJson(obj[typeName]); } else { throw new Error(`Unrecognized load balancing config name ${typeName}`); } diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts index 71683dbf7..352496240 100644 --- a/packages/grpc-js/src/logging.ts +++ b/packages/grpc-js/src/logging.ts @@ -20,7 +20,8 @@ import { LogVerbosity } from './constants'; let _logger: Partial = console; let _logVerbosity: LogVerbosity = LogVerbosity.ERROR; -const verbosityString = process.env.GRPC_NODE_VERBOSITY ?? process.env.GRPC_VERBOSITY ?? ''; +const verbosityString = + process.env.GRPC_NODE_VERBOSITY ?? process.env.GRPC_VERBOSITY ?? ''; switch (verbosityString.toUpperCase()) { case 'DEBUG': @@ -58,14 +59,15 @@ export const log = (severity: LogVerbosity, ...args: any[]): void => { } }; -const tracersString = process.env.GRPC_NODE_TRACE ?? process.env.GRPC_TRACE ?? ''; +const tracersString = + process.env.GRPC_NODE_TRACE ?? process.env.GRPC_TRACE ?? ''; const enabledTracers = new Set(); const disabledTracers = new Set(); for (const tracerName of tracersString.split(',')) { if (tracerName.startsWith('-')) { disabledTracers.add(tracerName.substring(1)); } else { - enabledTracers.add(tracerName) + enabledTracers.add(tracerName); } } const allEnabled = enabledTracers.has('all'); @@ -75,7 +77,10 @@ export function trace( tracer: string, text: string ): void { - if (!disabledTracers.has(tracer) && (allEnabled || enabledTracers.has(tracer))) { + if ( + !disabledTracers.has(tracer) && + (allEnabled || enabledTracers.has(tracer)) + ) { log(severity, new Date().toISOString() + ' | ' + tracer + ' | ' + text); } } diff --git a/packages/grpc-js/src/make-client.ts b/packages/grpc-js/src/make-client.ts index a6cb91007..b8ddda29f 100644 --- a/packages/grpc-js/src/make-client.ts +++ b/packages/grpc-js/src/make-client.ts @@ -98,7 +98,7 @@ export interface ServiceClientConstructor { * keys. * @param key key for check, string. */ -function isPrototypePolluted(key: string): Boolean { +function isPrototypePolluted(key: string): boolean { return ['__proto__', 'prototype', 'constructor'].includes(key); } diff --git a/packages/grpc-js/src/max-message-size-filter.ts b/packages/grpc-js/src/max-message-size-filter.ts index f820c02e6..9a3bc9c0a 100644 --- a/packages/grpc-js/src/max-message-size-filter.ts +++ b/packages/grpc-js/src/max-message-size-filter.ts @@ -15,10 +15,14 @@ * */ -import { BaseFilter, Filter, FilterFactory } from "./filter"; -import { Call, WriteObject } from "./call-stream"; -import { Status, DEFAULT_MAX_SEND_MESSAGE_LENGTH, DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH } from "./constants"; -import { ChannelOptions } from "./channel-options"; +import { BaseFilter, Filter, FilterFactory } from './filter'; +import { Call, WriteObject } from './call-stream'; +import { + Status, + DEFAULT_MAX_SEND_MESSAGE_LENGTH, + DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, +} from './constants'; +import { ChannelOptions } from './channel-options'; export class MaxMessageSizeFilter extends BaseFilter implements Filter { private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; @@ -44,7 +48,10 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { } else { const concreteMessage = await message; if (concreteMessage.message.length > this.maxSendMessageSize) { - this.callStream.cancelWithStatus(Status.RESOURCE_EXHAUSTED, `Sent message larger than max (${concreteMessage.message.length} vs. ${this.maxSendMessageSize})`); + this.callStream.cancelWithStatus( + Status.RESOURCE_EXHAUSTED, + `Sent message larger than max (${concreteMessage.message.length} vs. ${this.maxSendMessageSize})` + ); return Promise.reject('Message too large'); } else { return concreteMessage; @@ -60,7 +67,10 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { } else { const concreteMessage = await message; if (concreteMessage.length > this.maxReceiveMessageSize) { - this.callStream.cancelWithStatus(Status.RESOURCE_EXHAUSTED, `Received message larger than max (${concreteMessage.length} vs. ${this.maxReceiveMessageSize})`); + this.callStream.cancelWithStatus( + Status.RESOURCE_EXHAUSTED, + `Received message larger than max (${concreteMessage.length} vs. ${this.maxReceiveMessageSize})` + ); return Promise.reject('Message too large'); } else { return concreteMessage; @@ -69,7 +79,8 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { } } -export class MaxMessageSizeFilterFactory implements FilterFactory { +export class MaxMessageSizeFilterFactory + implements FilterFactory { constructor(private readonly options: ChannelOptions) {} createFilter(callStream: Call): MaxMessageSizeFilter { diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts index bc66012ce..04db642ef 100644 --- a/packages/grpc-js/src/metadata.ts +++ b/packages/grpc-js/src/metadata.ts @@ -247,7 +247,7 @@ export class Metadata { * representation of the metadata map. */ toJSON() { - const result: {[key: string]: MetadataValue[]} = {}; + const result: { [key: string]: MetadataValue[] } = {}; for (const [key, values] of this.internalRepr.entries()) { result[key] = values; } diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index 25929884b..7aeed89b3 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -85,7 +85,7 @@ export interface DropCallPickResult extends PickResult { export interface PickArgs { metadata: Metadata; - extraPickInfo: {[key: string]: string}; + extraPickInfo: { [key: string]: string }; } /** diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 44246c779..9077228b8 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -28,7 +28,7 @@ import { StatusObject } from './call-stream'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { SubchannelAddress, TcpSubchannelAddress } from "./subchannel-address"; +import { SubchannelAddress, TcpSubchannelAddress } from './subchannel-address'; import { GrpcUri, uriToString, splitHostPort } from './uri-parser'; import { isIPv6, isIPv4 } from 'net'; import { ChannelOptions } from './channel-options'; @@ -129,7 +129,13 @@ class DnsResolver implements Resolver { if (this.ipResult !== null) { trace('Returning IP address for target ' + uriToString(this.target)); setImmediate(() => { - this.listener.onSuccessfulResolution(this.ipResult!, null, null, null, {}); + this.listener.onSuccessfulResolution( + this.ipResult!, + null, + null, + null, + {} + ); }); return; } diff --git a/packages/grpc-js/src/resolver-ip.ts b/packages/grpc-js/src/resolver-ip.ts index 18bbe54aa..24efc3fdc 100644 --- a/packages/grpc-js/src/resolver-ip.ts +++ b/packages/grpc-js/src/resolver-ip.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { isIPv4, isIPv6 } from "net"; -import { StatusObject } from "./call-stream"; -import { ChannelOptions } from "./channel-options"; -import { LogVerbosity, Status } from "./constants"; -import { Metadata } from "./metadata"; -import { registerResolver, Resolver, ResolverListener } from "./resolver"; -import { SubchannelAddress } from "./subchannel-address"; -import { GrpcUri, splitHostPort, uriToString } from "./uri-parser"; +import { isIPv4, isIPv6 } from 'net'; +import { StatusObject } from './call-stream'; +import { ChannelOptions } from './channel-options'; +import { LogVerbosity, Status } from './constants'; +import { Metadata } from './metadata'; +import { registerResolver, Resolver, ResolverListener } from './resolver'; +import { SubchannelAddress } from './subchannel-address'; +import { GrpcUri, splitHostPort, uriToString } from './uri-parser'; import * as logging from './logging'; const TRACER_NAME = 'ip_resolver'; @@ -52,7 +52,7 @@ class IpResolver implements Resolver { this.error = { code: Status.UNAVAILABLE, details: `Unrecognized scheme ${target.scheme} in IP resolver`, - metadata: new Metadata() + metadata: new Metadata(), }; return; } @@ -63,21 +63,24 @@ class IpResolver implements Resolver { this.error = { code: Status.UNAVAILABLE, details: `Failed to parse ${target.scheme} address ${path}`, - metadata: new Metadata() + metadata: new Metadata(), }; return; } - if ((target.scheme === IPV4_SCHEME && !isIPv4(hostPort.host)) || (target.scheme === IPV6_SCHEME && !isIPv6(hostPort.host))) { + if ( + (target.scheme === IPV4_SCHEME && !isIPv4(hostPort.host)) || + (target.scheme === IPV6_SCHEME && !isIPv6(hostPort.host)) + ) { this.error = { code: Status.UNAVAILABLE, details: `Failed to parse ${target.scheme} address ${path}`, - metadata: new Metadata() + metadata: new Metadata(), }; return; } addresses.push({ host: hostPort.host, - port: hostPort.port ?? DEFAULT_PORT + port: hostPort.port ?? DEFAULT_PORT, }); } this.addresses = addresses; @@ -86,9 +89,15 @@ class IpResolver implements Resolver { updateResolution(): void { process.nextTick(() => { if (this.error) { - this.listener.onError(this.error) + this.listener.onError(this.error); } else { - this.listener.onSuccessfulResolution(this.addresses, null, null, null, {}); + this.listener.onSuccessfulResolution( + this.addresses, + null, + null, + null, + {} + ); } }); } @@ -104,4 +113,4 @@ class IpResolver implements Resolver { export function setup() { registerResolver(IPV4_SCHEME, IpResolver); registerResolver(IPV6_SCHEME, IpResolver); -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/resolver-uds.ts b/packages/grpc-js/src/resolver-uds.ts index 1c5a7a64c..24095ec29 100644 --- a/packages/grpc-js/src/resolver-uds.ts +++ b/packages/grpc-js/src/resolver-uds.ts @@ -15,7 +15,7 @@ */ import { Resolver, ResolverListener, registerResolver } from './resolver'; -import { SubchannelAddress } from "./subchannel-address"; +import { SubchannelAddress } from './subchannel-address'; import { GrpcUri } from './uri-parser'; import { ChannelOptions } from './channel-options'; diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index 5bf7df2e7..59c5400f1 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -17,7 +17,7 @@ import { MethodConfig, ServiceConfig } from './service-config'; import { StatusObject } from './call-stream'; -import { SubchannelAddress } from "./subchannel-address"; +import { SubchannelAddress } from './subchannel-address'; import { GrpcUri, uriToString } from './uri-parser'; import { ChannelOptions } from './channel-options'; import { Metadata } from './metadata'; @@ -26,7 +26,7 @@ import { Status } from './constants'; export interface CallConfig { methodConfig: MethodConfig; onCommitted?: () => void; - pickInformation: {[key: string]: string}; + pickInformation: { [key: string]: string }; status: Status; } @@ -78,7 +78,7 @@ export interface Resolver { * called synchronously with the constructor or updateResolution. */ updateResolution(): void; - + /** * Destroy the resolver. Should be called when the owning channel shuts down. */ diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 9825086c1..5729937d1 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -19,10 +19,10 @@ import { ChannelControlHelper, LoadBalancer, LoadBalancingConfig, - getFirstUsableConfig + getFirstUsableConfig, } from './load-balancer'; import { ServiceConfig, validateServiceConfig } from './service-config'; -import { ConnectivityState } from "./connectivity-state"; +import { ConnectivityState } from './connectivity-state'; import { ConfigSelector, createResolver, Resolver } from './resolver'; import { ServiceError } from './call'; import { Picker, UnavailablePicker, QueuePicker } from './picker'; @@ -32,7 +32,7 @@ import { StatusObject } from './call-stream'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { SubchannelAddress } from "./subchannel-address"; +import { SubchannelAddress } from './subchannel-address'; import { GrpcUri, uriToString } from './uri-parser'; import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; import { ChannelOptions } from './channel-options'; @@ -46,30 +46,38 @@ function trace(text: string): void { const DEFAULT_LOAD_BALANCER_NAME = 'pick_first'; -function getDefaultConfigSelector(serviceConfig: ServiceConfig | null): ConfigSelector { - return function defaultConfigSelector(methodName: string, metadata: Metadata) { - const splitName = methodName.split('/').filter(x => x.length > 0); +function getDefaultConfigSelector( + serviceConfig: ServiceConfig | null +): ConfigSelector { + return function defaultConfigSelector( + methodName: string, + metadata: Metadata + ) { + const splitName = methodName.split('/').filter((x) => x.length > 0); const service = splitName[0] ?? ''; const method = splitName[1] ?? ''; if (serviceConfig && serviceConfig.methodConfig) { for (const methodConfig of serviceConfig.methodConfig) { for (const name of methodConfig.name) { - if (name.service === service && (name.method === undefined || name.method === method)) { + if ( + name.service === service && + (name.method === undefined || name.method === method) + ) { return { methodConfig: methodConfig, pickInformation: {}, - status: Status.OK + status: Status.OK, }; } } } } return { - methodConfig: {name: []}, + methodConfig: { name: [] }, pickInformation: {}, - status: Status.OK + status: Status.OK, }; - } + }; } export interface ResolutionCallback { @@ -201,7 +209,10 @@ export class ResolvingLoadBalancer implements LoadBalancer { } const workingConfigList = workingServiceConfig?.loadBalancingConfig ?? []; - const loadBalancingConfig = getFirstUsableConfig(workingConfigList, true); + const loadBalancingConfig = getFirstUsableConfig( + workingConfigList, + true + ); if (loadBalancingConfig === null) { // There were load balancing configs but none are supported. This counts as a resolution failure this.handleResolutionFailure({ @@ -217,8 +228,11 @@ export class ResolvingLoadBalancer implements LoadBalancer { loadBalancingConfig, attributes ); - const finalServiceConfig = workingServiceConfig ?? this.defaultServiceConfig; - this.onSuccessfulResolution(configSelector ?? getDefaultConfigSelector(finalServiceConfig)); + const finalServiceConfig = + workingServiceConfig ?? this.defaultServiceConfig; + this.onSuccessfulResolution( + configSelector ?? getDefaultConfigSelector(finalServiceConfig) + ); }, onError: (error: StatusObject) => { this.handleResolutionFailure(error); diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index aa8bd647e..4d24cdc86 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -100,7 +100,8 @@ export type ServerDuplexStream = ServerSurfaceCall & ObjectReadable & ObjectWritable & { end: (metadata?: Metadata) => void }; -export class ServerUnaryCallImpl extends EventEmitter +export class ServerUnaryCallImpl + extends EventEmitter implements ServerUnaryCall { cancelled: boolean; @@ -239,7 +240,8 @@ export class ServerWritableStreamImpl } } -export class ServerDuplexStreamImpl extends Duplex +export class ServerDuplexStreamImpl + extends Duplex implements ServerDuplexStream { cancelled: boolean; private trailingMetadata: Metadata; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index aec149556..017312219 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -55,8 +55,8 @@ import { SubchannelAddress, TcpSubchannelAddress, isTcpSubchannelAddress, - subchannelAddressToString -} from "./subchannel-address"; + subchannelAddressToString, +} from './subchannel-address'; import { parseUri } from './uri-parser'; const TRACER_NAME = 'server'; @@ -209,10 +209,7 @@ export class Server { } removeService(service: ServiceDefinition): void { - if ( - service === null || - typeof service !== 'object' - ) { + if (service === null || typeof service !== 'object') { throw new Error('removeService() requires object as argument'); } @@ -258,10 +255,12 @@ export class Server { } const serverOptions: http2.ServerOptions = { - maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER + maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, }; if ('grpc-node.max_session_memory' in this.options) { - serverOptions.maxSessionMemory = this.options['grpc-node.max_session_memory']; + serverOptions.maxSessionMemory = this.options[ + 'grpc-node.max_session_memory' + ]; } if ('grpc.max_concurrent_streams' in this.options) { serverOptions.settings = { diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index efc09f9c4..3f0a00868 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -27,7 +27,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as os from 'os'; -import { LoadBalancingConfig, validateLoadBalancingConfig } from './load-balancer'; +import { + LoadBalancingConfig, + validateLoadBalancingConfig, +} from './load-balancer'; export interface MethodConfigName { service: string; @@ -107,21 +110,30 @@ function validateMethodConfig(obj: any): MethodConfig { } if ('timeout' in obj) { if (typeof obj.timeout === 'object') { - if (!('seconds' in obj.timeout) || !(typeof obj.timeout.seconds === 'number')) { + if ( + !('seconds' in obj.timeout) || + !(typeof obj.timeout.seconds === 'number') + ) { throw new Error('Invalid method config: invalid timeout.seconds'); } - if (!('nanos' in obj.timeout) || !(typeof obj.timeout.nanos === 'number')) { + if ( + !('nanos' in obj.timeout) || + !(typeof obj.timeout.nanos === 'number') + ) { throw new Error('Invalid method config: invalid timeout.nanos'); } result.timeout = obj.timeout; } else if ( - (typeof obj.timeout === 'string') && TIMEOUT_REGEX.test(obj.timeout) + typeof obj.timeout === 'string' && + TIMEOUT_REGEX.test(obj.timeout) ) { - const timeoutParts = obj.timeout.substring(0, obj.timeout.length - 1).split('.'); + const timeoutParts = obj.timeout + .substring(0, obj.timeout.length - 1) + .split('.'); result.timeout = { seconds: timeoutParts[0] | 0, - nanos: (timeoutParts[1] ?? 0) | 0 - } + nanos: (timeoutParts[1] ?? 0) | 0, + }; } else { throw new Error('Invalid method config: invalid timeout'); } diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts index 65c5f2177..cd74cad81 100644 --- a/packages/grpc-js/src/subchannel-pool.ts +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -16,13 +16,11 @@ */ import { ChannelOptions, channelOptionsEqual } from './channel-options'; -import { - Subchannel, -} from './subchannel'; +import { Subchannel } from './subchannel'; import { SubchannelAddress, - subchannelAddressEqual -} from "./subchannel-address"; + subchannelAddressEqual, +} from './subchannel-address'; import { ChannelCredentials } from './channel-credentials'; import { GrpcUri, uriToString } from './uri-parser'; diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 7d3e6e871..ae23feb3d 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -21,7 +21,7 @@ import { Metadata } from './metadata'; import { Http2CallStream } from './call-stream'; import { ChannelOptions } from './channel-options'; import { PeerCertificate, checkServerIdentity } from 'tls'; -import { ConnectivityState } from "./connectivity-state"; +import { ConnectivityState } from './connectivity-state'; import { BackoffTimeout, BackoffOptions } from './backoff-timeout'; import { getDefaultAuthority } from './resolver'; import * as logging from './logging'; @@ -31,7 +31,10 @@ import * as net from 'net'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { ConnectionOptions } from 'tls'; import { FilterFactory, Filter } from './filter'; -import { SubchannelAddress, subchannelAddressToString } from './subchannel-address'; +import { + SubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; const clientVersion = require('../../package.json').version; @@ -138,7 +141,7 @@ export class Subchannel { /** * Indicates whether keepalive pings should be sent without any active calls */ - private keepaliveWithoutCalls: boolean = false; + private keepaliveWithoutCalls = false; /** * Tracks calls with references to this subchannel @@ -186,7 +189,8 @@ export class Subchannel { this.keepaliveTimeoutMs = options['grpc.keepalive_timeout_ms']!; } if ('grpc.keepalive_permit_without_calls' in options) { - this.keepaliveWithoutCalls = options['grpc.keepalive_permit_without_calls'] === 1; + this.keepaliveWithoutCalls = + options['grpc.keepalive_permit_without_calls'] === 1; } else { this.keepaliveWithoutCalls = false; } @@ -231,7 +235,11 @@ export class Subchannel { } private sendPing() { - logging.trace(LogVerbosity.DEBUG, 'keepalive', 'Sending ping to ' + this.subchannelAddressString); + logging.trace( + LogVerbosity.DEBUG, + 'keepalive', + 'Sending ping to ' + this.subchannelAddressString + ); this.keepaliveTimeoutId = setTimeout(() => { this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); }, this.keepaliveTimeoutMs); @@ -247,7 +255,7 @@ export class Subchannel { this.keepaliveIntervalId = setInterval(() => { this.sendPing(); }, this.keepaliveTimeMs); - this.keepaliveIntervalId.unref?.() + this.keepaliveIntervalId.unref?.(); /* Don't send a ping immediately because whatever caused us to start * sending pings should also involve some network activity. */ } @@ -265,7 +273,9 @@ export class Subchannel { this.credentials._getConnectionOptions() || {}; connectionOptions.maxSendHeaderBlockLength = Number.MAX_SAFE_INTEGER; if ('grpc-node.max_session_memory' in this.options) { - connectionOptions.maxSessionMemory = this.options['grpc-node.max_session_memory']; + connectionOptions.maxSessionMemory = this.options[ + 'grpc-node.max_session_memory' + ]; } let addressScheme = 'http://'; if ('secureContext' in connectionOptions) { @@ -387,7 +397,11 @@ export class Subchannel { ); logging.log( LogVerbosity.ERROR, - `Connection to ${uriToString(this.channelTarget)} at ${this.subchannelAddressString} rejected by server because of excess pings. Increasing ping interval to ${this.keepaliveTimeMs} ms` + `Connection to ${uriToString(this.channelTarget)} at ${ + this.subchannelAddressString + } rejected by server because of excess pings. Increasing ping interval to ${ + this.keepaliveTimeMs + } ms` ); } trace( @@ -553,10 +567,7 @@ export class Subchannel { * this subchannel, we can be sure it will never be used again. */ if (this.callRefcount === 0 && this.refcount === 0) { this.transitionToState( - [ - ConnectivityState.CONNECTING, - ConnectivityState.READY, - ], + [ConnectivityState.CONNECTING, ConnectivityState.READY], ConnectivityState.TRANSIENT_FAILURE ); } @@ -675,7 +686,14 @@ export class Subchannel { for (const header of Object.keys(headers)) { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; } - logging.trace(LogVerbosity.DEBUG, 'call_stream', 'Starting stream on subchannel ' + this.subchannelAddressString + ' with headers\n' + headersString); + logging.trace( + LogVerbosity.DEBUG, + 'call_stream', + 'Starting stream on subchannel ' + + this.subchannelAddressString + + ' with headers\n' + + headersString + ); callStream.attachHttp2Stream(http2Stream, this, extraFilterFactories); } From 728acf3853723642e6f3edf944ee3072e393e43e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 30 Jun 2021 14:52:55 -0700 Subject: [PATCH 036/694] grpc-js-xds: Increase interop test timeout --- test/kokoro/xds-interop.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/kokoro/xds-interop.cfg b/test/kokoro/xds-interop.cfg index 16a7dc7dc..a615d0530 100644 --- a/test/kokoro/xds-interop.cfg +++ b/test/kokoro/xds-interop.cfg @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc-node/packages/grpc-js-xds/scripts/xds.sh" -timeout_mins: 120 +timeout_mins: 180 action { define_artifacts { regex: "github/grpc/reports/**" From 972281770fd288330939764c7d4a5c6f39c28180 Mon Sep 17 00:00:00 2001 From: Eric Gribkoff Date: Thu, 1 Jul 2021 05:14:50 -0700 Subject: [PATCH 037/694] Increase xDS job timeouts to match Java and Go --- test/kokoro/xds-interop.cfg | 2 +- test/kokoro/xds-v3-interop.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/kokoro/xds-interop.cfg b/test/kokoro/xds-interop.cfg index a615d0530..866cb4b58 100644 --- a/test/kokoro/xds-interop.cfg +++ b/test/kokoro/xds-interop.cfg @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc-node/packages/grpc-js-xds/scripts/xds.sh" -timeout_mins: 180 +timeout_mins: 360 action { define_artifacts { regex: "github/grpc/reports/**" diff --git a/test/kokoro/xds-v3-interop.cfg b/test/kokoro/xds-v3-interop.cfg index 4480f1590..75377fe16 100644 --- a/test/kokoro/xds-v3-interop.cfg +++ b/test/kokoro/xds-v3-interop.cfg @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc-node/packages/grpc-js-xds/scripts/xds-v3.sh" -timeout_mins: 180 +timeout_mins: 360 action { define_artifacts { regex: "github/grpc/reports/**" From 6b3ebbb82921e1e42d8a0974a7e900ff606a66cc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 8 Jul 2021 10:27:31 -0700 Subject: [PATCH 038/694] grpc-js: Add logging for TLS over proxy connection errors --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/http_proxy.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 8acb8a145..97d7e2758 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.3.4", + "version": "1.3.5", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index f6921378b..8c461d4f8 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -244,7 +244,13 @@ export function getProxiedConnection( resolve({ socket: cts, realTarget: parsedTarget }); } ); - cts.on('error', () => { + cts.on('error', (error: Error) => { + trace('Failed to establish a TLS connection to ' + + options.path + + ' through proxy ' + + proxyAddressString + + ' with error ' + + error.message); reject(); }); } else { From 194aeec0513786cba009d0fc910ccba828ecca34 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 8 Jul 2021 10:32:12 -0700 Subject: [PATCH 039/694] grpc-js-xds: Increase interop test timeout to 6 hours --- test/kokoro/xds-interop.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/kokoro/xds-interop.cfg b/test/kokoro/xds-interop.cfg index 16a7dc7dc..866cb4b58 100644 --- a/test/kokoro/xds-interop.cfg +++ b/test/kokoro/xds-interop.cfg @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc-node/packages/grpc-js-xds/scripts/xds.sh" -timeout_mins: 120 +timeout_mins: 360 action { define_artifacts { regex: "github/grpc/reports/**" From 311aca31e411da18e542686665aa14711aa7ec3e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 17 Jun 2021 09:45:19 -0700 Subject: [PATCH 040/694] grpc-js-xds: Add HTTP Filters support --- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/environment.ts | 4 +- .../generated/google/protobuf/EnumOptions.ts | 3 - .../google/protobuf/EnumValueOptions.ts | 5 - .../generated/google/protobuf/FieldOptions.ts | 10 - .../generated/google/protobuf/FileOptions.ts | 6 - .../google/protobuf/MessageOptions.ts | 6 - .../grpc-js-xds/src/generated/typed_struct.ts | 74 ++++++ .../src/generated/udpa/type/v1/TypedStruct.ts | 77 ++++++ packages/grpc-js-xds/src/http-filter.ts | 221 ++++++++++++++++++ packages/grpc-js-xds/src/protobuf-any.ts | 23 ++ packages/grpc-js-xds/src/resolver-xds.ts | 139 ++++++++++- packages/grpc-js-xds/src/route-action.ts | 27 ++- .../src/xds-stream-state/lds-state.ts | 9 + .../src/xds-stream-state/rds-state.ts | 25 ++ packages/grpc-js/src/call-stream.ts | 4 + packages/grpc-js/src/channel.ts | 1 + packages/grpc-js/src/resolver.ts | 2 + .../grpc-js/src/resolving-load-balancer.ts | 6 +- 19 files changed, 596 insertions(+), 48 deletions(-) create mode 100644 packages/grpc-js-xds/src/generated/typed_struct.ts create mode 100644 packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts create mode 100644 packages/grpc-js-xds/src/http-filter.ts create mode 100644 packages/grpc-js-xds/src/protobuf-any.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index f69c8bf49..8b6005d5c 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/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", "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" }, "repository": { diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index c2c7f2e05..18e9e70bd 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -13,4 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. * - */ \ No newline at end of file + */ + +export const EXPERIMENTAL_FAULT_INJECTION = process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION; \ No newline at end of file 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 b92f699a0..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); } export interface EnumOptions__Output { 'allowAlias': (boolean); 'deprecated': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; - '.udpa.annotations.enum_migrate'?: (_udpa_annotations_MigrateAnnotation__Output); } 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 db2770534..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,18 +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); } 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); } 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 63f8a015f..530022afe 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -2,8 +2,6 @@ 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 { FieldSecurityAnnotation as _udpa_annotations_FieldSecurityAnnotation, FieldSecurityAnnotation__Output as _udpa_annotations_FieldSecurityAnnotation__Output } from '../../udpa/annotations/FieldSecurityAnnotation'; -import type { FieldMigrateAnnotation as _udpa_annotations_FieldMigrateAnnotation, FieldMigrateAnnotation__Output as _udpa_annotations_FieldMigrateAnnotation__Output } from '../../udpa/annotations/FieldMigrateAnnotation'; // Original file: null @@ -30,10 +28,6 @@ export interface FieldOptions { 'weak'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; '.validate.rules'?: (_validate_FieldRules); - '.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation); - '.udpa.annotations.sensitive'?: (boolean); - '.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation); - '.envoy.annotations.disallowed_by_default'?: (boolean); } export interface FieldOptions__Output { @@ -45,8 +39,4 @@ export interface FieldOptions__Output { 'weak': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; '.validate.rules'?: (_validate_FieldRules__Output); - '.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation__Output); - '.udpa.annotations.sensitive': (boolean); - '.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation__Output); - '.envoy.annotations.disallowed_by_default': (boolean); } 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 b2ddbb374..573e847c0 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts @@ -1,8 +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 { 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'; // Original file: null @@ -28,8 +26,6 @@ export interface FileOptions { 'objcClassPrefix'?: (string); 'csharpNamespace'?: (string); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; - '.udpa.annotations.file_migrate'?: (_udpa_annotations_FileMigrateAnnotation); - '.udpa.annotations.file_status'?: (_udpa_annotations_StatusAnnotation); } export interface FileOptions__Output { @@ -48,6 +44,4 @@ export interface FileOptions__Output { 'objcClassPrefix': (string); 'csharpNamespace': (string); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; - '.udpa.annotations.file_migrate'?: (_udpa_annotations_FileMigrateAnnotation__Output); - '.udpa.annotations.file_status'?: (_udpa_annotations_StatusAnnotation__Output); } 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 219e4bfd2..d3b5a54b8 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts @@ -1,8 +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'; export interface MessageOptions { 'messageSetWireFormat'?: (boolean); @@ -11,8 +9,6 @@ export interface MessageOptions { 'mapEntry'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; '.validate.disabled'?: (boolean); - '.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation); - '.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation); } export interface MessageOptions__Output { @@ -22,6 +18,4 @@ export interface MessageOptions__Output { 'mapEntry': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; '.validate.disabled': (boolean); - '.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation__Output); - '.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation__Output); } diff --git a/packages/grpc-js-xds/src/generated/typed_struct.ts b/packages/grpc-js-xds/src/generated/typed_struct.ts new file mode 100644 index 000000000..78d781a29 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/typed_struct.ts @@ -0,0 +1,74 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + google: { + protobuf: { + DescriptorProto: MessageTypeDefinition + Duration: MessageTypeDefinition + EnumDescriptorProto: MessageTypeDefinition + EnumOptions: MessageTypeDefinition + EnumValueDescriptorProto: MessageTypeDefinition + EnumValueOptions: MessageTypeDefinition + FieldDescriptorProto: MessageTypeDefinition + FieldOptions: MessageTypeDefinition + FileDescriptorProto: MessageTypeDefinition + FileDescriptorSet: MessageTypeDefinition + FileOptions: MessageTypeDefinition + GeneratedCodeInfo: MessageTypeDefinition + ListValue: MessageTypeDefinition + MessageOptions: MessageTypeDefinition + MethodDescriptorProto: MessageTypeDefinition + MethodOptions: MessageTypeDefinition + NullValue: EnumTypeDefinition + OneofDescriptorProto: MessageTypeDefinition + OneofOptions: MessageTypeDefinition + ServiceDescriptorProto: MessageTypeDefinition + ServiceOptions: MessageTypeDefinition + SourceCodeInfo: MessageTypeDefinition + Struct: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UninterpretedOption: MessageTypeDefinition + Value: MessageTypeDefinition + } + } + udpa: { + type: { + v1: { + TypedStruct: 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 + } +} + diff --git a/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts new file mode 100644 index 000000000..f9d842982 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts @@ -0,0 +1,77 @@ +// Original file: deps/udpa/udpa/type/v1/typed_struct.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct'; + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url'?: (string); + /** + * A JSON representation of the above specified type. + */ + 'value'?: (_google_protobuf_Struct); +} + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct__Output { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url': (string); + /** + * A JSON representation of the above specified type. + */ + 'value'?: (_google_protobuf_Struct__Output); +} diff --git a/packages/grpc-js-xds/src/http-filter.ts b/packages/grpc-js-xds/src/http-filter.ts new file mode 100644 index 000000000..e7ea32957 --- /dev/null +++ b/packages/grpc-js-xds/src/http-filter.ts @@ -0,0 +1,221 @@ +/* + * Copyright 2021 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. + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { experimental } from '@grpc/grpc-js'; +import { Any__Output } from './generated/google/protobuf/Any'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import { TypedStruct__Output } from './generated/udpa/type/v1/TypedStruct'; +import { FilterConfig__Output } from './generated/envoy/config/route/v3/FilterConfig'; +import { HttpFilter__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter'; + +const TYPED_STRUCT_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct'; +const TYPED_STRUCT_NAME = 'udpa.type.v1.TypedStruct'; + +const FILTER_CONFIG_URL = 'type.googleapis.com/envoy.config.route.v3.FilterConfig'; +const FILTER_CONFIG_NAME = 'envoy.config.route.v3.FilterConfig'; + +const resourceRoot = loadProtosWithOptionsSync([ + 'udpa/type/v1/typed_struct.proto', + 'envoy/config/route/v3/route_components.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/udpa/', + __dirname + '/../../deps/envoy-api/' + ], + } +); + +export interface HttpFilterConfig { + typeUrl: string; + config: any; +} + +export interface HttpFilterFactoryConstructor { + new(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory; +} + +export interface HttpFilterRegistryEntry { + parseTopLevelFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null; + parseOverrideFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null; + httpFilterConstructor: HttpFilterFactoryConstructor; +} + +const FILTER_REGISTRY = new Map(); + +export function registerHttpFilter(typeName: string, entry: HttpFilterRegistryEntry) { + FILTER_REGISTRY.set(typeName, entry); +} + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +function parseAnyMessage(message: Any__Output): MessageType | null { + const messageType = resourceRoot.lookup(message.type_url); + if (messageType) { + const decodedMessage = (messageType as any).decode(message.value); + return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType; + } else { + return null; + } +} + +function getTopLevelFilterUrl(encodedConfig: Any__Output): string { + let typeUrl: string; + if (encodedConfig.type_url === TYPED_STRUCT_URL) { + const typedStruct = parseAnyMessage(encodedConfig) + if (typedStruct) { + return typedStruct.type_url; + } else { + throw new Error('Failed to parse TypedStruct'); + } + } else { + return encodedConfig.type_url; + } +} + +export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean { + if (!httpFilter.typed_config) { + return false; + } + const encodedConfig = httpFilter.typed_config; + let typeUrl: string; + try { + typeUrl = getTopLevelFilterUrl(encodedConfig); + } catch (e) { + return false; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + const parsedConfig = registryEntry.parseTopLevelFilterConfig(encodedConfig); + return parsedConfig !== null; + } else { + if (httpFilter.is_optional) { + return true; + } else { + return false; + } + } +} + +export function validateOverrideFilter(encodedConfig: Any__Output): boolean { + let typeUrl: string; + let realConfig: Any__Output; + let isOptional = false; + if (encodedConfig.type_url === FILTER_CONFIG_URL) { + const filterConfig = parseAnyMessage(encodedConfig); + if (filterConfig) { + isOptional = filterConfig.is_optional; + if (filterConfig.config) { + realConfig = filterConfig.config; + } else { + return false; + } + } else { + return false; + } + } else { + realConfig = encodedConfig; + } + if (realConfig.type_url === TYPED_STRUCT_URL) { + const typedStruct = parseAnyMessage(encodedConfig); + if (typedStruct) { + typeUrl = typedStruct.type_url; + } else { + return false; + } + } else { + typeUrl = realConfig.type_url; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + const parsedConfig = registryEntry.parseOverrideFilterConfig(encodedConfig); + return parsedConfig !== null; + } else { + if (isOptional) { + return true; + } else { + return false; + } + } +} + +export function parseTopLevelFilterConfig(encodedConfig: Any__Output) { + let typeUrl: string; + try { + typeUrl = getTopLevelFilterUrl(encodedConfig); + } catch (e) { + return null; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + return registryEntry.parseTopLevelFilterConfig(encodedConfig); + } else { + // Filter type URL not found in registry + return null; + } +} + +export function parseOverrideFilterConfig(encodedConfig: Any__Output) { + let typeUrl: string; + let realConfig: Any__Output; + if (encodedConfig.type_url === FILTER_CONFIG_URL) { + const filterConfig = parseAnyMessage(encodedConfig); + if (filterConfig) { + if (filterConfig.config) { + realConfig = filterConfig.config; + } else { + return null; + } + } else { + return null; + } + } else { + realConfig = encodedConfig; + } + if (realConfig.type_url === TYPED_STRUCT_URL) { + const typedStruct = parseAnyMessage(encodedConfig); + if (typedStruct) { + typeUrl = typedStruct.type_url; + } else { + return null; + } + } else { + typeUrl = realConfig.type_url; + } + const registryEntry = FILTER_REGISTRY.get(typeUrl); + if (registryEntry) { + return registryEntry.parseOverrideFilterConfig(encodedConfig); + } else { + return null; + } +} + +export function createHttpFilter(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory | null { + const registryEntry = FILTER_REGISTRY.get(config.typeUrl); + if (registryEntry) { + return new registryEntry.httpFilterConstructor(config, overrideConfig); + } else { + return null; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/protobuf-any.ts b/packages/grpc-js-xds/src/protobuf-any.ts new file mode 100644 index 000000000..cfee35f91 --- /dev/null +++ b/packages/grpc-js-xds/src/protobuf-any.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2021 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. + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { Any__Output } from './generated/google/protobuf/Any'; + +function parseAnyMessage(encodedMessage: Any__Output) { + +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 892cf5711..372d8b052 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -42,6 +42,12 @@ import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedCluster import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from './resources'; import Duration = experimental.Duration; import { Duration__Output } from './generated/google/protobuf/Duration'; +import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter'; +import { EXPERIMENTAL_FAULT_INJECTION } from './environment'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import BaseFilter = experimental.BaseFilter; +import CallStream = experimental.CallStream; const TRACER_NAME = 'xds_resolver'; @@ -203,6 +209,25 @@ function protoDurationToDuration(duration: Duration__Output): Duration { } } +class NoRouterFilter extends BaseFilter implements Filter { + constructor(private call: CallStream) { + super(); + } + + sendMetadata(metadata: Promise): Promise { + this.call.cancelWithStatus(status.UNAVAILABLE, 'no xDS HTTP router filter configured'); + return Promise.reject(new Error('no xDS HTTP router filter configured')); + } +} + +class NoRouterFilterFactory implements FilterFactory { + createFilter(callStream: CallStream): NoRouterFilter { + return new NoRouterFilter(callStream); + } +} + +const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; + class XdsResolver implements Resolver { private hasReportedSuccess = false; @@ -221,6 +246,9 @@ class XdsResolver implements Resolver { private latestDefaultTimeout: Duration | undefined = undefined; + private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = []; + private hasRouterFilter = false; + constructor( private target: GrpcUri, private listener: ResolverListener, @@ -235,6 +263,21 @@ class XdsResolver implements Resolver { } else { this.latestDefaultTimeout = protoDurationToDuration(defaultTimeout); } + if (EXPERIMENTAL_FAULT_INJECTION) { + this.ldsHttpFilterConfigs = []; + this.hasRouterFilter = false; + for (const filter of httpConnectionManager.http_filters) { + if (filter.typed_config?.type_url === ROUTER_FILTER_URL) { + this.hasRouterFilter = true; + break; + } + // typed_config must be set here, or validation would have failed + const filterConfig = parseTopLevelFilterConfig(filter.typed_config!); + if (filterConfig) { + this.ldsHttpFilterConfigs.push({name: filter.name, config: filterConfig}); + } + } + } switch (httpConnectionManager.route_specifier) { case 'rds': { const routeConfigName = httpConnectionManager.rds!.route_config_name; @@ -314,6 +357,15 @@ class XdsResolver implements Resolver { this.reportResolutionError('No matching route found'); return; } + const virtualHostHttpFilterOverrides = new Map(); + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filter] of Object.entries(virtualHost.typed_per_filter_config ?? {})) { + const parsedConfig = parseOverrideFilterConfig(filter); + if (parsedConfig) { + virtualHostHttpFilterOverrides.set(name, parsedConfig); + } + } + } trace('Received virtual host config ' + JSON.stringify(virtualHost, undefined, 2)); const allConfigClusters = new Set(); const matchList: {matcher: Matcher, action: RouteAction}[] = []; @@ -334,20 +386,89 @@ class XdsResolver implements Resolver { if (timeout?.seconds === 0 && timeout.nanos === 0) { timeout = undefined; } + const routeHttpFilterOverrides = new Map(); + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filter] of Object.entries(route.typed_per_filter_config ?? {})) { + const parsedConfig = parseOverrideFilterConfig(filter); + if (parsedConfig) { + routeHttpFilterOverrides.set(name, parsedConfig); + } + } + } switch (route.route!.cluster_specifier) { case 'cluster_header': continue; case 'cluster':{ const cluster = route.route!.cluster!; allConfigClusters.add(cluster); - routeAction = new SingleClusterRouteAction(cluster, timeout); + const extraFilterFactories: FilterFactory[] = []; + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const filterConfig of this.ldsHttpFilterConfigs) { + if (routeHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else { + const filter = createHttpFilter(filterConfig.config); + if (filter) { + extraFilterFactories.push(filter); + } + } + } + if (!this.hasRouterFilter) { + extraFilterFactories.push(new NoRouterFilterFactory()); + } + } + routeAction = new SingleClusterRouteAction(cluster, timeout, extraFilterFactories); break; } case 'weighted_clusters': { const weightedClusters: WeightedCluster[] = []; for (const clusterWeight of route.route!.weighted_clusters!.clusters) { allConfigClusters.add(clusterWeight.name); - weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0}); + const extraFilterFactories: FilterFactory[] = []; + const clusterHttpFilterOverrides = new Map(); + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filter] of Object.entries(clusterWeight.typed_per_filter_config ?? {})) { + const parsedConfig = parseOverrideFilterConfig(filter); + if (parsedConfig) { + clusterHttpFilterOverrides.set(name, parsedConfig); + } + } + for (const filterConfig of this.ldsHttpFilterConfigs) { + if (clusterHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, clusterHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else if (routeHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) { + const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!); + if (filter) { + extraFilterFactories.push(filter); + } + } else { + const filter = createHttpFilter(filterConfig.config); + if (filter) { + extraFilterFactories.push(filter); + } + } + } + if (!this.hasRouterFilter) { + extraFilterFactories.push(new NoRouterFilterFactory()); + } + } + weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, extraFilterFactories: extraFilterFactories}); } routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, timeout); } @@ -376,16 +497,17 @@ class XdsResolver implements Resolver { const configSelector: ConfigSelector = (methodName, metadata) => { for (const {matcher, action} of matchList) { if (matcher.apply(methodName, metadata)) { - const clusterName = action.getCluster(); - this.refCluster(clusterName); + const clusterResult = action.getCluster(); + this.refCluster(clusterResult.name); const onCommitted = () => { - this.unrefCluster(clusterName); + this.unrefCluster(clusterResult.name); } return { methodConfig: {name: [], timeout: action.getTimeout()}, onCommitted: onCommitted, - pickInformation: {cluster: clusterName}, - status: status.OK + pickInformation: {cluster: clusterResult.name}, + status: status.OK, + extraFilterFactories: clusterResult.extraFilterFactories }; } } @@ -393,7 +515,8 @@ class XdsResolver implements Resolver { methodConfig: {name: []}, // cluster won't be used here, but it's set because of some TypeScript weirdness pickInformation: {cluster: ''}, - status: status.UNAVAILABLE + status: status.UNAVAILABLE, + extraFilterFactories: [] }; }; trace('Created ConfigSelector with configuration:'); diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts index 5574bcd91..a45bf02a4 100644 --- a/packages/grpc-js-xds/src/route-action.ts +++ b/packages/grpc-js-xds/src/route-action.ts @@ -16,10 +16,17 @@ import { experimental } from '@grpc/grpc-js'; import Duration = experimental.Duration; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; + +export interface ClusterResult { + name: string; + extraFilterFactories: FilterFactory[]; +} export interface RouteAction { toString(): string; - getCluster(): string; + getCluster(): ClusterResult; getTimeout(): Duration | undefined; } @@ -33,10 +40,13 @@ function durationToLogString(duration: Duration) { } export class SingleClusterRouteAction implements RouteAction { - constructor(private cluster: string, private timeout: Duration | undefined) {} + constructor(private cluster: string, private timeout: Duration | undefined, private extraFilterFactories: FilterFactory[]) {} getCluster() { - return this.cluster; + return { + name: this.cluster, + extraFilterFactories: this.extraFilterFactories + }; } toString() { @@ -55,11 +65,13 @@ export class SingleClusterRouteAction implements RouteAction { export interface WeightedCluster { name: string; weight: number; + extraFilterFactories: FilterFactory[]; } interface ClusterChoice { name: string; numerator: number; + extraFilterFactories: FilterFactory[]; } export class WeightedClusterRouteAction implements RouteAction { @@ -72,7 +84,7 @@ export class WeightedClusterRouteAction implements RouteAction { let lastNumerator = 0; for (const clusterWeight of clusters) { lastNumerator += clusterWeight.weight; - this.clusterChoices.push({name: clusterWeight.name, numerator: lastNumerator}); + this.clusterChoices.push({name: clusterWeight.name, numerator: lastNumerator, extraFilterFactories: clusterWeight.extraFilterFactories}); } } @@ -80,11 +92,14 @@ export class WeightedClusterRouteAction implements RouteAction { const randomNumber = Math.random() * this.totalWeight; for (const choice of this.clusterChoices) { if (randomNumber < choice.numerator) { - return choice.name; + return { + name: choice.name, + extraFilterFactories: choice.extraFilterFactories + }; } } // This should be prevented by the validation rules - return ''; + return {name: '', extraFilterFactories: []}; } toString() { diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index eb76f7994..10ada540b 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -22,6 +22,8 @@ import { RdsState } from "./rds-state"; import { Watcher, XdsStreamState } from "./xds-stream-state"; import { HttpConnectionManager__Output } from '../generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V2, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from '../resources'; +import { validateTopLevelFilter } from '../http-filter'; +import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; const TRACER_NAME = 'xds_client'; @@ -100,6 +102,13 @@ export class LdsState implements XdsStreamState { return false; } const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener.value); + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const httpFilter of httpConnectionManager.http_filters) { + if (!validateTopLevelFilter(httpFilter)) { + return false; + } + } + } switch (httpConnectionManager.route_specifier) { case 'rds': return !!httpConnectionManager.rds?.config_source?.ads; diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 1e50f22e7..9441a7ecc 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -16,7 +16,9 @@ */ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; +import { EXPERIMENTAL_FAULT_INJECTION } from "../environment"; import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; +import { validateOverrideFilter } from "../http-filter"; import { CdsLoadBalancingConfig } from "../load-balancer-cds"; import { Watcher, XdsStreamState } from "./xds-stream-state"; import ServiceConfig = experimental.ServiceConfig; @@ -112,6 +114,13 @@ export class RdsState implements XdsStreamState { return false; } } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return false; + } + } + } for (const route of virtualHost.routes) { const match = route.match; if (!match) { @@ -131,6 +140,13 @@ export class RdsState implements XdsStreamState { if ((route.route === undefined) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { return false; } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const filterConfig of Object.values(route.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return false; + } + } + } if (route.route!.cluster_specifier === 'weighted_clusters') { let weightSum = 0; for (const clusterWeight of route.route.weighted_clusters!.clusters) { @@ -139,6 +155,15 @@ export class RdsState implements XdsStreamState { if (weightSum !== route.route.weighted_clusters!.total_weight?.value ?? 100) { return false; } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const weightedCluster of route.route!.weighted_clusters!.clusters) { + for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return false; + } + } + } + } } } } diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index ae6342604..e9e1cb8de 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -721,6 +721,10 @@ export class Http2CallStream implements Call { this.configDeadline = configDeadline; } + addFilterFactories(extraFilterFactories: FilterFactory[]) { + this.filterStack.push(extraFilterFactories.map(filterFactory => filterFactory.createFilter(this))); + } + startRead() { /* If the stream has ended with an error, we should not emit any more * messages and we should communicate that the stream has ended */ diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index cd2fc94a2..859a6e76d 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -536,6 +536,7 @@ export class ChannelImplementation implements Channel { // Refreshing the filters makes the deadline filter pick up the new deadline stream.filterStack.refresh(); } + stream.addFilterFactories(callConfig.extraFilterFactories); this.tryPick(stream, metadata, callConfig); } else { stream.cancelWithStatus(callConfig.status, "Failed to route call to method " + stream.getMethod()); diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index 147ace30d..cf5f7ee26 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -24,12 +24,14 @@ import { GrpcUri, uriToString } from './uri-parser'; import { ChannelOptions } from './channel-options'; import { Metadata } from './metadata'; import { Status } from './constants'; +import { Filter, FilterFactory } from './filter'; export interface CallConfig { methodConfig: MethodConfig; onCommitted?: () => void; pickInformation: {[key: string]: string}; status: Status; + extraFilterFactories: FilterFactory[]; } /** diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 94dd8c4a9..3f84af378 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -58,7 +58,8 @@ function getDefaultConfigSelector(serviceConfig: ServiceConfig | null): ConfigSe return { methodConfig: methodConfig, pickInformation: {}, - status: Status.OK + status: Status.OK, + extraFilterFactories: [] }; } } @@ -67,7 +68,8 @@ function getDefaultConfigSelector(serviceConfig: ServiceConfig | null): ConfigSe return { methodConfig: {name: []}, pickInformation: {}, - status: Status.OK + status: Status.OK, + extraFilterFactories: [] }; } } From a0f298c514c6ac56e33c149f8477d35deecbb4d2 Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Thu, 15 Jul 2021 10:08:05 -0700 Subject: [PATCH 041/694] grpc-js: Split out logs for different severity levels --- packages/grpc-js/src/logging.ts | 37 ++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts index 352496240..549cd860e 100644 --- a/packages/grpc-js/src/logging.ts +++ b/packages/grpc-js/src/logging.ts @@ -17,7 +17,19 @@ import { LogVerbosity } from './constants'; -let _logger: Partial = console; +const DEFAULT_LOGGER: Partial = { + error: (message?: any, ...optionalParams: any[]) => { + console.error('E ' + message, ...optionalParams); + }, + info: (message?: any, ...optionalParams: any[]) => { + console.error('I ' + message, ...optionalParams); + }, + debug: (message?: any, ...optionalParams: any[]) => { + console.error('D ' + message, ...optionalParams); + }, +} + +let _logger: Partial = DEFAULT_LOGGER; let _logVerbosity: LogVerbosity = LogVerbosity.ERROR; const verbosityString = @@ -54,8 +66,27 @@ export const setLoggerVerbosity = (verbosity: LogVerbosity): void => { // eslint-disable-next-line @typescript-eslint/no-explicit-any export const log = (severity: LogVerbosity, ...args: any[]): void => { - if (severity >= _logVerbosity && typeof _logger.error === 'function') { - _logger.error(...args); + let logFunction: typeof DEFAULT_LOGGER.error; + if (severity >= _logVerbosity) { + switch (severity) { + case LogVerbosity.DEBUG: + logFunction = _logger.debug; + break; + case LogVerbosity.INFO: + logFunction = _logger.info; + break; + case LogVerbosity.ERROR: + logFunction = _logger.error; + break; + } + /* Fall back to _logger.error when other methods are not available for + * compatiblity with older behavior that always logged to _logger.error */ + if (!logFunction) { + logFunction = _logger.error; + } + if (logFunction) { + logFunction.bind(_logger)(...args); + } } }; From 312b7613def2b0b418e84bb51d304776ae4ebfce Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Thu, 15 Jul 2021 10:28:15 -0700 Subject: [PATCH 042/694] grpc-js: Tighten server.bindAsync creds typecheck --- packages/grpc-js/src/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 017312219..42b79a3cf 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -237,8 +237,8 @@ export class Server { throw new TypeError('port must be a string'); } - if (creds === null || typeof creds !== 'object') { - throw new TypeError('creds must be an object'); + if (creds === null || !(creds instanceof ServerCredentials)) { + throw new TypeError('creds must be a ServerCredentials object'); } if (typeof callback !== 'function') { From 6359bf066f481d51efb2a63f3a477e0fc151bbab Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Thu, 15 Jul 2021 10:55:50 -0700 Subject: [PATCH 043/694] Remove test for exact default logger identity --- packages/grpc-js/test/test-logging.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/grpc-js/test/test-logging.ts b/packages/grpc-js/test/test-logging.ts index c1601cbc5..d275158cf 100644 --- a/packages/grpc-js/test/test-logging.ts +++ b/packages/grpc-js/test/test-logging.ts @@ -27,10 +27,6 @@ describe('Logging', () => { grpc.setLogVerbosity(grpc.logVerbosity.DEBUG); }); - it('logger defaults to console', () => { - assert.strictEqual(logging.getLogger(), console); - }); - it('sets the logger to a new value', () => { const logger: Partial = {}; From a5449155049351c81b4ffead4a3004217e504568 Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Thu, 15 Jul 2021 11:29:28 -0700 Subject: [PATCH 044/694] Add router filter registry entry --- .../src/http-filter/router-filter.ts | 49 +++++++++++++++++++ packages/grpc-js-xds/src/index.ts | 2 + 2 files changed, 51 insertions(+) create mode 100644 packages/grpc-js-xds/src/http-filter/router-filter.ts diff --git a/packages/grpc-js-xds/src/http-filter/router-filter.ts b/packages/grpc-js-xds/src/http-filter/router-filter.ts new file mode 100644 index 000000000..81450c0ff --- /dev/null +++ b/packages/grpc-js-xds/src/http-filter/router-filter.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2021 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 { experimental } from '@grpc/grpc-js'; +import { Any__Output } from '../generated/google/protobuf/Any'; +import { HttpFilterConfig, registerHttpFilter } from '../http-filter'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import BaseFilter = experimental.BaseFilter; + +class RouterFilter extends BaseFilter implements Filter {} + +class RouterFilterFactory implements FilterFactory { + constructor(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig) {} + + createFilter(callStream: experimental.CallStream): RouterFilter { + return new RouterFilter(); + } +} + +const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; + +function parseConfig(encodedConfig: Any__Output): HttpFilterConfig | null { + return { + typeUrl: ROUTER_FILTER_URL, + config: null + }; +} + +export function setup() { + registerHttpFilter(ROUTER_FILTER_URL, { + parseTopLevelFilterConfig: parseConfig, + parseOverrideFilterConfig: parseConfig, + httpFilterConstructor: RouterFilterFactory + }); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 1b24d25e6..2fee339d1 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -22,6 +22,7 @@ import * as load_balancer_lrs from './load-balancer-lrs'; import * as load_balancer_priority from './load-balancer-priority'; import * as load_balancer_weighted_target from './load-balancer-weighted-target'; import * as load_balancer_xds_cluster_manager from './load-balancer-xds-cluster-manager'; +import * as router_filter from './http-filter/router-filter'; /** * Register the "xds:" name scheme with the @grpc/grpc-js library. @@ -34,4 +35,5 @@ export function register() { load_balancer_priority.setup(); load_balancer_weighted_target.setup(); load_balancer_xds_cluster_manager.setup(); + router_filter.setup(); } \ No newline at end of file From d0745b3a4caf84525357cbcca7f62ad625dd16fd Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Thu, 15 Jul 2021 14:56:47 -0700 Subject: [PATCH 045/694] Run call config filter factories before load balancing --- packages/grpc-js-xds/src/resolver-xds.ts | 6 +-- packages/grpc-js-xds/src/route-action.ts | 14 ++--- packages/grpc-js/src/call-stream.ts | 8 +-- packages/grpc-js/src/channel.ts | 51 ++++++++++++++----- packages/grpc-js/src/filter-stack.ts | 4 ++ packages/grpc-js/src/resolver.ts | 2 +- .../grpc-js/src/resolving-load-balancer.ts | 4 +- packages/grpc-js/src/subchannel.ts | 4 +- 8 files changed, 61 insertions(+), 32 deletions(-) diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 372d8b052..c48cc251c 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -468,7 +468,7 @@ class XdsResolver implements Resolver { extraFilterFactories.push(new NoRouterFilterFactory()); } } - weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, extraFilterFactories: extraFilterFactories}); + weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, dynamicFilterFactories: extraFilterFactories}); } routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, timeout); } @@ -507,7 +507,7 @@ class XdsResolver implements Resolver { onCommitted: onCommitted, pickInformation: {cluster: clusterResult.name}, status: status.OK, - extraFilterFactories: clusterResult.extraFilterFactories + dynamicFilterFactories: clusterResult.dynamicFilterFactories }; } } @@ -516,7 +516,7 @@ class XdsResolver implements Resolver { // cluster won't be used here, but it's set because of some TypeScript weirdness pickInformation: {cluster: ''}, status: status.UNAVAILABLE, - extraFilterFactories: [] + dynamicFilterFactories: [] }; }; trace('Created ConfigSelector with configuration:'); diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts index a45bf02a4..d29e67b9b 100644 --- a/packages/grpc-js-xds/src/route-action.ts +++ b/packages/grpc-js-xds/src/route-action.ts @@ -21,7 +21,7 @@ import FilterFactory = experimental.FilterFactory; export interface ClusterResult { name: string; - extraFilterFactories: FilterFactory[]; + dynamicFilterFactories: FilterFactory[]; } export interface RouteAction { @@ -45,7 +45,7 @@ export class SingleClusterRouteAction implements RouteAction { getCluster() { return { name: this.cluster, - extraFilterFactories: this.extraFilterFactories + dynamicFilterFactories: this.extraFilterFactories }; } @@ -65,13 +65,13 @@ export class SingleClusterRouteAction implements RouteAction { export interface WeightedCluster { name: string; weight: number; - extraFilterFactories: FilterFactory[]; + dynamicFilterFactories: FilterFactory[]; } interface ClusterChoice { name: string; numerator: number; - extraFilterFactories: FilterFactory[]; + dynamicFilterFactories: FilterFactory[]; } export class WeightedClusterRouteAction implements RouteAction { @@ -84,7 +84,7 @@ export class WeightedClusterRouteAction implements RouteAction { let lastNumerator = 0; for (const clusterWeight of clusters) { lastNumerator += clusterWeight.weight; - this.clusterChoices.push({name: clusterWeight.name, numerator: lastNumerator, extraFilterFactories: clusterWeight.extraFilterFactories}); + this.clusterChoices.push({name: clusterWeight.name, numerator: lastNumerator, dynamicFilterFactories: clusterWeight.dynamicFilterFactories}); } } @@ -94,12 +94,12 @@ export class WeightedClusterRouteAction implements RouteAction { if (randomNumber < choice.numerator) { return { name: choice.name, - extraFilterFactories: choice.extraFilterFactories + dynamicFilterFactories: choice.dynamicFilterFactories }; } } // This should be prevented by the validation rules - return {name: '', extraFilterFactories: []}; + return {name: '', dynamicFilterFactories: []}; } toString() { diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index e9e1cb8de..accc275fc 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -462,9 +462,9 @@ export class Http2CallStream implements Call { attachHttp2Stream( stream: http2.ClientHttp2Stream, subchannel: Subchannel, - extraFilters: FilterFactory[] + extraFilters: Filter[] ): void { - this.filterStack.push(extraFilters.map(filterFactory => filterFactory.createFilter(this))); + this.filterStack.push(extraFilters); if (this.finalStatus !== null) { stream.close(NGHTTP2_CANCEL); } else { @@ -721,8 +721,8 @@ export class Http2CallStream implements Call { this.configDeadline = configDeadline; } - addFilterFactories(extraFilterFactories: FilterFactory[]) { - this.filterStack.push(extraFilterFactories.map(filterFactory => filterFactory.createFilter(this))); + addFilters(extraFilters: Filter[]) { + this.filterStack.push(extraFilters); } startRead() { diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 859a6e76d..8b5f31bfb 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -41,6 +41,7 @@ import { mapProxyName } from './http_proxy'; import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; import { SurfaceCall } from './call'; +import { Filter } from './filter'; export enum ConnectivityState { IDLE, @@ -148,6 +149,7 @@ export class ChannelImplementation implements Channel { callStream: Http2CallStream; callMetadata: Metadata; callConfig: CallConfig; + dynamicFilters: Filter[]; }> = []; private connectivityStateWatchers: ConnectivityStateWatcher[] = []; private defaultAuthority: string; @@ -237,8 +239,8 @@ export class ChannelImplementation implements Channel { const queueCopy = this.pickQueue.slice(); this.pickQueue = []; this.callRefTimerUnref(); - for (const { callStream, callMetadata, callConfig } of queueCopy) { - this.tryPick(callStream, callMetadata, callConfig); + for (const { callStream, callMetadata, callConfig, dynamicFilters } of queueCopy) { + this.tryPick(callStream, callMetadata, callConfig, dynamicFilters); } this.updateState(connectivityState); }, @@ -308,8 +310,8 @@ export class ChannelImplementation implements Channel { } } - private pushPick(callStream: Http2CallStream, callMetadata: Metadata, callConfig: CallConfig) { - this.pickQueue.push({ callStream, callMetadata, callConfig }); + private pushPick(callStream: Http2CallStream, callMetadata: Metadata, callConfig: CallConfig, dynamicFilters: Filter[]) { + this.pickQueue.push({ callStream, callMetadata, callConfig, dynamicFilters }); this.callRefTimerRef(); } @@ -320,7 +322,10 @@ export class ChannelImplementation implements Channel { * @param callStream * @param callMetadata */ - private tryPick(callStream: Http2CallStream, callMetadata: Metadata, callConfig: CallConfig) { + private tryPick(callStream: Http2CallStream, callMetadata: Metadata, callConfig: CallConfig, dynamicFilters: Filter[]) { + if (callStream.getStatus() !== null) { + return; + } const pickResult = this.currentPicker.pick({ metadata: callMetadata, extraPickInfo: callConfig.pickInformation }); trace( LogVerbosity.DEBUG, @@ -357,7 +362,7 @@ export class ChannelImplementation implements Channel { ' has state ' + ConnectivityState[pickResult.subchannel!.getConnectivityState()] ); - this.pushPick(callStream, callMetadata, callConfig); + this.pushPick(callStream, callMetadata, callConfig, dynamicFilters); break; } /* We need to clone the callMetadata here because the transparent @@ -370,10 +375,11 @@ export class ChannelImplementation implements Channel { const subchannelState: ConnectivityState = pickResult.subchannel!.getConnectivityState(); if (subchannelState === ConnectivityState.READY) { try { + const pickExtraFilters = pickResult.extraFilterFactories.map(factory => factory.createFilter(callStream)); pickResult.subchannel!.startCallStream( finalMetadata, callStream, - pickResult.extraFilterFactories + [...dynamicFilters, ...pickExtraFilters] ); /* If we reach this point, the call stream has started * successfully */ @@ -406,7 +412,7 @@ export class ChannelImplementation implements Channel { (error as Error).message + '. Retrying pick' ); - this.tryPick(callStream, callMetadata, callConfig); + this.tryPick(callStream, callMetadata, callConfig, dynamicFilters); } else { trace( LogVerbosity.INFO, @@ -435,7 +441,7 @@ export class ChannelImplementation implements Channel { ConnectivityState[subchannelState] + ' after metadata filters. Retrying pick' ); - this.tryPick(callStream, callMetadata, callConfig); + this.tryPick(callStream, callMetadata, callConfig, dynamicFilters); } }, (error: Error & { code: number }) => { @@ -449,11 +455,11 @@ export class ChannelImplementation implements Channel { } break; case PickResultType.QUEUE: - this.pushPick(callStream, callMetadata, callConfig); + this.pushPick(callStream, callMetadata, callConfig, dynamicFilters); break; case PickResultType.TRANSIENT_FAILURE: if (callMetadata.getOptions().waitForReady) { - this.pushPick(callStream, callMetadata, callConfig); + this.pushPick(callStream, callMetadata, callConfig, dynamicFilters); } else { callStream.cancelWithStatus( pickResult.status!.code, @@ -536,8 +542,27 @@ export class ChannelImplementation implements Channel { // Refreshing the filters makes the deadline filter pick up the new deadline stream.filterStack.refresh(); } - stream.addFilterFactories(callConfig.extraFilterFactories); - this.tryPick(stream, metadata, callConfig); + if (callConfig.dynamicFilterFactories.length > 0) { + /* These dynamicFilters are the mechanism for implementing gRFC A39: + * https://github.com/grpc/proposal/blob/master/A39-xds-http-filters.md + * We run them here instead of with the rest of the filters because + * that spec says "the xDS HTTP filters will run in between name + * resolution and load balancing". + * + * We use the filter stack here to simplify the multi-filter async + * waterfall logic, but we pass along the underlying list of filters + * to avoid having nested filter stacks when combining it with the + * original filter stack. We do not pass along the original filter + * factory list because these filters may need to persist data + * between sending headers and other operations. */ + const dynamicFilterStackFactory = new FilterStackFactory(callConfig.dynamicFilterFactories); + const dynamicFilterStack = dynamicFilterStackFactory.createFilter(stream); + dynamicFilterStack.sendMetadata(Promise.resolve(metadata)).then(filteredMetadata => { + this.tryPick(stream, filteredMetadata, callConfig, dynamicFilterStack.getFilters()); + }); + } else { + this.tryPick(stream, metadata, callConfig, []); + } } else { stream.cancelWithStatus(callConfig.status, "Failed to route call to method " + stream.getMethod()); } diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts index 4e0ccf07f..a9e754428 100644 --- a/packages/grpc-js/src/filter-stack.ts +++ b/packages/grpc-js/src/filter-stack.ts @@ -81,6 +81,10 @@ export class FilterStack implements Filter { push(filters: Filter[]) { this.filters.unshift(...filters); } + + getFilters(): Filter[] { + return this.filters; + } } export class FilterStackFactory implements FilterFactory { diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index cf5f7ee26..57db665cd 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -31,7 +31,7 @@ export interface CallConfig { onCommitted?: () => void; pickInformation: {[key: string]: string}; status: Status; - extraFilterFactories: FilterFactory[]; + dynamicFilterFactories: FilterFactory[]; } /** diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 3f84af378..7f6b41fdb 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -59,7 +59,7 @@ function getDefaultConfigSelector(serviceConfig: ServiceConfig | null): ConfigSe methodConfig: methodConfig, pickInformation: {}, status: Status.OK, - extraFilterFactories: [] + dynamicFilterFactories: [] }; } } @@ -69,7 +69,7 @@ function getDefaultConfigSelector(serviceConfig: ServiceConfig | null): ConfigSe methodConfig: {name: []}, pickInformation: {}, status: Status.OK, - extraFilterFactories: [] + dynamicFilterFactories: [] }; } } diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index c50a68e77..6dc3e9b37 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -688,7 +688,7 @@ export class Subchannel { startCallStream( metadata: Metadata, callStream: Http2CallStream, - extraFilterFactories: FilterFactory[] + extraFilters: Filter[] ) { const headers = metadata.toHttp2Headers(); headers[HTTP2_HEADER_AUTHORITY] = callStream.getHost(); @@ -720,7 +720,7 @@ export class Subchannel { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; } logging.trace(LogVerbosity.DEBUG, 'call_stream', 'Starting stream on subchannel ' + this.subchannelAddressString + ' with headers\n' + headersString); - callStream.attachHttp2Stream(http2Stream, this, extraFilterFactories); + callStream.attachHttp2Stream(http2Stream, this, extraFilters); } /** From 9e4039d86b1acbe4c072863171b4b2e3bb49a2fc Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Fri, 16 Jul 2021 10:45:40 -0700 Subject: [PATCH 046/694] Updated text in bindAsync error test --- packages/grpc-js/test/test-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 58b102883..c4b5e30a0 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -116,7 +116,7 @@ describe('Server', () => { assert.throws(() => { server.bindAsync('localhost:0', null as any, noop); - }, /creds must be an object/); + }, /creds must be a ServerCredentials object/); assert.throws(() => { server.bindAsync( From af1676a5a5b26de4f41fa600ef1e20d58b600ab1 Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Fri, 16 Jul 2021 16:39:26 -0700 Subject: [PATCH 047/694] Add test for passing client credentials to server --- packages/grpc-js/test/test-server.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index c4b5e30a0..8b2815745 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -118,6 +118,10 @@ describe('Server', () => { server.bindAsync('localhost:0', null as any, noop); }, /creds must be a ServerCredentials object/); + assert.throws(() => { + server.bindAsync('localhost:0', grpc.credentials.createInsecure() as any, noop); + }, /creds must be a ServerCredentials object/); + assert.throws(() => { server.bindAsync( 'localhost:0', From f03b4dd87fdc9a56e4ef652ccdb9213f7553e97c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 20 Jul 2021 14:12:59 -0700 Subject: [PATCH 048/694] Validate uniqueness of http filter names --- packages/grpc-js-xds/src/xds-stream-state/rds-state.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 9441a7ecc..0322218d2 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -141,7 +141,12 @@ export class RdsState implements XdsStreamState { return false; } if (EXPERIMENTAL_FAULT_INJECTION) { - for (const filterConfig of Object.values(route.typed_per_filter_config ?? {})) { + const filterNames = new Set(); + for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { + if (filterNames.has(name)) { + return false; + } + filterNames.add(name); if (!validateOverrideFilter(filterConfig)) { return false; } From 215cdcd1344b4b1426d989772ae6219770476777 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 22 Jul 2021 12:42:11 -0700 Subject: [PATCH 049/694] Check for router filter in validation step --- packages/grpc-js-xds/src/resolver-xds.ts | 33 ------------------- .../src/xds-stream-state/lds-state.ts | 20 ++++++++++- .../src/xds-stream-state/rds-state.ts | 5 --- 3 files changed, 19 insertions(+), 39 deletions(-) diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 59a3fec47..3eaabb111 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -46,8 +46,6 @@ import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTop import { EXPERIMENTAL_FAULT_INJECTION } from './environment'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; -import BaseFilter = experimental.BaseFilter; -import CallStream = experimental.CallStream; const TRACER_NAME = 'xds_resolver'; @@ -209,25 +207,6 @@ function protoDurationToDuration(duration: Duration__Output): Duration { } } -class NoRouterFilter extends BaseFilter implements Filter { - constructor(private call: CallStream) { - super(); - } - - sendMetadata(metadata: Promise): Promise { - this.call.cancelWithStatus(status.UNAVAILABLE, 'no xDS HTTP router filter configured'); - return Promise.reject(new Error('no xDS HTTP router filter configured')); - } -} - -class NoRouterFilterFactory implements FilterFactory { - createFilter(callStream: CallStream): NoRouterFilter { - return new NoRouterFilter(callStream); - } -} - -const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; - class XdsResolver implements Resolver { private hasReportedSuccess = false; @@ -247,7 +226,6 @@ class XdsResolver implements Resolver { private latestDefaultTimeout: Duration | undefined = undefined; private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = []; - private hasRouterFilter = false; constructor( private target: GrpcUri, @@ -265,12 +243,7 @@ class XdsResolver implements Resolver { } if (EXPERIMENTAL_FAULT_INJECTION) { this.ldsHttpFilterConfigs = []; - this.hasRouterFilter = false; for (const filter of httpConnectionManager.http_filters) { - if (filter.typed_config?.type_url === ROUTER_FILTER_URL) { - this.hasRouterFilter = true; - break; - } // typed_config must be set here, or validation would have failed const filterConfig = parseTopLevelFilterConfig(filter.typed_config!); if (filterConfig) { @@ -421,9 +394,6 @@ class XdsResolver implements Resolver { } } } - if (!this.hasRouterFilter) { - extraFilterFactories.push(new NoRouterFilterFactory()); - } } routeAction = new SingleClusterRouteAction(cluster, timeout, extraFilterFactories); break; @@ -464,9 +434,6 @@ class XdsResolver implements Resolver { } } } - if (!this.hasRouterFilter) { - extraFilterFactories.push(new NoRouterFilterFactory()); - } } weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, dynamicFilterFactories: extraFilterFactories}); } diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 10ada540b..3efc64b92 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -31,6 +31,8 @@ function trace(text: string): void { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); } +const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; + export class LdsState implements XdsStreamState { versionInfo = ''; nonce = ''; @@ -103,10 +105,26 @@ export class LdsState implements XdsStreamState { } const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener.value); if (EXPERIMENTAL_FAULT_INJECTION) { - for (const httpFilter of httpConnectionManager.http_filters) { + const filterNames = new Set(); + for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { + if (filterNames.has(httpFilter.name)) { + return false; + } + filterNames.add(httpFilter.name); if (!validateTopLevelFilter(httpFilter)) { return false; } + /* Validate that the last filter, and only the last filter, is the + * router filter. */ + if (index < httpConnectionManager.http_filters.length - 1) { + if (httpFilter.name === ROUTER_FILTER_URL) { + return false; + } + } else { + if (httpFilter.name !== ROUTER_FILTER_URL) { + return false; + } + } } } switch (httpConnectionManager.route_specifier) { diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 0322218d2..f04692859 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -141,12 +141,7 @@ export class RdsState implements XdsStreamState { return false; } if (EXPERIMENTAL_FAULT_INJECTION) { - const filterNames = new Set(); for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { - if (filterNames.has(name)) { - return false; - } - filterNames.add(name); if (!validateOverrideFilter(filterConfig)) { return false; } From 2455c3d50aad13e8198c7b7583267bec5064e09c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 26 Jul 2021 10:52:11 -0700 Subject: [PATCH 050/694] grpc-js-xds: notify watchers when NACKing resource updates --- packages/grpc-js-xds/src/xds-client.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index ddca9284f..6828a8a8e 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -665,6 +665,11 @@ export class XdsClient { break; } if (serviceKind) { + this.adsState[serviceKind].reportStreamError({ + code: status.UNAVAILABLE, + details: message, + metadata: new Metadata() + }); resourceNames = this.adsState[serviceKind].getResourceNames(); nonce = this.adsState[serviceKind].nonce; versionInfo = this.adsState[serviceKind].versionInfo; From 82d5eb82f8babaac793f88eef3b54df37c05198b Mon Sep 17 00:00:00 2001 From: April Kyle Nassi Date: Wed, 28 Jul 2021 12:38:24 -0700 Subject: [PATCH 051/694] Update MAINTAINERS.md Moved 3 to emeritus --- MAINTAINERS.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index c9e0522a1..bdcef2e26 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -8,16 +8,17 @@ See [CONTRIBUTING.md](https://github.com/grpc/grpc-community/blob/master/CONTRIB for general contribution guidelines. ## Maintainers (in alphabetical order) - - [jiangtaoli2016](https://github.com/jiangtaoli2016), Google Inc. + - [jtattermusch](https://github.com/jtattermusch), Google Inc. - [murgatroid99](https://github.com/murgatroid99), Google Inc. - [nicolasnoble](https://github.com/nicolasnoble), Google Inc. - - [ofrobots](https://github.com/ofrobots), Google Inc. - [srini100](https://github.com/srini100), Google Inc. - - [WeiranFang](https://github.com/WeiranFang), Google Inc. - [wenbozhu](https://github.com/wenbozhu), Google Inc. ## Emeritus Maintainers (in alphabetical order) + - [jiangtaoli2016](https://github.com/jiangtaoli2016), Google Inc. - [kjin](https://github.com/kjin), Google Inc. - [matt-kwong](https://github.com/matt-kwong), Google Inc. - \ No newline at end of file + - [ofrobots](https://github.com/ofrobots), Google Inc. + - [WeiranFang](https://github.com/WeiranFang), Google Inc. + From 5518d0e8f4de98624202d664c3d797c97346055a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Huan=20LI=20=28=E6=9D=8E=E5=8D=93=E6=A1=93=29?= Date: Mon, 2 Aug 2021 11:28:24 +0800 Subject: [PATCH 052/694] add xds support reading bootstrap config directly from env var (#1868) --- packages/grpc-js-xds/src/xds-bootstrap.ts | 88 +++++++++++++++-------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 3da4ec3e6..28e926058 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -244,34 +244,66 @@ export async function loadBootstrapInfo(): Promise { if (loadedBootstrapInfo !== null) { return loadedBootstrapInfo; } + + /** + * If GRPC_XDS_BOOTSTRAP exists + * then use its value as the name of the bootstrap file. + * + * If the file is missing or the contents of the file are malformed, + * return an error. + */ const bootstrapPath = process.env.GRPC_XDS_BOOTSTRAP; - if (bootstrapPath === undefined) { - return Promise.reject( - new Error( - 'The GRPC_XDS_BOOTSTRAP environment variable needs to be set to the path to the bootstrap file to use xDS' - ) - ); - } - loadedBootstrapInfo = new Promise((resolve, reject) => { - fs.readFile(bootstrapPath, { encoding: 'utf8' }, (err, data) => { - if (err) { - reject( - new Error( - `Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${err.message}` - ) - ); - } - try { - const parsedFile = JSON.parse(data); - resolve(validateBootstrapFile(parsedFile)); - } catch (e) { - reject( - new Error( - `Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${e.message}` - ) - ); - } + if (bootstrapPath) { + loadedBootstrapInfo = new Promise((resolve, reject) => { + fs.readFile(bootstrapPath, { encoding: 'utf8' }, (err, data) => { + if (err) { + reject( + new Error( + `Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${err.message}` + ) + ); + } + try { + const parsedFile = JSON.parse(data); + resolve(validateBootstrapFile(parsedFile)); + } catch (e) { + reject( + new Error( + `Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${e.message}` + ) + ); + } + }); }); - }); - return loadedBootstrapInfo; + return loadedBootstrapInfo; + } + + /** + * Else, if GRPC_XDS_BOOTSTRAP_CONFIG exists + * then use its value as the bootstrap config. + * + * If the value is malformed, return an error. + * + * See: https://github.com/grpc/grpc-node/issues/1868 + */ + const bootstrapConfig = process.env.GRPC_XDS_BOOTSTRAP_CONFIG; + if (bootstrapConfig) { + try { + const parsedConfig = JSON.parse(bootstrapConfig); + const loadedBootstrapInfoValue = validateBootstrapFile(parsedConfig); + loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue); + } catch (e) { + throw new Error( + `Failed to parse xDS bootstrap config from process.env.GRPC_XDS_BOOTSTRAP_CONFIG with error ${e.message}` + ); + } + + return loadedBootstrapInfo; + } + + return Promise.reject( + new Error( + 'The GRPC_XDS_BOOTSTRAP environment variable needs to be set to the path to the bootstrap file to use xDS' + ) + ); } From 6404ef7014b24d1cba2434fd57af7f0d85cd92e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Huan=20LI=20=28=E6=9D=8E=E5=8D=93=E6=A1=93=29?= Date: Mon, 2 Aug 2021 11:32:21 +0800 Subject: [PATCH 053/694] better error msg for both env vars --- packages/grpc-js-xds/src/xds-bootstrap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 28e926058..830ad0bba 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -303,7 +303,7 @@ export async function loadBootstrapInfo(): Promise { return Promise.reject( new Error( - 'The GRPC_XDS_BOOTSTRAP environment variable needs to be set to the path to the bootstrap file to use xDS' + 'The GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG environment variables need to be set to the path to the bootstrap file to use xDS' ) ); } From d9bbd013f4b3090f780f7916ac1b5ff66a891e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Huan=20=28=E6=9D=8E=E5=8D=93=E6=A1=93=29?= Date: Tue, 3 Aug 2021 00:31:07 +0800 Subject: [PATCH 054/694] Update xds-bootstrap.ts --- packages/grpc-js-xds/src/xds-bootstrap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 830ad0bba..64a7bcb94 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -294,7 +294,7 @@ export async function loadBootstrapInfo(): Promise { loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue); } catch (e) { throw new Error( - `Failed to parse xDS bootstrap config from process.env.GRPC_XDS_BOOTSTRAP_CONFIG with error ${e.message}` + `Failed to parse xDS bootstrap config from environment variable GRPC_XDS_BOOTSTRAP_CONFIG with error ${e.message}` ); } From faaad56c73a2f8982e92f8c71f218c78bb3408bc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 14:57:58 -0700 Subject: [PATCH 055/694] grpc-js-xds: Regenerate files with new proto-loader version --- packages/grpc-js-xds/src/generated/ads.ts | 10 +- packages/grpc-js-xds/src/generated/cluster.ts | 2 +- .../grpc-js-xds/src/generated/endpoint.ts | 2 +- .../envoy/api/v2/DeltaDiscoveryRequest.ts | 8 +- .../envoy/api/v2/DiscoveryRequest.ts | 8 +- .../envoy/api/v2/DiscoveryResponse.ts | 4 +- .../src/generated/envoy/api/v2/Resource.ts | 4 +- .../generated/envoy/api/v2/core/Address.ts | 8 +- .../envoy/api/v2/core/AsyncDataSource.ts | 8 +- .../envoy/api/v2/core/BackoffStrategy.ts | 8 +- .../generated/envoy/api/v2/core/BindConfig.ts | 8 +- .../envoy/api/v2/core/BuildVersion.ts | 8 +- .../generated/envoy/api/v2/core/CidrRange.ts | 4 +- .../generated/envoy/api/v2/core/Extension.ts | 4 +- .../envoy/api/v2/core/HeaderValueOption.ts | 8 +- .../generated/envoy/api/v2/core/HttpUri.ts | 4 +- .../generated/envoy/api/v2/core/Metadata.ts | 2 +- .../src/generated/envoy/api/v2/core/Node.ts | 12 +- .../envoy/api/v2/core/RemoteDataSource.ts | 8 +- .../envoy/api/v2/core/RetryPolicy.ts | 8 +- .../envoy/api/v2/core/RuntimeFeatureFlag.ts | 4 +- .../api/v2/core/RuntimeFractionalPercent.ts | 4 +- .../envoy/api/v2/core/TcpKeepalive.ts | 12 +- .../envoy/api/v2/core/TransportSocket.ts | 8 +- .../envoy/api/v2/endpoint/ClusterStats.ts | 4 +- .../api/v2/endpoint/UpstreamEndpointStats.ts | 8 +- .../api/v2/endpoint/UpstreamLocalityStats.ts | 4 +- .../envoy/config/accesslog/v3/AccessLog.ts | 8 +- .../config/accesslog/v3/AccessLogFilter.ts | 48 ++-- .../config/accesslog/v3/ComparisonFilter.ts | 4 +- .../config/accesslog/v3/DurationFilter.ts | 4 +- .../config/accesslog/v3/ExtensionFilter.ts | 4 +- .../envoy/config/accesslog/v3/HeaderFilter.ts | 4 +- .../config/accesslog/v3/MetadataFilter.ts | 8 +- .../config/accesslog/v3/RuntimeFilter.ts | 4 +- .../config/accesslog/v3/StatusCodeFilter.ts | 4 +- .../config/cluster/v3/CircuitBreakers.ts | 32 +-- .../envoy/config/cluster/v3/Cluster.ts | 210 +++++++++--------- .../config/cluster/v3/ClusterCollection.ts | 4 +- .../envoy/config/cluster/v3/Filter.ts | 4 +- .../config/cluster/v3/LoadBalancingPolicy.ts | 4 +- .../config/cluster/v3/OutlierDetection.ts | 80 +++---- .../config/cluster/v3/UpstreamBindConfig.ts | 4 +- .../cluster/v3/UpstreamConnectionOptions.ts | 4 +- .../generated/envoy/config/core/v3/Address.ts | 12 +- .../envoy/config/core/v3/ApiConfigSource.ts | 12 +- .../envoy/config/core/v3/AsyncDataSource.ts | 8 +- .../envoy/config/core/v3/BackoffStrategy.ts | 8 +- .../envoy/config/core/v3/BindConfig.ts | 8 +- .../envoy/config/core/v3/BuildVersion.ts | 8 +- .../envoy/config/core/v3/CidrRange.ts | 4 +- .../envoy/config/core/v3/ConfigSource.ts | 16 +- .../config/core/v3/EventServiceConfig.ts | 4 +- .../envoy/config/core/v3/Extension.ts | 4 +- .../config/core/v3/ExtensionConfigSource.ts | 8 +- .../config/core/v3/GrpcProtocolOptions.ts | 4 +- .../envoy/config/core/v3/GrpcService.ts | 78 +++---- .../envoy/config/core/v3/HeaderValueOption.ts | 8 +- .../envoy/config/core/v3/HealthCheck.ts | 100 ++++----- .../config/core/v3/Http1ProtocolOptions.ts | 16 +- .../config/core/v3/Http2ProtocolOptions.ts | 52 ++--- .../config/core/v3/HttpProtocolOptions.ts | 16 +- .../generated/envoy/config/core/v3/HttpUri.ts | 4 +- .../envoy/config/core/v3/KeepaliveSettings.ts | 12 +- .../envoy/config/core/v3/Metadata.ts | 2 +- .../generated/envoy/config/core/v3/Node.ts | 12 +- .../envoy/config/core/v3/RateLimitSettings.ts | 8 +- .../envoy/config/core/v3/RemoteDataSource.ts | 8 +- .../envoy/config/core/v3/RetryPolicy.ts | 8 +- .../config/core/v3/RuntimeFeatureFlag.ts | 4 +- .../core/v3/RuntimeFractionalPercent.ts | 4 +- .../envoy/config/core/v3/RuntimePercent.ts | 4 +- .../core/v3/SubstitutionFormatString.ts | 8 +- .../envoy/config/core/v3/TcpKeepalive.ts | 12 +- .../envoy/config/core/v3/TransportSocket.ts | 4 +- .../config/core/v3/TypedExtensionConfig.ts | 4 +- .../endpoint/v3/ClusterLoadAssignment.ts | 18 +- .../envoy/config/endpoint/v3/ClusterStats.ts | 4 +- .../envoy/config/endpoint/v3/Endpoint.ts | 8 +- .../envoy/config/endpoint/v3/LbEndpoint.ts | 12 +- .../config/endpoint/v3/LocalityLbEndpoints.ts | 12 +- .../endpoint/v3/UpstreamEndpointStats.ts | 8 +- .../endpoint/v3/UpstreamLocalityStats.ts | 4 +- .../envoy/config/listener/v3/ApiListener.ts | 4 +- .../envoy/config/listener/v3/Filter.ts | 8 +- .../envoy/config/listener/v3/FilterChain.ts | 28 +-- .../config/listener/v3/FilterChainMatch.ts | 8 +- .../envoy/config/listener/v3/Listener.ts | 72 +++--- .../config/listener/v3/ListenerFilter.ts | 8 +- .../v3/ListenerFilterChainMatchPredicate.ts | 16 +- .../config/listener/v3/UdpListenerConfig.ts | 4 +- .../envoy/config/route/v3/CorsPolicy.ts | 12 +- .../envoy/config/route/v3/Decorator.ts | 4 +- .../config/route/v3/DirectResponseAction.ts | 4 +- .../envoy/config/route/v3/FilterAction.ts | 4 +- .../envoy/config/route/v3/FilterConfig.ts | 4 +- .../envoy/config/route/v3/HeaderMatcher.ts | 8 +- .../envoy/config/route/v3/HedgePolicy.ts | 8 +- .../config/route/v3/InternalRedirectPolicy.ts | 4 +- .../config/route/v3/QueryParameterMatcher.ts | 4 +- .../envoy/config/route/v3/RateLimit.ts | 64 +++--- .../envoy/config/route/v3/RedirectAction.ts | 4 +- .../envoy/config/route/v3/RetryPolicy.ts | 40 ++-- .../generated/envoy/config/route/v3/Route.ts | 38 ++-- .../envoy/config/route/v3/RouteAction.ts | 128 +++++------ .../config/route/v3/RouteConfiguration.ts | 12 +- .../envoy/config/route/v3/RouteMatch.ts | 32 +-- .../route/v3/ScopedRouteConfiguration.ts | 4 +- .../envoy/config/route/v3/Tracing.ts | 12 +- .../generated/envoy/config/route/v3/Vhds.ts | 4 +- .../envoy/config/route/v3/VirtualHost.ts | 22 +- .../envoy/config/route/v3/WeightedCluster.ts | 14 +- .../envoy/config/trace/v3/Tracing.ts | 8 +- .../v3/HttpConnectionManager.ts | 116 +++++----- .../http_connection_manager/v3/HttpFilter.ts | 8 +- .../v3/LocalReplyConfig.ts | 4 +- .../network/http_connection_manager/v3/Rds.ts | 4 +- .../v3/RequestIDExtension.ts | 4 +- .../v3/ResponseMapper.ts | 16 +- .../http_connection_manager/v3/ScopedRds.ts | 4 +- .../v3/ScopedRoutes.ts | 24 +- .../v2/AggregatedDiscoveryService.ts | 6 + .../v3/AggregatedDiscoveryService.ts | 6 + .../discovery/v3/DeltaDiscoveryRequest.ts | 8 +- .../discovery/v3/DeltaDiscoveryResponse.ts | 4 +- .../service/discovery/v3/DiscoveryRequest.ts | 8 +- .../service/discovery/v3/DiscoveryResponse.ts | 4 +- .../envoy/service/discovery/v3/Resource.ts | 12 +- .../load_stats/v2/LoadReportingService.ts | 5 + .../service/load_stats/v2/LoadStatsRequest.ts | 4 +- .../load_stats/v2/LoadStatsResponse.ts | 4 +- .../load_stats/v3/LoadReportingService.ts | 5 + .../service/load_stats/v3/LoadStatsRequest.ts | 4 +- .../load_stats/v3/LoadStatsResponse.ts | 4 +- .../envoy/type/matcher/v3/DoubleMatcher.ts | 4 +- .../envoy/type/matcher/v3/ListMatcher.ts | 4 +- .../envoy/type/matcher/v3/MetadataMatcher.ts | 4 +- .../matcher/v3/RegexMatchAndSubstitute.ts | 4 +- .../envoy/type/matcher/v3/RegexMatcher.ts | 8 +- .../envoy/type/matcher/v3/StringMatcher.ts | 4 +- .../envoy/type/matcher/v3/ValueMatcher.ts | 16 +- .../envoy/type/metadata/v3/MetadataKind.ts | 16 +- .../envoy/type/tracing/v3/CustomTag.ts | 24 +- .../google/protobuf/DescriptorProto.ts | 4 +- .../google/protobuf/EnumDescriptorProto.ts | 4 +- .../generated/google/protobuf/EnumOptions.ts | 3 + .../protobuf/EnumValueDescriptorProto.ts | 4 +- .../google/protobuf/EnumValueOptions.ts | 5 + .../google/protobuf/FieldDescriptorProto.ts | 4 +- .../generated/google/protobuf/FieldOptions.ts | 11 +- .../google/protobuf/FileDescriptorProto.ts | 8 +- .../generated/google/protobuf/FileOptions.ts | 6 + .../google/protobuf/MessageOptions.ts | 6 + .../google/protobuf/MethodDescriptorProto.ts | 4 +- .../google/protobuf/OneofDescriptorProto.ts | 4 +- .../google/protobuf/ServiceDescriptorProto.ts | 4 +- .../src/generated/google/protobuf/Struct.ts | 2 +- .../src/generated/google/protobuf/Value.ts | 8 +- .../src/generated/http_connection_manager.ts | 2 +- .../grpc-js-xds/src/generated/listener.ts | 2 +- packages/grpc-js-xds/src/generated/lrs.ts | 10 +- packages/grpc-js-xds/src/generated/route.ts | 2 +- .../grpc-js-xds/src/generated/typed_struct.ts | 2 +- .../src/generated/udpa/type/v1/TypedStruct.ts | 4 +- .../src/generated/validate/DurationRules.ts | 20 +- .../src/generated/validate/FieldRules.ts | 88 ++++---- .../src/generated/validate/MapRules.ts | 8 +- .../src/generated/validate/RepeatedRules.ts | 4 +- .../src/generated/validate/TimestampRules.ts | 24 +- .../generated/xds/core/v3/CollectionEntry.ts | 12 +- .../generated/xds/core/v3/ResourceLocator.ts | 8 +- 171 files changed, 1180 insertions(+), 1131 deletions(-) diff --git a/packages/grpc-js-xds/src/generated/ads.ts b/packages/grpc-js-xds/src/generated/ads.ts index 665374958..f8e336131 100644 --- a/packages/grpc-js-xds/src/generated/ads.ts +++ b/packages/grpc-js-xds/src/generated/ads.ts @@ -1,8 +1,8 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; -import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v2_AggregatedDiscoveryServiceClient } from './envoy/service/discovery/v2/AggregatedDiscoveryService'; -import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v3_AggregatedDiscoveryServiceClient } from './envoy/service/discovery/v3/AggregatedDiscoveryService'; +import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v2_AggregatedDiscoveryServiceClient, AggregatedDiscoveryServiceDefinition as _envoy_service_discovery_v2_AggregatedDiscoveryServiceDefinition } from './envoy/service/discovery/v2/AggregatedDiscoveryService'; +import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v3_AggregatedDiscoveryServiceClient, AggregatedDiscoveryServiceDefinition as _envoy_service_discovery_v3_AggregatedDiscoveryServiceDefinition } from './envoy/service/discovery/v3/AggregatedDiscoveryService'; type SubtypeConstructor any, Subtype> = { new(...args: ConstructorParameters): Subtype; @@ -102,7 +102,7 @@ export interface ProtoGrpcType { * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover * the multiplexed singleton APIs at the Envoy instance and management server. */ - AggregatedDiscoveryService: SubtypeConstructor & { service: ServiceDefinition } + AggregatedDiscoveryService: SubtypeConstructor & { service: _envoy_service_discovery_v2_AggregatedDiscoveryServiceDefinition } } v3: { AdsDummy: MessageTypeDefinition @@ -114,7 +114,7 @@ export interface ProtoGrpcType { * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover * the multiplexed singleton APIs at the Envoy instance and management server. */ - AggregatedDiscoveryService: SubtypeConstructor & { service: ServiceDefinition } + AggregatedDiscoveryService: SubtypeConstructor & { service: _envoy_service_discovery_v3_AggregatedDiscoveryServiceDefinition } DeltaDiscoveryRequest: MessageTypeDefinition DeltaDiscoveryResponse: MessageTypeDefinition DiscoveryRequest: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index 5e4683580..bc4465a30 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; type SubtypeConstructor any, Subtype> = { diff --git a/packages/grpc-js-xds/src/generated/endpoint.ts b/packages/grpc-js-xds/src/generated/endpoint.ts index dda6a78da..630bdeb62 100644 --- a/packages/grpc-js-xds/src/generated/endpoint.ts +++ b/packages/grpc-js-xds/src/generated/endpoint.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; type SubtypeConstructor any, Subtype> = { diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts index 11cbe8c2c..20ddb1b14 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts @@ -42,7 +42,7 @@ export interface DeltaDiscoveryRequest { /** * The node making the request. */ - 'node'?: (_envoy_api_v2_core_Node); + 'node'?: (_envoy_api_v2_core_Node | null); /** * Type of the resource that is being requested, e.g. * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". @@ -99,7 +99,7 @@ export interface DeltaDiscoveryRequest { * failed to update configuration. The *message* field in *error_details* * provides the Envoy internal exception related to the failure. */ - 'error_detail'?: (_google_rpc_Status); + 'error_detail'?: (_google_rpc_Status | null); } /** @@ -141,7 +141,7 @@ export interface DeltaDiscoveryRequest__Output { /** * The node making the request. */ - 'node'?: (_envoy_api_v2_core_Node__Output); + 'node': (_envoy_api_v2_core_Node__Output | null); /** * Type of the resource that is being requested, e.g. * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". @@ -198,5 +198,5 @@ export interface DeltaDiscoveryRequest__Output { * failed to update configuration. The *message* field in *error_details* * provides the Envoy internal exception related to the failure. */ - 'error_detail'?: (_google_rpc_Status__Output); + 'error_detail': (_google_rpc_Status__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts index 2150c41bc..2386e71c4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts @@ -22,7 +22,7 @@ export interface DiscoveryRequest { /** * The node making the request. */ - 'node'?: (_envoy_api_v2_core_Node); + 'node'?: (_envoy_api_v2_core_Node | null); /** * List of resources to subscribe to, e.g. list of cluster names or a route * configuration name. If this is empty, all resources for the API are @@ -53,7 +53,7 @@ export interface DiscoveryRequest { * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. */ - 'error_detail'?: (_google_rpc_Status); + 'error_detail'?: (_google_rpc_Status | null); } /** @@ -75,7 +75,7 @@ export interface DiscoveryRequest__Output { /** * The node making the request. */ - 'node'?: (_envoy_api_v2_core_Node__Output); + 'node': (_envoy_api_v2_core_Node__Output | null); /** * List of resources to subscribe to, e.g. list of cluster names or a route * configuration name. If this is empty, all resources for the API are @@ -106,5 +106,5 @@ export interface DiscoveryRequest__Output { * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. */ - 'error_detail'?: (_google_rpc_Status__Output); + 'error_detail': (_google_rpc_Status__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts index dd7e70d4d..179143c67 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts @@ -52,7 +52,7 @@ export interface DiscoveryResponse { * [#not-implemented-hide:] * The control plane instance that sent the response. */ - 'control_plane'?: (_envoy_api_v2_core_ControlPlane); + 'control_plane'?: (_envoy_api_v2_core_ControlPlane | null); } /** @@ -104,5 +104,5 @@ export interface DiscoveryResponse__Output { * [#not-implemented-hide:] * The control plane instance that sent the response. */ - 'control_plane'?: (_envoy_api_v2_core_ControlPlane__Output); + 'control_plane': (_envoy_api_v2_core_ControlPlane__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts index 4804201bd..6ce856ee7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts @@ -11,7 +11,7 @@ export interface Resource { /** * The resource being tracked. */ - 'resource'?: (_google_protobuf_Any); + 'resource'?: (_google_protobuf_Any | null); /** * The resource's name, to distinguish it from others of the same type of resource. */ @@ -31,7 +31,7 @@ export interface Resource__Output { /** * The resource being tracked. */ - 'resource'?: (_google_protobuf_Any__Output); + 'resource': (_google_protobuf_Any__Output | null); /** * The resource's name, to distinguish it from others of the same type of resource. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts index b12c8d9b0..65044b74a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts @@ -9,8 +9,8 @@ import type { Pipe as _envoy_api_v2_core_Pipe, Pipe__Output as _envoy_api_v2_cor * management servers. */ export interface Address { - 'socket_address'?: (_envoy_api_v2_core_SocketAddress); - 'pipe'?: (_envoy_api_v2_core_Pipe); + 'socket_address'?: (_envoy_api_v2_core_SocketAddress | null); + 'pipe'?: (_envoy_api_v2_core_Pipe | null); 'address'?: "socket_address"|"pipe"; } @@ -20,7 +20,7 @@ export interface Address { * management servers. */ export interface Address__Output { - 'socket_address'?: (_envoy_api_v2_core_SocketAddress__Output); - 'pipe'?: (_envoy_api_v2_core_Pipe__Output); + 'socket_address'?: (_envoy_api_v2_core_SocketAddress__Output | null); + 'pipe'?: (_envoy_api_v2_core_Pipe__Output | null); 'address': "socket_address"|"pipe"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts index fb312bb40..139f82689 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts @@ -10,11 +10,11 @@ export interface AsyncDataSource { /** * Local async data source. */ - 'local'?: (_envoy_api_v2_core_DataSource); + 'local'?: (_envoy_api_v2_core_DataSource | null); /** * Remote async data source. */ - 'remote'?: (_envoy_api_v2_core_RemoteDataSource); + 'remote'?: (_envoy_api_v2_core_RemoteDataSource | null); 'specifier'?: "local"|"remote"; } @@ -25,10 +25,10 @@ export interface AsyncDataSource__Output { /** * Local async data source. */ - 'local'?: (_envoy_api_v2_core_DataSource__Output); + 'local'?: (_envoy_api_v2_core_DataSource__Output | null); /** * Remote async data source. */ - 'remote'?: (_envoy_api_v2_core_RemoteDataSource__Output); + 'remote'?: (_envoy_api_v2_core_RemoteDataSource__Output | null); 'specifier': "local"|"remote"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts index 48f9d904d..173aff0e1 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts @@ -11,7 +11,7 @@ export interface BackoffStrategy { * be greater than zero and less than or equal to :ref:`max_interval * `. */ - 'base_interval'?: (_google_protobuf_Duration); + 'base_interval'?: (_google_protobuf_Duration | null); /** * Specifies the maximum interval between retries. This parameter is optional, * but must be greater than or equal to the :ref:`base_interval @@ -19,7 +19,7 @@ export interface BackoffStrategy { * is 10 times the :ref:`base_interval * `. */ - 'max_interval'?: (_google_protobuf_Duration); + 'max_interval'?: (_google_protobuf_Duration | null); } /** @@ -31,7 +31,7 @@ export interface BackoffStrategy__Output { * be greater than zero and less than or equal to :ref:`max_interval * `. */ - 'base_interval'?: (_google_protobuf_Duration__Output); + 'base_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the maximum interval between retries. This parameter is optional, * but must be greater than or equal to the :ref:`base_interval @@ -39,5 +39,5 @@ export interface BackoffStrategy__Output { * is 10 times the :ref:`base_interval * `. */ - 'max_interval'?: (_google_protobuf_Duration__Output); + 'max_interval': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts index f989494ea..d4765c1ba 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts @@ -8,7 +8,7 @@ export interface BindConfig { /** * The address to bind to when creating a socket. */ - 'source_address'?: (_envoy_api_v2_core_SocketAddress); + 'source_address'?: (_envoy_api_v2_core_SocketAddress | null); /** * Whether to set the *IP_FREEBIND* option when creating the socket. When this * flag is set to true, allows the :ref:`source_address @@ -18,7 +18,7 @@ export interface BindConfig { * flag is not set (default), the socket is not modified, i.e. the option is * neither enabled nor disabled. */ - 'freebind'?: (_google_protobuf_BoolValue); + 'freebind'?: (_google_protobuf_BoolValue | null); /** * Additional socket options that may not be present in Envoy source code or * precompiled binaries. @@ -30,7 +30,7 @@ export interface BindConfig__Output { /** * The address to bind to when creating a socket. */ - 'source_address'?: (_envoy_api_v2_core_SocketAddress__Output); + 'source_address': (_envoy_api_v2_core_SocketAddress__Output | null); /** * Whether to set the *IP_FREEBIND* option when creating the socket. When this * flag is set to true, allows the :ref:`source_address @@ -40,7 +40,7 @@ export interface BindConfig__Output { * flag is not set (default), the socket is not modified, i.e. the option is * neither enabled nor disabled. */ - 'freebind'?: (_google_protobuf_BoolValue__Output); + 'freebind': (_google_protobuf_BoolValue__Output | null); /** * Additional socket options that may not be present in Envoy source code or * precompiled binaries. diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts index 50a44a3b2..a33015d89 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts @@ -11,12 +11,12 @@ export interface BuildVersion { /** * SemVer version of extension. */ - 'version'?: (_envoy_type_SemanticVersion); + 'version'?: (_envoy_type_SemanticVersion | null); /** * Free-form build information. * Envoy defines several well known keys in the source/common/version/version.h file */ - 'metadata'?: (_google_protobuf_Struct); + 'metadata'?: (_google_protobuf_Struct | null); } /** @@ -27,10 +27,10 @@ export interface BuildVersion__Output { /** * SemVer version of extension. */ - 'version'?: (_envoy_type_SemanticVersion__Output); + 'version': (_envoy_type_SemanticVersion__Output | null); /** * Free-form build information. * Envoy defines several well known keys in the source/common/version/version.h file */ - 'metadata'?: (_google_protobuf_Struct__Output); + 'metadata': (_google_protobuf_Struct__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts index 00c67734f..cca1bc2d8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts @@ -14,7 +14,7 @@ export interface CidrRange { /** * Length of prefix, e.g. 0, 32. */ - 'prefix_len'?: (_google_protobuf_UInt32Value); + 'prefix_len'?: (_google_protobuf_UInt32Value | null); } /** @@ -29,5 +29,5 @@ export interface CidrRange__Output { /** * Length of prefix, e.g. 0, 32. */ - 'prefix_len'?: (_google_protobuf_UInt32Value__Output); + 'prefix_len': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts index 418c7a360..67c5e1736 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts @@ -31,7 +31,7 @@ export interface Extension { * of other extensions and the Envoy API. * This field is not set when extension did not provide version information. */ - 'version'?: (_envoy_api_v2_core_BuildVersion); + 'version'?: (_envoy_api_v2_core_BuildVersion | null); /** * Indicates that the extension is present but was disabled via dynamic configuration. */ @@ -67,7 +67,7 @@ export interface Extension__Output { * of other extensions and the Envoy API. * This field is not set when extension did not provide version information. */ - 'version'?: (_envoy_api_v2_core_BuildVersion__Output); + 'version': (_envoy_api_v2_core_BuildVersion__Output | null); /** * Indicates that the extension is present but was disabled via dynamic configuration. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts index b90bd85e1..12421d8f4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts @@ -10,12 +10,12 @@ export interface HeaderValueOption { /** * Header name/value pair that this option applies to. */ - 'header'?: (_envoy_api_v2_core_HeaderValue); + 'header'?: (_envoy_api_v2_core_HeaderValue | null); /** * Should the value be appended? If true (default), the value is appended to * existing values. */ - 'append'?: (_google_protobuf_BoolValue); + 'append'?: (_google_protobuf_BoolValue | null); } /** @@ -25,10 +25,10 @@ export interface HeaderValueOption__Output { /** * Header name/value pair that this option applies to. */ - 'header'?: (_envoy_api_v2_core_HeaderValue__Output); + 'header': (_envoy_api_v2_core_HeaderValue__Output | null); /** * Should the value be appended? If true (default), the value is appended to * existing values. */ - 'append'?: (_google_protobuf_BoolValue__Output); + 'append': (_google_protobuf_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts index 19711dde7..c206a2454 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts @@ -30,7 +30,7 @@ export interface HttpUri { /** * Sets the maximum duration in milliseconds that a response can take to arrive upon request. */ - 'timeout'?: (_google_protobuf_Duration); + 'timeout'?: (_google_protobuf_Duration | null); /** * Specify how `uri` is to be fetched. Today, this requires an explicit * cluster, but in the future we may support dynamic cluster creation or @@ -68,7 +68,7 @@ export interface HttpUri__Output { /** * Sets the maximum duration in milliseconds that a response can take to arrive upon request. */ - 'timeout'?: (_google_protobuf_Duration__Output); + 'timeout': (_google_protobuf_Duration__Output | null); /** * Specify how `uri` is to be fetched. Today, this requires an explicit * cluster, but in the future we may support dynamic cluster creation or diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts index ca823a27c..7a6871eeb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts @@ -63,5 +63,5 @@ export interface Metadata__Output { * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* * namespace is reserved for Envoy's built-in filters. */ - 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct__Output}); + 'filter_metadata': ({[key: string]: _google_protobuf_Struct__Output}); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts index c6ddea9d4..ddf10f08b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts @@ -41,11 +41,11 @@ export interface Node { * Opaque metadata extending the node identifier. Envoy will pass this * directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct); + 'metadata'?: (_google_protobuf_Struct | null); /** * Locality specifying where the Envoy instance is running. */ - 'locality'?: (_envoy_api_v2_core_Locality); + 'locality'?: (_envoy_api_v2_core_Locality | null); /** * This is motivated by informing a management server during canary which * version of Envoy is being tested in a heterogeneous fleet. This will be set @@ -66,7 +66,7 @@ export interface Node { /** * Structured version of the entity requesting config. */ - 'user_agent_build_version'?: (_envoy_api_v2_core_BuildVersion); + 'user_agent_build_version'?: (_envoy_api_v2_core_BuildVersion | null); /** * List of extensions and their versions supported by the node. */ @@ -124,11 +124,11 @@ export interface Node__Output { * Opaque metadata extending the node identifier. Envoy will pass this * directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct__Output); + 'metadata': (_google_protobuf_Struct__Output | null); /** * Locality specifying where the Envoy instance is running. */ - 'locality'?: (_envoy_api_v2_core_Locality__Output); + 'locality': (_envoy_api_v2_core_Locality__Output | null); /** * This is motivated by informing a management server during canary which * version of Envoy is being tested in a heterogeneous fleet. This will be set @@ -149,7 +149,7 @@ export interface Node__Output { /** * Structured version of the entity requesting config. */ - 'user_agent_build_version'?: (_envoy_api_v2_core_BuildVersion__Output); + 'user_agent_build_version'?: (_envoy_api_v2_core_BuildVersion__Output | null); /** * List of extensions and their versions supported by the node. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts index 93e722eef..516dbf864 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts @@ -10,7 +10,7 @@ export interface RemoteDataSource { /** * The HTTP URI to fetch the remote data. */ - 'http_uri'?: (_envoy_api_v2_core_HttpUri); + 'http_uri'?: (_envoy_api_v2_core_HttpUri | null); /** * SHA256 string for verifying data. */ @@ -18,7 +18,7 @@ export interface RemoteDataSource { /** * Retry policy for fetching remote data. */ - 'retry_policy'?: (_envoy_api_v2_core_RetryPolicy); + 'retry_policy'?: (_envoy_api_v2_core_RetryPolicy | null); } /** @@ -28,7 +28,7 @@ export interface RemoteDataSource__Output { /** * The HTTP URI to fetch the remote data. */ - 'http_uri'?: (_envoy_api_v2_core_HttpUri__Output); + 'http_uri': (_envoy_api_v2_core_HttpUri__Output | null); /** * SHA256 string for verifying data. */ @@ -36,5 +36,5 @@ export interface RemoteDataSource__Output { /** * Retry policy for fetching remote data. */ - 'retry_policy'?: (_envoy_api_v2_core_RetryPolicy__Output); + 'retry_policy': (_envoy_api_v2_core_RetryPolicy__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts index 27c1096b7..7ff35e375 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts @@ -12,12 +12,12 @@ export interface RetryPolicy { * This parameter is optional, in which case the default base interval is 1000 milliseconds. The * default maximum interval is 10 times the base interval. */ - 'retry_back_off'?: (_envoy_api_v2_core_BackoffStrategy); + 'retry_back_off'?: (_envoy_api_v2_core_BackoffStrategy | null); /** * Specifies the allowed number of retries. This parameter is optional and * defaults to 1. */ - 'num_retries'?: (_google_protobuf_UInt32Value); + 'num_retries'?: (_google_protobuf_UInt32Value | null); } /** @@ -29,10 +29,10 @@ export interface RetryPolicy__Output { * This parameter is optional, in which case the default base interval is 1000 milliseconds. The * default maximum interval is 10 times the base interval. */ - 'retry_back_off'?: (_envoy_api_v2_core_BackoffStrategy__Output); + 'retry_back_off': (_envoy_api_v2_core_BackoffStrategy__Output | null); /** * Specifies the allowed number of retries. This parameter is optional and * defaults to 1. */ - 'num_retries'?: (_google_protobuf_UInt32Value__Output); + 'num_retries': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts index 47cf24097..4ad57de46 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts @@ -9,7 +9,7 @@ export interface RuntimeFeatureFlag { /** * Default value if runtime value is not available. */ - 'default_value'?: (_google_protobuf_BoolValue); + 'default_value'?: (_google_protobuf_BoolValue | null); /** * Runtime key to get value for comparison. This value is used if defined. The boolean value must * be represented via its @@ -25,7 +25,7 @@ export interface RuntimeFeatureFlag__Output { /** * Default value if runtime value is not available. */ - 'default_value'?: (_google_protobuf_BoolValue__Output); + 'default_value': (_google_protobuf_BoolValue__Output | null); /** * Runtime key to get value for comparison. This value is used if defined. The boolean value must * be represented via its diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts index 08e29de1f..a168af60c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts @@ -18,7 +18,7 @@ export interface RuntimeFractionalPercent { /** * Default value if the runtime value's for the numerator/denominator keys are not available. */ - 'default_value'?: (_envoy_type_FractionalPercent); + 'default_value'?: (_envoy_type_FractionalPercent | null); /** * Runtime key for a YAML representation of a FractionalPercent. */ @@ -41,7 +41,7 @@ export interface RuntimeFractionalPercent__Output { /** * Default value if the runtime value's for the numerator/denominator keys are not available. */ - 'default_value'?: (_envoy_type_FractionalPercent__Output); + 'default_value': (_envoy_type_FractionalPercent__Output | null); /** * Runtime key for a YAML representation of a FractionalPercent. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts index 394a54feb..e9d805c76 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts @@ -8,18 +8,18 @@ export interface TcpKeepalive { * the connection is dead. Default is to use the OS level configuration (unless * overridden, Linux defaults to 9.) */ - 'keepalive_probes'?: (_google_protobuf_UInt32Value); + 'keepalive_probes'?: (_google_protobuf_UInt32Value | null); /** * The number of seconds a connection needs to be idle before keep-alive probes * start being sent. Default is to use the OS level configuration (unless * overridden, Linux defaults to 7200s (i.e., 2 hours.) */ - 'keepalive_time'?: (_google_protobuf_UInt32Value); + 'keepalive_time'?: (_google_protobuf_UInt32Value | null); /** * The number of seconds between keep-alive probes. Default is to use the OS * level configuration (unless overridden, Linux defaults to 75s.) */ - 'keepalive_interval'?: (_google_protobuf_UInt32Value); + 'keepalive_interval'?: (_google_protobuf_UInt32Value | null); } export interface TcpKeepalive__Output { @@ -28,16 +28,16 @@ export interface TcpKeepalive__Output { * the connection is dead. Default is to use the OS level configuration (unless * overridden, Linux defaults to 9.) */ - 'keepalive_probes'?: (_google_protobuf_UInt32Value__Output); + 'keepalive_probes': (_google_protobuf_UInt32Value__Output | null); /** * The number of seconds a connection needs to be idle before keep-alive probes * start being sent. Default is to use the OS level configuration (unless * overridden, Linux defaults to 7200s (i.e., 2 hours.) */ - 'keepalive_time'?: (_google_protobuf_UInt32Value__Output); + 'keepalive_time': (_google_protobuf_UInt32Value__Output | null); /** * The number of seconds between keep-alive probes. Default is to use the OS * level configuration (unless overridden, Linux defaults to 75s.) */ - 'keepalive_interval'?: (_google_protobuf_UInt32Value__Output); + 'keepalive_interval': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts index b45767eb2..87fbcaf38 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts @@ -15,8 +15,8 @@ export interface TransportSocket { * socket implementation. */ 'name'?: (string); - 'config'?: (_google_protobuf_Struct); - 'typed_config'?: (_google_protobuf_Any); + 'config'?: (_google_protobuf_Struct | null); + 'typed_config'?: (_google_protobuf_Any | null); /** * Implementation specific configuration which depends on the implementation being instantiated. * See the supported transport socket implementations for further documentation. @@ -36,8 +36,8 @@ export interface TransportSocket__Output { * socket implementation. */ 'name': (string); - 'config'?: (_google_protobuf_Struct__Output); - 'typed_config'?: (_google_protobuf_Any__Output); + 'config'?: (_google_protobuf_Struct__Output | null); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Implementation specific configuration which depends on the implementation being instantiated. * See the supported transport socket implementations for further documentation. diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts index 4b5c30f4c..3609ea1e4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts @@ -57,7 +57,7 @@ export interface ClusterStats { * and the *LoadStatsResponse* message sent from the management server, this may be longer than * the requested load reporting interval in the *LoadStatsResponse*. */ - 'load_report_interval'?: (_google_protobuf_Duration); + 'load_report_interval'?: (_google_protobuf_Duration | null); /** * Information about deliberately dropped requests for each category specified * in the DropOverload policy. @@ -102,7 +102,7 @@ export interface ClusterStats__Output { * and the *LoadStatsResponse* message sent from the management server, this may be longer than * the requested load reporting interval in the *LoadStatsResponse*. */ - 'load_report_interval'?: (_google_protobuf_Duration__Output); + 'load_report_interval': (_google_protobuf_Duration__Output | null); /** * Information about deliberately dropped requests for each category specified * in the DropOverload policy. diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts index 5b5c62b32..e4551bd37 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts @@ -13,7 +13,7 @@ export interface UpstreamEndpointStats { /** * Upstream host address. */ - 'address'?: (_envoy_api_v2_core_Address); + 'address'?: (_envoy_api_v2_core_Address | null); /** * The total number of requests successfully completed by the endpoints in the * locality. These include non-5xx responses for HTTP, where errors @@ -46,7 +46,7 @@ export interface UpstreamEndpointStats { * Opaque and implementation dependent metadata of the * endpoint. Envoy will pass this directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct); + 'metadata'?: (_google_protobuf_Struct | null); /** * The total number of requests that were issued to this endpoint * since the last report. A single TCP connection, HTTP or gRPC @@ -63,7 +63,7 @@ export interface UpstreamEndpointStats__Output { /** * Upstream host address. */ - 'address'?: (_envoy_api_v2_core_Address__Output); + 'address': (_envoy_api_v2_core_Address__Output | null); /** * The total number of requests successfully completed by the endpoints in the * locality. These include non-5xx responses for HTTP, where errors @@ -96,7 +96,7 @@ export interface UpstreamEndpointStats__Output { * Opaque and implementation dependent metadata of the * endpoint. Envoy will pass this directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct__Output); + 'metadata': (_google_protobuf_Struct__Output | null); /** * The total number of requests that were issued to this endpoint * since the last report. A single TCP connection, HTTP or gRPC diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts index a1b20897e..df4d4eb89 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts @@ -18,7 +18,7 @@ export interface UpstreamLocalityStats { * Name of zone, region and optionally endpoint group these metrics were * collected from. Zone and region names could be empty if unknown. */ - 'locality'?: (_envoy_api_v2_core_Locality); + 'locality'?: (_envoy_api_v2_core_Locality | null); /** * The total number of requests successfully completed by the endpoints in the * locality. @@ -69,7 +69,7 @@ export interface UpstreamLocalityStats__Output { * Name of zone, region and optionally endpoint group these metrics were * collected from. Zone and region names could be empty if unknown. */ - 'locality'?: (_envoy_api_v2_core_Locality__Output); + 'locality': (_envoy_api_v2_core_Locality__Output | null); /** * The total number of requests successfully completed by the endpoints in the * locality. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts index 413f569ce..369f36bc2 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts @@ -16,8 +16,8 @@ export interface AccessLog { /** * Filter which is used to determine if the access log needs to be written. */ - 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter); - 'typed_config'?: (_google_protobuf_Any); + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter | null); + 'typed_config'?: (_google_protobuf_Any | null); /** * Custom configuration that depends on the access log being instantiated. * Built-in configurations include: @@ -45,8 +45,8 @@ export interface AccessLog__Output { /** * Filter which is used to determine if the access log needs to be written. */ - 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter__Output); - 'typed_config'?: (_google_protobuf_Any__Output); + 'filter': (_envoy_config_accesslog_v3_AccessLogFilter__Output | null); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Custom configuration that depends on the access log being instantiated. * Built-in configurations include: diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts index 9470b6eaa..85a952c9c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts @@ -20,51 +20,51 @@ export interface AccessLogFilter { /** * Status code filter. */ - 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter); + 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter | null); /** * Duration filter. */ - 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter); + 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter | null); /** * Not health check filter. */ - 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter); + 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter | null); /** * Traceable filter. */ - 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter); + 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter | null); /** * Runtime filter. */ - 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter); + 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter | null); /** * And filter. */ - 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter); + 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter | null); /** * Or filter. */ - 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter); + 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter | null); /** * Header filter. */ - 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter); + 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter | null); /** * Response flag filter. */ - 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter); + 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter | null); /** * gRPC status filter. */ - 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter); + 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter | null); /** * Extension filter. */ - 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter); + 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter | null); /** * Metadata Filter */ - 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter); + 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter | null); 'filter_specifier'?: "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; } @@ -75,50 +75,50 @@ export interface AccessLogFilter__Output { /** * Status code filter. */ - 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter__Output); + 'status_code_filter'?: (_envoy_config_accesslog_v3_StatusCodeFilter__Output | null); /** * Duration filter. */ - 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter__Output); + 'duration_filter'?: (_envoy_config_accesslog_v3_DurationFilter__Output | null); /** * Not health check filter. */ - 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter__Output); + 'not_health_check_filter'?: (_envoy_config_accesslog_v3_NotHealthCheckFilter__Output | null); /** * Traceable filter. */ - 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter__Output); + 'traceable_filter'?: (_envoy_config_accesslog_v3_TraceableFilter__Output | null); /** * Runtime filter. */ - 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter__Output); + 'runtime_filter'?: (_envoy_config_accesslog_v3_RuntimeFilter__Output | null); /** * And filter. */ - 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter__Output); + 'and_filter'?: (_envoy_config_accesslog_v3_AndFilter__Output | null); /** * Or filter. */ - 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter__Output); + 'or_filter'?: (_envoy_config_accesslog_v3_OrFilter__Output | null); /** * Header filter. */ - 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter__Output); + 'header_filter'?: (_envoy_config_accesslog_v3_HeaderFilter__Output | null); /** * Response flag filter. */ - 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter__Output); + 'response_flag_filter'?: (_envoy_config_accesslog_v3_ResponseFlagFilter__Output | null); /** * gRPC status filter. */ - 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter__Output); + 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter__Output | null); /** * Extension filter. */ - 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter__Output); + 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter__Output | null); /** * Metadata Filter */ - 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter__Output); + 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter__Output | null); 'filter_specifier': "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts index 118a47a75..07893f2dd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts @@ -30,7 +30,7 @@ export interface ComparisonFilter { /** * Value to compare against. */ - 'value'?: (_envoy_config_core_v3_RuntimeUInt32); + 'value'?: (_envoy_config_core_v3_RuntimeUInt32 | null); } /** @@ -44,5 +44,5 @@ export interface ComparisonFilter__Output { /** * Value to compare against. */ - 'value'?: (_envoy_config_core_v3_RuntimeUInt32__Output); + 'value': (_envoy_config_core_v3_RuntimeUInt32__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts index 03b0500e8..ee61e55fa 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts @@ -9,7 +9,7 @@ export interface DurationFilter { /** * Comparison. */ - 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter); + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter | null); } /** @@ -19,5 +19,5 @@ export interface DurationFilter__Output { /** * Comparison. */ - 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter__Output); + 'comparison': (_envoy_config_accesslog_v3_ComparisonFilter__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts index 127618eef..19edb671b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ExtensionFilter.ts @@ -11,7 +11,7 @@ export interface ExtensionFilter { * match a statically registered filter. */ 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * Custom configuration that depends on the filter being instantiated. */ @@ -27,7 +27,7 @@ export interface ExtensionFilter__Output { * match a statically registered filter. */ 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Custom configuration that depends on the filter being instantiated. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts index 9cda884b3..294084d1c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/HeaderFilter.ts @@ -10,7 +10,7 @@ export interface HeaderFilter { * Only requests with a header which matches the specified HeaderMatcher will * pass the filter check. */ - 'header'?: (_envoy_config_route_v3_HeaderMatcher); + 'header'?: (_envoy_config_route_v3_HeaderMatcher | null); } /** @@ -21,5 +21,5 @@ export interface HeaderFilter__Output { * Only requests with a header which matches the specified HeaderMatcher will * pass the filter check. */ - 'header'?: (_envoy_config_route_v3_HeaderMatcher__Output); + 'header': (_envoy_config_route_v3_HeaderMatcher__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts index 7fdd1d447..cd821fef6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/MetadataFilter.ts @@ -17,12 +17,12 @@ export interface MetadataFilter { * access_log_hint metadata, set the filter to "envoy.common" and the path to * "access_log_hint", and the value to "true". */ - 'matcher'?: (_envoy_type_matcher_v3_MetadataMatcher); + 'matcher'?: (_envoy_type_matcher_v3_MetadataMatcher | null); /** * Default result if the key does not exist in dynamic metadata: if unset or * true, then log; if false, then don't log. */ - 'match_if_key_not_found'?: (_google_protobuf_BoolValue); + 'match_if_key_not_found'?: (_google_protobuf_BoolValue | null); } /** @@ -39,10 +39,10 @@ export interface MetadataFilter__Output { * access_log_hint metadata, set the filter to "envoy.common" and the path to * "access_log_hint", and the value to "true". */ - 'matcher'?: (_envoy_type_matcher_v3_MetadataMatcher__Output); + 'matcher': (_envoy_type_matcher_v3_MetadataMatcher__Output | null); /** * Default result if the key does not exist in dynamic metadata: if unset or * true, then log; if false, then don't log. */ - 'match_if_key_not_found'?: (_google_protobuf_BoolValue__Output); + 'match_if_key_not_found': (_google_protobuf_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts index 9388f49ec..e605fa1a4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts @@ -16,7 +16,7 @@ export interface RuntimeFilter { * The default sampling percentage. If not specified, defaults to 0% with * denominator of 100. */ - 'percent_sampled'?: (_envoy_type_v3_FractionalPercent); + 'percent_sampled'?: (_envoy_type_v3_FractionalPercent | null); /** * By default, sampling pivots on the header * :ref:`x-request-id` being @@ -51,7 +51,7 @@ export interface RuntimeFilter__Output { * The default sampling percentage. If not specified, defaults to 0% with * denominator of 100. */ - 'percent_sampled'?: (_envoy_type_v3_FractionalPercent__Output); + 'percent_sampled': (_envoy_type_v3_FractionalPercent__Output | null); /** * By default, sampling pivots on the header * :ref:`x-request-id` being diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts index 313ed86ca..a071b5cbb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/StatusCodeFilter.ts @@ -9,7 +9,7 @@ export interface StatusCodeFilter { /** * Comparison. */ - 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter); + 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter | null); } /** @@ -19,5 +19,5 @@ export interface StatusCodeFilter__Output { /** * Comparison. */ - 'comparison'?: (_envoy_config_accesslog_v3_ComparisonFilter__Output); + 'comparison': (_envoy_config_accesslog_v3_ComparisonFilter__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts index c4f4add69..e64afb780 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts @@ -12,14 +12,14 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget * * This parameter is optional. Defaults to 20%. */ - 'budget_percent'?: (_envoy_type_v3_Percent); + 'budget_percent'?: (_envoy_type_v3_Percent | null); /** * Specifies the minimum retry concurrency allowed for the retry budget. The limit on the * number of active retries may never go below this number. * * This parameter is optional. Defaults to 3. */ - 'min_retry_concurrency'?: (_google_protobuf_UInt32Value); + 'min_retry_concurrency'?: (_google_protobuf_UInt32Value | null); } export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget__Output { @@ -30,14 +30,14 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget * * This parameter is optional. Defaults to 20%. */ - 'budget_percent'?: (_envoy_type_v3_Percent__Output); + 'budget_percent': (_envoy_type_v3_Percent__Output | null); /** * Specifies the minimum retry concurrency allowed for the retry budget. The limit on the * number of active retries may never go below this number. * * This parameter is optional. Defaults to 3. */ - 'min_retry_concurrency'?: (_google_protobuf_UInt32Value__Output); + 'min_retry_concurrency': (_google_protobuf_UInt32Value__Output | null); } /** @@ -55,22 +55,22 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { * The maximum number of connections that Envoy will make to the upstream * cluster. If not specified, the default is 1024. */ - 'max_connections'?: (_google_protobuf_UInt32Value); + 'max_connections'?: (_google_protobuf_UInt32Value | null); /** * The maximum number of pending requests that Envoy will allow to the * upstream cluster. If not specified, the default is 1024. */ - 'max_pending_requests'?: (_google_protobuf_UInt32Value); + 'max_pending_requests'?: (_google_protobuf_UInt32Value | null); /** * The maximum number of parallel requests that Envoy will make to the * upstream cluster. If not specified, the default is 1024. */ - 'max_requests'?: (_google_protobuf_UInt32Value); + 'max_requests'?: (_google_protobuf_UInt32Value | null); /** * The maximum number of parallel retries that Envoy will allow to the * upstream cluster. If not specified, the default is 3. */ - 'max_retries'?: (_google_protobuf_UInt32Value); + 'max_retries'?: (_google_protobuf_UInt32Value | null); /** * Specifies a limit on concurrent retries in relation to the number of active requests. This * parameter is optional. @@ -80,7 +80,7 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { * If this field is set, the retry budget will override any configured retry circuit * breaker. */ - 'retry_budget'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget); + 'retry_budget'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget | null); /** * If track_remaining is true, then stats will be published that expose * the number of resources remaining until the circuit breakers open. If @@ -99,7 +99,7 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { * :ref:`Circuit Breaking ` for * more details. */ - 'max_connection_pools'?: (_google_protobuf_UInt32Value); + 'max_connection_pools'?: (_google_protobuf_UInt32Value | null); } /** @@ -117,22 +117,22 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { * The maximum number of connections that Envoy will make to the upstream * cluster. If not specified, the default is 1024. */ - 'max_connections'?: (_google_protobuf_UInt32Value__Output); + 'max_connections': (_google_protobuf_UInt32Value__Output | null); /** * The maximum number of pending requests that Envoy will allow to the * upstream cluster. If not specified, the default is 1024. */ - 'max_pending_requests'?: (_google_protobuf_UInt32Value__Output); + 'max_pending_requests': (_google_protobuf_UInt32Value__Output | null); /** * The maximum number of parallel requests that Envoy will make to the * upstream cluster. If not specified, the default is 1024. */ - 'max_requests'?: (_google_protobuf_UInt32Value__Output); + 'max_requests': (_google_protobuf_UInt32Value__Output | null); /** * The maximum number of parallel retries that Envoy will allow to the * upstream cluster. If not specified, the default is 3. */ - 'max_retries'?: (_google_protobuf_UInt32Value__Output); + 'max_retries': (_google_protobuf_UInt32Value__Output | null); /** * Specifies a limit on concurrent retries in relation to the number of active requests. This * parameter is optional. @@ -142,7 +142,7 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { * If this field is set, the retry budget will override any configured retry circuit * breaker. */ - 'retry_budget'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget__Output); + 'retry_budget': (_envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget__Output | null); /** * If track_remaining is true, then stats will be published that expose * the number of resources remaining until the circuit breakers open. If @@ -161,7 +161,7 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { * :ref:`Circuit Breaking ` for * more details. */ - 'max_connection_pools'?: (_google_protobuf_UInt32Value__Output); + 'max_connection_pools': (_google_protobuf_UInt32Value__Output | null); } /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts index fa76fba89..c20440704 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts @@ -56,9 +56,9 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { * .. note:: * The specified percent will be truncated to the nearest 1%. */ - 'healthy_panic_threshold'?: (_envoy_type_v3_Percent); - 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig); - 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig); + 'healthy_panic_threshold'?: (_envoy_type_v3_Percent | null); + 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig | null); + 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig | null); /** * If set, all health check/weight/metadata updates that happen within this duration will be * merged and delivered in one shot when the duration expires. The start of the duration is when @@ -75,7 +75,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { * because merging those updates isn't currently safe. See * https://github.com/envoyproxy/envoy/pull/3941. */ - 'update_merge_window'?: (_google_protobuf_Duration); + 'update_merge_window'?: (_google_protobuf_Duration | null); /** * If set to true, Envoy will :ref:`exclude ` new hosts * when computing load balancing weights until they have been health checked for the first time. @@ -90,7 +90,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { /** * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ - 'consistent_hashing_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig); + 'consistent_hashing_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig | null); 'locality_config_specifier'?: "zone_aware_lb_config"|"locality_weighted_lb_config"; } @@ -107,9 +107,9 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { * .. note:: * The specified percent will be truncated to the nearest 1%. */ - 'healthy_panic_threshold'?: (_envoy_type_v3_Percent__Output); - 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output); - 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output); + 'healthy_panic_threshold': (_envoy_type_v3_Percent__Output | null); + 'zone_aware_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConfig__Output | null); + 'locality_weighted_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeightedLbConfig__Output | null); /** * If set, all health check/weight/metadata updates that happen within this duration will be * merged and delivered in one shot when the duration expires. The start of the duration is when @@ -126,7 +126,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { * because merging those updates isn't currently safe. See * https://github.com/envoyproxy/envoy/pull/3941. */ - 'update_merge_window'?: (_google_protobuf_Duration__Output); + 'update_merge_window': (_google_protobuf_Duration__Output | null); /** * If set to true, Envoy will :ref:`exclude ` new hosts * when computing load balancing weights until they have been health checked for the first time. @@ -141,7 +141,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { /** * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ - 'consistent_hashing_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output); + 'consistent_hashing_lb_config': (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output | null); 'locality_config_specifier': "zone_aware_lb_config"|"locality_weighted_lb_config"; } @@ -174,7 +174,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashi * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts * being probed, so use a higher value if you require better performance. */ - 'hash_balance_factor'?: (_google_protobuf_UInt32Value); + 'hash_balance_factor'?: (_google_protobuf_UInt32Value | null); } /** @@ -206,7 +206,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashi * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts * being probed, so use a higher value if you require better performance. */ - 'hash_balance_factor'?: (_google_protobuf_UInt32Value__Output); + 'hash_balance_factor': (_google_protobuf_UInt32Value__Output | null); } /** @@ -221,7 +221,7 @@ export interface _envoy_config_cluster_v3_Cluster_CustomClusterType { * Cluster specific configuration which depends on the cluster being instantiated. * See the supported cluster for further documentation. */ - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); } /** @@ -236,7 +236,7 @@ export interface _envoy_config_cluster_v3_Cluster_CustomClusterType__Output { * Cluster specific configuration which depends on the cluster being instantiated. * See the supported cluster for further documentation. */ - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config': (_google_protobuf_Any__Output | null); } // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto @@ -303,7 +303,7 @@ export interface _envoy_config_cluster_v3_Cluster_EdsClusterConfig { /** * Configuration for the source of EDS updates for this Cluster. */ - 'eds_config'?: (_envoy_config_core_v3_ConfigSource); + 'eds_config'?: (_envoy_config_core_v3_ConfigSource | null); /** * Optional alternative to cluster name to present to EDS. This does not * have the same restrictions as cluster name, i.e. it may be arbitrary @@ -319,7 +319,7 @@ export interface _envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output { /** * Configuration for the source of EDS updates for this Cluster. */ - 'eds_config'?: (_envoy_config_core_v3_ConfigSource__Output); + 'eds_config': (_envoy_config_core_v3_ConfigSource__Output | null); /** * Optional alternative to cluster name to present to EDS. This does not * have the same restrictions as cluster name, i.e. it may be arbitrary @@ -420,7 +420,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { * is the same as a fallback_policy of * :ref:`NO_FALLBACK`. */ - 'default_subset'?: (_google_protobuf_Struct); + 'default_subset'?: (_google_protobuf_Struct | null); /** * For each entry, LbEndpoint.Metadata's * *envoy.lb* namespace is traversed and a subset is created for each unique @@ -497,7 +497,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { * is the same as a fallback_policy of * :ref:`NO_FALLBACK`. */ - 'default_subset'?: (_google_protobuf_Struct__Output); + 'default_subset': (_google_protobuf_Struct__Output | null); /** * For each entry, LbEndpoint.Metadata's * *envoy.lb* namespace is traversed and a subset is created for each unique @@ -693,7 +693,7 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig { * The number of random healthy hosts from which the host with the fewest active requests will * be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. */ - 'choice_count'?: (_google_protobuf_UInt32Value); + 'choice_count'?: (_google_protobuf_UInt32Value | null); /** * The following formula is used to calculate the dynamic weights when hosts have different load * balancing weights: @@ -719,7 +719,7 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig { * .. note:: * This setting only takes effect if all host weights are not equal. */ - 'active_request_bias'?: (_envoy_config_core_v3_RuntimeDouble); + 'active_request_bias'?: (_envoy_config_core_v3_RuntimeDouble | null); } /** @@ -730,7 +730,7 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output { * The number of random healthy hosts from which the host with the fewest active requests will * be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. */ - 'choice_count'?: (_google_protobuf_UInt32Value__Output); + 'choice_count': (_google_protobuf_UInt32Value__Output | null); /** * The following formula is used to calculate the dynamic weights when hosts have different load * balancing weights: @@ -756,7 +756,7 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output { * .. note:: * This setting only takes effect if all host weights are not equal. */ - 'active_request_bias'?: (_envoy_config_core_v3_RuntimeDouble__Output); + 'active_request_bias': (_envoy_config_core_v3_RuntimeDouble__Output | null); } /** @@ -784,7 +784,7 @@ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig { * upstream as it was before. Increasing the table size reduces the amount of disruption. * The table size must be prime number. If it is not specified, the default is 65537. */ - 'table_size'?: (_google_protobuf_UInt64Value); + 'table_size'?: (_google_protobuf_UInt64Value | null); } /** @@ -798,7 +798,7 @@ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output { * upstream as it was before. Increasing the table size reduces the amount of disruption. * The table size must be prime number. If it is not specified, the default is 65537. */ - 'table_size'?: (_google_protobuf_UInt64Value__Output); + 'table_size': (_google_protobuf_UInt64Value__Output | null); } /** @@ -876,7 +876,7 @@ export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy { * This is limited somewhat arbitrarily to 3 because preconnecting too aggressively can * harm latency more than the preconnecting helps. */ - 'per_upstream_preconnect_ratio'?: (_google_protobuf_DoubleValue); + 'per_upstream_preconnect_ratio'?: (_google_protobuf_DoubleValue | null); /** * Indicates how many many streams (rounded up) can be anticipated across a cluster for each * stream, useful for low QPS services. This is currently supported for a subset of @@ -901,7 +901,7 @@ export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy { * basically preconnecting max(predictive-preconnect, per-upstream-preconnect), for each * upstream. */ - 'predictive_preconnect_ratio'?: (_google_protobuf_DoubleValue); + 'predictive_preconnect_ratio'?: (_google_protobuf_DoubleValue | null); } export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output { @@ -931,7 +931,7 @@ export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output { * This is limited somewhat arbitrarily to 3 because preconnecting too aggressively can * harm latency more than the preconnecting helps. */ - 'per_upstream_preconnect_ratio'?: (_google_protobuf_DoubleValue__Output); + 'per_upstream_preconnect_ratio': (_google_protobuf_DoubleValue__Output | null); /** * Indicates how many many streams (rounded up) can be anticipated across a cluster for each * stream, useful for low QPS services. This is currently supported for a subset of @@ -956,7 +956,7 @@ export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output { * basically preconnecting max(predictive-preconnect, per-upstream-preconnect), for each * upstream. */ - 'predictive_preconnect_ratio'?: (_google_protobuf_DoubleValue__Output); + 'predictive_preconnect_ratio': (_google_protobuf_DoubleValue__Output | null); } export interface _envoy_config_cluster_v3_Cluster_RefreshRate { @@ -965,14 +965,14 @@ export interface _envoy_config_cluster_v3_Cluster_RefreshRate { * than zero and less than * :ref:`max_interval `. */ - 'base_interval'?: (_google_protobuf_Duration); + 'base_interval'?: (_google_protobuf_Duration | null); /** * Specifies the maximum interval between refreshes. This parameter is optional, but must be * greater than or equal to the * :ref:`base_interval ` if set. The default * is 10 times the :ref:`base_interval `. */ - 'max_interval'?: (_google_protobuf_Duration); + 'max_interval'?: (_google_protobuf_Duration | null); } export interface _envoy_config_cluster_v3_Cluster_RefreshRate__Output { @@ -981,14 +981,14 @@ export interface _envoy_config_cluster_v3_Cluster_RefreshRate__Output { * than zero and less than * :ref:`max_interval `. */ - 'base_interval'?: (_google_protobuf_Duration__Output); + 'base_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the maximum interval between refreshes. This parameter is optional, but must be * greater than or equal to the * :ref:`base_interval ` if set. The default * is 10 times the :ref:`base_interval `. */ - 'max_interval'?: (_google_protobuf_Duration__Output); + 'max_interval': (_google_protobuf_Duration__Output | null); } /** @@ -1002,7 +1002,7 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig { * to 1024 entries, and limited to 8M entries. See also * :ref:`maximum_ring_size`. */ - 'minimum_ring_size'?: (_google_protobuf_UInt64Value); + 'minimum_ring_size'?: (_google_protobuf_UInt64Value | null); /** * The hash function used to hash hosts onto the ketama ring. The value defaults to * :ref:`XX_HASH`. @@ -1013,7 +1013,7 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig { * to further constrain resource use. See also * :ref:`minimum_ring_size`. */ - 'maximum_ring_size'?: (_google_protobuf_UInt64Value); + 'maximum_ring_size'?: (_google_protobuf_UInt64Value | null); } /** @@ -1027,7 +1027,7 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output { * to 1024 entries, and limited to 8M entries. See also * :ref:`maximum_ring_size`. */ - 'minimum_ring_size'?: (_google_protobuf_UInt64Value__Output); + 'minimum_ring_size': (_google_protobuf_UInt64Value__Output | null); /** * The hash function used to hash hosts onto the ketama ring. The value defaults to * :ref:`XX_HASH`. @@ -1038,7 +1038,7 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output { * to further constrain resource use. See also * :ref:`minimum_ring_size`. */ - 'maximum_ring_size'?: (_google_protobuf_UInt64Value__Output); + 'maximum_ring_size': (_google_protobuf_UInt64Value__Output | null); } /** @@ -1057,11 +1057,11 @@ export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch { * The endpoint's metadata entry in *envoy.transport_socket_match* is used to match * against the values specified in this field. */ - 'match'?: (_google_protobuf_Struct); + 'match'?: (_google_protobuf_Struct | null); /** * The configuration of the transport socket. */ - 'transport_socket'?: (_envoy_config_core_v3_TransportSocket); + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); } /** @@ -1080,11 +1080,11 @@ export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output { * The endpoint's metadata entry in *envoy.transport_socket_match* is used to match * against the values specified in this field. */ - 'match'?: (_google_protobuf_Struct__Output); + 'match': (_google_protobuf_Struct__Output | null); /** * The configuration of the transport socket. */ - 'transport_socket'?: (_envoy_config_core_v3_TransportSocket__Output); + 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); } /** @@ -1098,7 +1098,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConf * * :ref:`runtime values `. * * :ref:`Zone aware routing support `. */ - 'routing_enabled'?: (_envoy_type_v3_Percent); + 'routing_enabled'?: (_envoy_type_v3_Percent | null); /** * Configures minimum upstream cluster size required for zone aware routing * If upstream cluster size is less than specified, zone aware routing is not performed @@ -1106,7 +1106,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConf * * :ref:`runtime values `. * * :ref:`Zone aware routing support `. */ - 'min_cluster_size'?: (_google_protobuf_UInt64Value); + 'min_cluster_size'?: (_google_protobuf_UInt64Value | null); /** * If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic * mode`. Instead, the cluster will fail all @@ -1127,7 +1127,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConf * * :ref:`runtime values `. * * :ref:`Zone aware routing support `. */ - 'routing_enabled'?: (_envoy_type_v3_Percent__Output); + 'routing_enabled': (_envoy_type_v3_Percent__Output | null); /** * Configures minimum upstream cluster size required for zone aware routing * If upstream cluster size is less than specified, zone aware routing is not performed @@ -1135,7 +1135,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConf * * :ref:`runtime values `. * * :ref:`Zone aware routing support `. */ - 'min_cluster_size'?: (_google_protobuf_UInt64Value__Output); + 'min_cluster_size': (_google_protobuf_UInt64Value__Output | null); /** * If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic * mode`. Instead, the cluster will fail all @@ -1166,16 +1166,16 @@ export interface Cluster { /** * Configuration to use for EDS updates for the Cluster. */ - 'eds_cluster_config'?: (_envoy_config_cluster_v3_Cluster_EdsClusterConfig); + 'eds_cluster_config'?: (_envoy_config_cluster_v3_Cluster_EdsClusterConfig | null); /** * The timeout for new network connections to hosts in the cluster. */ - 'connect_timeout'?: (_google_protobuf_Duration); + 'connect_timeout'?: (_google_protobuf_Duration | null); /** * Soft limit on size of the cluster’s connections read and write buffers. If * unspecified, an implementation defined default is applied (1MiB). */ - 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value); + 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); /** * The :ref:`load balancer type ` to use * when picking a host in the cluster. @@ -1195,11 +1195,11 @@ export interface Cluster { * implementations. If not specified, there is no limit. Setting this * parameter to 1 will effectively disable keep alive. */ - 'max_requests_per_connection'?: (_google_protobuf_UInt32Value); + 'max_requests_per_connection'?: (_google_protobuf_UInt32Value | null); /** * Optional :ref:`circuit breaking ` for the cluster. */ - 'circuit_breakers'?: (_envoy_config_cluster_v3_CircuitBreakers); + 'circuit_breakers'?: (_envoy_config_cluster_v3_CircuitBreakers | null); /** * Additional options when handling HTTP1 requests. * This has been deprecated in favor of http_protocol_options fields in the in the @@ -1210,7 +1210,7 @@ export interface Cluster { * ` * for example usage. */ - 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions); + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions | null); /** * Even if default HTTP2 protocol options are desired, this field must be * set so that Envoy will assume that the upstream supports HTTP/2 when @@ -1226,7 +1226,7 @@ export interface Cluster { * ` * for example usage. */ - 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions); + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); /** * If the DNS refresh rate is specified and the cluster type is either * :ref:`STRICT_DNS`, @@ -1238,7 +1238,7 @@ export interface Cluster { * and :ref:`LOGICAL_DNS` * this setting is ignored. */ - 'dns_refresh_rate'?: (_google_protobuf_Duration); + 'dns_refresh_rate'?: (_google_protobuf_Duration | null); /** * The DNS IP address resolution policy. If this setting is not specified, the * value defaults to @@ -1266,7 +1266,7 @@ export interface Cluster { * Each of the configuration values can be overridden via * :ref:`runtime values `. */ - 'outlier_detection'?: (_envoy_config_cluster_v3_OutlierDetection); + 'outlier_detection'?: (_envoy_config_cluster_v3_OutlierDetection | null); /** * The interval for removing stale hosts from a cluster type * :ref:`ORIGINAL_DST`. @@ -1282,21 +1282,21 @@ export interface Cluster { * :ref:`ORIGINAL_DST` * this setting is ignored. */ - 'cleanup_interval'?: (_google_protobuf_Duration); + 'cleanup_interval'?: (_google_protobuf_Duration | null); /** * Optional configuration used to bind newly established upstream connections. * This overrides any bind_config specified in the bootstrap proto. * If the address and port are empty, no bind will be performed. */ - 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig); + 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig | null); /** * Configuration for load balancing subsetting. */ - 'lb_subset_config'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig); + 'lb_subset_config'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig | null); /** * Optional configuration for the Ring Hash load balancing policy. */ - 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig); + 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig | null); /** * Optional custom transport socket implementation to use for upstream connections. * To setup TLS, set a transport socket with name `tls` and @@ -1304,7 +1304,7 @@ export interface Cluster { * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ - 'transport_socket'?: (_envoy_config_core_v3_TransportSocket); + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); /** * The Metadata field can be used to provide additional information about the * cluster. It can be used for stats, logging, and varying filter behavior. @@ -1312,7 +1312,7 @@ export interface Cluster { * will need the information. For instance, if the metadata is intended for * the Router filter, the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_config_core_v3_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * Determines how Envoy selects the protocol used to speak to upstream hosts. * This has been deprecated in favor of setting explicit protocol selection @@ -1325,7 +1325,7 @@ export interface Cluster { /** * Common configuration for all load balancer implementations. */ - 'common_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig); + 'common_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig | null); /** * An optional alternative to the cluster name to be used while emitting stats. * Any ``:`` in the name will be converted to ``_`` when emitting statistics. This should not be @@ -1345,11 +1345,11 @@ export interface Cluster { * ` * for example usage. */ - 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions); + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions | null); /** * Optional options for upstream connections. */ - 'upstream_connection_options'?: (_envoy_config_cluster_v3_UpstreamConnectionOptions); + 'upstream_connection_options'?: (_envoy_config_cluster_v3_UpstreamConnectionOptions | null); /** * If an upstream host becomes unhealthy (as determined by the configured health checks * or outlier detection), immediately close all connections to the failed host. @@ -1384,11 +1384,11 @@ export interface Cluster { * Setting this allows non-EDS cluster types to contain embedded EDS equivalent * :ref:`endpoint assignments`. */ - 'load_assignment'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment); + 'load_assignment'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment | null); /** * Optional configuration for the Original Destination load balancing policy. */ - 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig); + 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig | null); /** * The extension_protocol_options field is used to provide extension-specific protocol options * for upstream connections. The key should match the extension filter name, such as @@ -1400,11 +1400,11 @@ export interface Cluster { /** * Optional configuration for the LeastRequest load balancing policy. */ - 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig); + 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig | null); /** * The custom cluster type. */ - 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType); + 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType | null); /** * Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, * cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS @@ -1422,7 +1422,7 @@ export interface Cluster { * :ref:`lb_policy` field has the value * :ref:`LOAD_BALANCING_POLICY_CONFIG`. */ - 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy); + 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy | null); /** * [#not-implemented-hide:] * If present, tells the client where to send load reports via LRS. If not present, the @@ -1439,7 +1439,7 @@ export interface Cluster { * maybe by allowing LRS to go on the ADS stream, or maybe by moving some of the negotiation * from the LRS stream here.] */ - 'lrs_server'?: (_envoy_config_core_v3_ConfigSource); + 'lrs_server'?: (_envoy_config_core_v3_ConfigSource | null); /** * Configuration to use different transport sockets for different endpoints. * The entry of *envoy.transport_socket_match* in the @@ -1502,7 +1502,7 @@ export interface Cluster { * :ref:`LOGICAL_DNS` this setting is * ignored. */ - 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate); + 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate | null); /** * [#next-major-version: Reconcile DNS options in a single message.] * Always use TCP queries instead of UDP queries for DNS lookups. @@ -1523,7 +1523,7 @@ export interface Cluster { * ` * for example usage. */ - 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions); + 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions | null); /** * If track_timeout_budgets is true, the :ref:`timeout budget histograms * ` will be published for each @@ -1556,15 +1556,15 @@ export interface Cluster { * CONNECT only if a custom filter indicates it is appropriate, the custom factories * can be registered and configured here. */ - 'upstream_config'?: (_envoy_config_core_v3_TypedExtensionConfig); + 'upstream_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); /** * Configuration to track optional cluster stats. */ - 'track_cluster_stats'?: (_envoy_config_cluster_v3_TrackClusterStats); + 'track_cluster_stats'?: (_envoy_config_cluster_v3_TrackClusterStats | null); /** * Preconnect configuration for this cluster. */ - 'preconnect_policy'?: (_envoy_config_cluster_v3_Cluster_PreconnectPolicy); + 'preconnect_policy'?: (_envoy_config_cluster_v3_Cluster_PreconnectPolicy | null); /** * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate * connection pool for every downstream connection @@ -1573,7 +1573,7 @@ export interface Cluster { /** * Optional configuration for the Maglev load balancing policy. */ - 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig); + 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig | null); 'cluster_discovery_type'?: "type"|"cluster_type"; /** * Optional configuration for the load balancing algorithm selected by @@ -1609,16 +1609,16 @@ export interface Cluster__Output { /** * Configuration to use for EDS updates for the Cluster. */ - 'eds_cluster_config'?: (_envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output); + 'eds_cluster_config': (_envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output | null); /** * The timeout for new network connections to hosts in the cluster. */ - 'connect_timeout'?: (_google_protobuf_Duration__Output); + 'connect_timeout': (_google_protobuf_Duration__Output | null); /** * Soft limit on size of the cluster’s connections read and write buffers. If * unspecified, an implementation defined default is applied (1MiB). */ - 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value__Output); + 'per_connection_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); /** * The :ref:`load balancer type ` to use * when picking a host in the cluster. @@ -1638,11 +1638,11 @@ export interface Cluster__Output { * implementations. If not specified, there is no limit. Setting this * parameter to 1 will effectively disable keep alive. */ - 'max_requests_per_connection'?: (_google_protobuf_UInt32Value__Output); + 'max_requests_per_connection': (_google_protobuf_UInt32Value__Output | null); /** * Optional :ref:`circuit breaking ` for the cluster. */ - 'circuit_breakers'?: (_envoy_config_cluster_v3_CircuitBreakers__Output); + 'circuit_breakers': (_envoy_config_cluster_v3_CircuitBreakers__Output | null); /** * Additional options when handling HTTP1 requests. * This has been deprecated in favor of http_protocol_options fields in the in the @@ -1653,7 +1653,7 @@ export interface Cluster__Output { * ` * for example usage. */ - 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions__Output); + 'http_protocol_options': (_envoy_config_core_v3_Http1ProtocolOptions__Output | null); /** * Even if default HTTP2 protocol options are desired, this field must be * set so that Envoy will assume that the upstream supports HTTP/2 when @@ -1669,7 +1669,7 @@ export interface Cluster__Output { * ` * for example usage. */ - 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions__Output); + 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); /** * If the DNS refresh rate is specified and the cluster type is either * :ref:`STRICT_DNS`, @@ -1681,7 +1681,7 @@ export interface Cluster__Output { * and :ref:`LOGICAL_DNS` * this setting is ignored. */ - 'dns_refresh_rate'?: (_google_protobuf_Duration__Output); + 'dns_refresh_rate': (_google_protobuf_Duration__Output | null); /** * The DNS IP address resolution policy. If this setting is not specified, the * value defaults to @@ -1709,7 +1709,7 @@ export interface Cluster__Output { * Each of the configuration values can be overridden via * :ref:`runtime values `. */ - 'outlier_detection'?: (_envoy_config_cluster_v3_OutlierDetection__Output); + 'outlier_detection': (_envoy_config_cluster_v3_OutlierDetection__Output | null); /** * The interval for removing stale hosts from a cluster type * :ref:`ORIGINAL_DST`. @@ -1725,21 +1725,21 @@ export interface Cluster__Output { * :ref:`ORIGINAL_DST` * this setting is ignored. */ - 'cleanup_interval'?: (_google_protobuf_Duration__Output); + 'cleanup_interval': (_google_protobuf_Duration__Output | null); /** * Optional configuration used to bind newly established upstream connections. * This overrides any bind_config specified in the bootstrap proto. * If the address and port are empty, no bind will be performed. */ - 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig__Output); + 'upstream_bind_config': (_envoy_config_core_v3_BindConfig__Output | null); /** * Configuration for load balancing subsetting. */ - 'lb_subset_config'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output); + 'lb_subset_config': (_envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output | null); /** * Optional configuration for the Ring Hash load balancing policy. */ - 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output); + 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output | null); /** * Optional custom transport socket implementation to use for upstream connections. * To setup TLS, set a transport socket with name `tls` and @@ -1747,7 +1747,7 @@ export interface Cluster__Output { * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ - 'transport_socket'?: (_envoy_config_core_v3_TransportSocket__Output); + 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); /** * The Metadata field can be used to provide additional information about the * cluster. It can be used for stats, logging, and varying filter behavior. @@ -1755,7 +1755,7 @@ export interface Cluster__Output { * will need the information. For instance, if the metadata is intended for * the Router filter, the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_config_core_v3_Metadata__Output); + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * Determines how Envoy selects the protocol used to speak to upstream hosts. * This has been deprecated in favor of setting explicit protocol selection @@ -1768,7 +1768,7 @@ export interface Cluster__Output { /** * Common configuration for all load balancer implementations. */ - 'common_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig__Output); + 'common_lb_config': (_envoy_config_cluster_v3_Cluster_CommonLbConfig__Output | null); /** * An optional alternative to the cluster name to be used while emitting stats. * Any ``:`` in the name will be converted to ``_`` when emitting statistics. This should not be @@ -1788,11 +1788,11 @@ export interface Cluster__Output { * ` * for example usage. */ - 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions__Output); + 'common_http_protocol_options': (_envoy_config_core_v3_HttpProtocolOptions__Output | null); /** * Optional options for upstream connections. */ - 'upstream_connection_options'?: (_envoy_config_cluster_v3_UpstreamConnectionOptions__Output); + 'upstream_connection_options': (_envoy_config_cluster_v3_UpstreamConnectionOptions__Output | null); /** * If an upstream host becomes unhealthy (as determined by the configured health checks * or outlier detection), immediately close all connections to the failed host. @@ -1827,11 +1827,11 @@ export interface Cluster__Output { * Setting this allows non-EDS cluster types to contain embedded EDS equivalent * :ref:`endpoint assignments`. */ - 'load_assignment'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment__Output); + 'load_assignment': (_envoy_config_endpoint_v3_ClusterLoadAssignment__Output | null); /** * Optional configuration for the Original Destination load balancing policy. */ - 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output); + 'original_dst_lb_config'?: (_envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output | null); /** * The extension_protocol_options field is used to provide extension-specific protocol options * for upstream connections. The key should match the extension filter name, such as @@ -1839,15 +1839,15 @@ export interface Cluster__Output { * specific options. * [#next-major-version: make this a list of typed extensions.] */ - 'typed_extension_protocol_options'?: ({[key: string]: _google_protobuf_Any__Output}); + 'typed_extension_protocol_options': ({[key: string]: _google_protobuf_Any__Output}); /** * Optional configuration for the LeastRequest load balancing policy. */ - 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output); + 'least_request_lb_config'?: (_envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output | null); /** * The custom cluster type. */ - 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType__Output); + 'cluster_type'?: (_envoy_config_cluster_v3_Cluster_CustomClusterType__Output | null); /** * Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, * cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS @@ -1865,7 +1865,7 @@ export interface Cluster__Output { * :ref:`lb_policy` field has the value * :ref:`LOAD_BALANCING_POLICY_CONFIG`. */ - 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy__Output); + 'load_balancing_policy': (_envoy_config_cluster_v3_LoadBalancingPolicy__Output | null); /** * [#not-implemented-hide:] * If present, tells the client where to send load reports via LRS. If not present, the @@ -1882,7 +1882,7 @@ export interface Cluster__Output { * maybe by allowing LRS to go on the ADS stream, or maybe by moving some of the negotiation * from the LRS stream here.] */ - 'lrs_server'?: (_envoy_config_core_v3_ConfigSource__Output); + 'lrs_server': (_envoy_config_core_v3_ConfigSource__Output | null); /** * Configuration to use different transport sockets for different endpoints. * The entry of *envoy.transport_socket_match* in the @@ -1945,7 +1945,7 @@ export interface Cluster__Output { * :ref:`LOGICAL_DNS` this setting is * ignored. */ - 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate__Output); + 'dns_failure_refresh_rate': (_envoy_config_cluster_v3_Cluster_RefreshRate__Output | null); /** * [#next-major-version: Reconcile DNS options in a single message.] * Always use TCP queries instead of UDP queries for DNS lookups. @@ -1966,7 +1966,7 @@ export interface Cluster__Output { * ` * for example usage. */ - 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions__Output); + 'upstream_http_protocol_options': (_envoy_config_core_v3_UpstreamHttpProtocolOptions__Output | null); /** * If track_timeout_budgets is true, the :ref:`timeout budget histograms * ` will be published for each @@ -1999,15 +1999,15 @@ export interface Cluster__Output { * CONNECT only if a custom filter indicates it is appropriate, the custom factories * can be registered and configured here. */ - 'upstream_config'?: (_envoy_config_core_v3_TypedExtensionConfig__Output); + 'upstream_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); /** * Configuration to track optional cluster stats. */ - 'track_cluster_stats'?: (_envoy_config_cluster_v3_TrackClusterStats__Output); + 'track_cluster_stats': (_envoy_config_cluster_v3_TrackClusterStats__Output | null); /** * Preconnect configuration for this cluster. */ - 'preconnect_policy'?: (_envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output); + 'preconnect_policy': (_envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output | null); /** * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate * connection pool for every downstream connection @@ -2016,7 +2016,7 @@ export interface Cluster__Output { /** * Optional configuration for the Maglev load balancing policy. */ - 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output); + 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output | null); 'cluster_discovery_type': "type"|"cluster_type"; /** * Optional configuration for the load balancing algorithm selected by diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts index aab9c6281..8b1394d4b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts @@ -7,7 +7,7 @@ import type { CollectionEntry as _xds_core_v3_CollectionEntry, CollectionEntry__ * [#not-implemented-hide:] */ export interface ClusterCollection { - 'entries'?: (_xds_core_v3_CollectionEntry); + 'entries'?: (_xds_core_v3_CollectionEntry | null); } /** @@ -15,5 +15,5 @@ export interface ClusterCollection { * [#not-implemented-hide:] */ export interface ClusterCollection__Output { - 'entries'?: (_xds_core_v3_CollectionEntry__Output); + 'entries': (_xds_core_v3_CollectionEntry__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts index 53feedb98..b2ccda5e4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts @@ -12,7 +12,7 @@ export interface Filter { * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. */ - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); } export interface Filter__Output { @@ -25,5 +25,5 @@ export interface Filter__Output { * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. */ - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config': (_google_protobuf_Any__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts index 29e343218..128a6458a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts @@ -7,7 +7,7 @@ export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy { * Required. The name of the LB policy. */ 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); } export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output { @@ -15,7 +15,7 @@ export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output { * Required. The name of the LB policy. */ 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config': (_google_protobuf_Any__Output | null); } /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts index f37001691..abae2e270 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts @@ -14,44 +14,44 @@ export interface OutlierDetection { * to 5xx error codes before a consecutive 5xx ejection * occurs. Defaults to 5. */ - 'consecutive_5xx'?: (_google_protobuf_UInt32Value); + 'consecutive_5xx'?: (_google_protobuf_UInt32Value | null); /** * The time interval between ejection analysis sweeps. This can result in * both new ejections as well as hosts being returned to service. Defaults * to 10000ms or 10s. */ - 'interval'?: (_google_protobuf_Duration); + 'interval'?: (_google_protobuf_Duration | null); /** * The base time that a host is ejected for. The real time is equal to the * base time multiplied by the number of times the host has been ejected and is * capped by :ref:`max_ejection_time`. * Defaults to 30000ms or 30s. */ - 'base_ejection_time'?: (_google_protobuf_Duration); + 'base_ejection_time'?: (_google_protobuf_Duration | null); /** * The maximum % of an upstream cluster that can be ejected due to outlier * detection. Defaults to 10% but will eject at least one host regardless of the value. */ - 'max_ejection_percent'?: (_google_protobuf_UInt32Value); + 'max_ejection_percent'?: (_google_protobuf_UInt32Value | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through consecutive 5xx. This setting can be used to disable * ejection or to ramp it up slowly. Defaults to 100. */ - 'enforcing_consecutive_5xx'?: (_google_protobuf_UInt32Value); + 'enforcing_consecutive_5xx'?: (_google_protobuf_UInt32Value | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through success rate statistics. This setting can be used to * disable ejection or to ramp it up slowly. Defaults to 100. */ - 'enforcing_success_rate'?: (_google_protobuf_UInt32Value); + 'enforcing_success_rate'?: (_google_protobuf_UInt32Value | null); /** * The number of hosts in a cluster that must have enough request volume to * detect success rate outliers. If the number of hosts is less than this * setting, outlier detection via success rate statistics is not performed * for any host in the cluster. Defaults to 5. */ - 'success_rate_minimum_hosts'?: (_google_protobuf_UInt32Value); + 'success_rate_minimum_hosts'?: (_google_protobuf_UInt32Value | null); /** * The minimum number of total requests that must be collected in one * interval (as defined by the interval duration above) to include this host @@ -59,7 +59,7 @@ export interface OutlierDetection { * setting, outlier detection via success rate statistics is not performed * for that host. Defaults to 100. */ - 'success_rate_request_volume'?: (_google_protobuf_UInt32Value); + 'success_rate_request_volume'?: (_google_protobuf_UInt32Value | null); /** * This factor is used to determine the ejection threshold for success rate * outlier ejection. The ejection threshold is the difference between the @@ -69,18 +69,18 @@ export interface OutlierDetection { * double. That is, if the desired factor is 1.9, the runtime value should * be 1900. Defaults to 1900. */ - 'success_rate_stdev_factor'?: (_google_protobuf_UInt32Value); + 'success_rate_stdev_factor'?: (_google_protobuf_UInt32Value | null); /** * The number of consecutive gateway failures (502, 503, 504 status codes) * before a consecutive gateway failure ejection occurs. Defaults to 5. */ - 'consecutive_gateway_failure'?: (_google_protobuf_UInt32Value); + 'consecutive_gateway_failure'?: (_google_protobuf_UInt32Value | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through consecutive gateway failures. This setting can be * used to disable ejection or to ramp it up slowly. Defaults to 0. */ - 'enforcing_consecutive_gateway_failure'?: (_google_protobuf_UInt32Value); + 'enforcing_consecutive_gateway_failure'?: (_google_protobuf_UInt32Value | null); /** * Determines whether to distinguish local origin failures from external errors. If set to true * the following configuration parameters are taken into account: @@ -97,7 +97,7 @@ export interface OutlierDetection { * :ref:`split_external_local_origin_errors` * is set to true. */ - 'consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value); + 'consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through consecutive locally originated failures. This setting can be @@ -106,7 +106,7 @@ export interface OutlierDetection { * :ref:`split_external_local_origin_errors` * is set to true. */ - 'enforcing_consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value); + 'enforcing_consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through success rate statistics for locally originated errors. @@ -115,13 +115,13 @@ export interface OutlierDetection { * :ref:`split_external_local_origin_errors` * is set to true. */ - 'enforcing_local_origin_success_rate'?: (_google_protobuf_UInt32Value); + 'enforcing_local_origin_success_rate'?: (_google_protobuf_UInt32Value | null); /** * The failure percentage to use when determining failure percentage-based outlier detection. If * the failure percentage of a given host is greater than or equal to this value, it will be * ejected. Defaults to 85. */ - 'failure_percentage_threshold'?: (_google_protobuf_UInt32Value); + 'failure_percentage_threshold'?: (_google_protobuf_UInt32Value | null); /** * The % chance that a host will be actually ejected when an outlier status is detected through * failure percentage statistics. This setting can be used to disable ejection or to ramp it up @@ -130,32 +130,32 @@ export interface OutlierDetection { * [#next-major-version: setting this without setting failure_percentage_threshold should be * invalid in v4.] */ - 'enforcing_failure_percentage'?: (_google_protobuf_UInt32Value); + 'enforcing_failure_percentage'?: (_google_protobuf_UInt32Value | null); /** * The % chance that a host will be actually ejected when an outlier status is detected through * local-origin failure percentage statistics. This setting can be used to disable ejection or to * ramp it up slowly. Defaults to 0. */ - 'enforcing_failure_percentage_local_origin'?: (_google_protobuf_UInt32Value); + 'enforcing_failure_percentage_local_origin'?: (_google_protobuf_UInt32Value | null); /** * The minimum number of hosts in a cluster in order to perform failure percentage-based ejection. * If the total number of hosts in the cluster is less than this value, failure percentage-based * ejection will not be performed. Defaults to 5. */ - 'failure_percentage_minimum_hosts'?: (_google_protobuf_UInt32Value); + 'failure_percentage_minimum_hosts'?: (_google_protobuf_UInt32Value | null); /** * The minimum number of total requests that must be collected in one interval (as defined by the * interval duration above) to perform failure percentage-based ejection for this host. If the * volume is lower than this setting, failure percentage-based ejection will not be performed for * this host. Defaults to 50. */ - 'failure_percentage_request_volume'?: (_google_protobuf_UInt32Value); + 'failure_percentage_request_volume'?: (_google_protobuf_UInt32Value | null); /** * The maximum time that a host is ejected for. See :ref:`base_ejection_time` * for more information. * Defaults to 300000ms or 300s. */ - 'max_ejection_time'?: (_google_protobuf_Duration); + 'max_ejection_time'?: (_google_protobuf_Duration | null); } /** @@ -169,44 +169,44 @@ export interface OutlierDetection__Output { * to 5xx error codes before a consecutive 5xx ejection * occurs. Defaults to 5. */ - 'consecutive_5xx'?: (_google_protobuf_UInt32Value__Output); + 'consecutive_5xx': (_google_protobuf_UInt32Value__Output | null); /** * The time interval between ejection analysis sweeps. This can result in * both new ejections as well as hosts being returned to service. Defaults * to 10000ms or 10s. */ - 'interval'?: (_google_protobuf_Duration__Output); + 'interval': (_google_protobuf_Duration__Output | null); /** * The base time that a host is ejected for. The real time is equal to the * base time multiplied by the number of times the host has been ejected and is * capped by :ref:`max_ejection_time`. * Defaults to 30000ms or 30s. */ - 'base_ejection_time'?: (_google_protobuf_Duration__Output); + 'base_ejection_time': (_google_protobuf_Duration__Output | null); /** * The maximum % of an upstream cluster that can be ejected due to outlier * detection. Defaults to 10% but will eject at least one host regardless of the value. */ - 'max_ejection_percent'?: (_google_protobuf_UInt32Value__Output); + 'max_ejection_percent': (_google_protobuf_UInt32Value__Output | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through consecutive 5xx. This setting can be used to disable * ejection or to ramp it up slowly. Defaults to 100. */ - 'enforcing_consecutive_5xx'?: (_google_protobuf_UInt32Value__Output); + 'enforcing_consecutive_5xx': (_google_protobuf_UInt32Value__Output | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through success rate statistics. This setting can be used to * disable ejection or to ramp it up slowly. Defaults to 100. */ - 'enforcing_success_rate'?: (_google_protobuf_UInt32Value__Output); + 'enforcing_success_rate': (_google_protobuf_UInt32Value__Output | null); /** * The number of hosts in a cluster that must have enough request volume to * detect success rate outliers. If the number of hosts is less than this * setting, outlier detection via success rate statistics is not performed * for any host in the cluster. Defaults to 5. */ - 'success_rate_minimum_hosts'?: (_google_protobuf_UInt32Value__Output); + 'success_rate_minimum_hosts': (_google_protobuf_UInt32Value__Output | null); /** * The minimum number of total requests that must be collected in one * interval (as defined by the interval duration above) to include this host @@ -214,7 +214,7 @@ export interface OutlierDetection__Output { * setting, outlier detection via success rate statistics is not performed * for that host. Defaults to 100. */ - 'success_rate_request_volume'?: (_google_protobuf_UInt32Value__Output); + 'success_rate_request_volume': (_google_protobuf_UInt32Value__Output | null); /** * This factor is used to determine the ejection threshold for success rate * outlier ejection. The ejection threshold is the difference between the @@ -224,18 +224,18 @@ export interface OutlierDetection__Output { * double. That is, if the desired factor is 1.9, the runtime value should * be 1900. Defaults to 1900. */ - 'success_rate_stdev_factor'?: (_google_protobuf_UInt32Value__Output); + 'success_rate_stdev_factor': (_google_protobuf_UInt32Value__Output | null); /** * The number of consecutive gateway failures (502, 503, 504 status codes) * before a consecutive gateway failure ejection occurs. Defaults to 5. */ - 'consecutive_gateway_failure'?: (_google_protobuf_UInt32Value__Output); + 'consecutive_gateway_failure': (_google_protobuf_UInt32Value__Output | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through consecutive gateway failures. This setting can be * used to disable ejection or to ramp it up slowly. Defaults to 0. */ - 'enforcing_consecutive_gateway_failure'?: (_google_protobuf_UInt32Value__Output); + 'enforcing_consecutive_gateway_failure': (_google_protobuf_UInt32Value__Output | null); /** * Determines whether to distinguish local origin failures from external errors. If set to true * the following configuration parameters are taken into account: @@ -252,7 +252,7 @@ export interface OutlierDetection__Output { * :ref:`split_external_local_origin_errors` * is set to true. */ - 'consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value__Output); + 'consecutive_local_origin_failure': (_google_protobuf_UInt32Value__Output | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through consecutive locally originated failures. This setting can be @@ -261,7 +261,7 @@ export interface OutlierDetection__Output { * :ref:`split_external_local_origin_errors` * is set to true. */ - 'enforcing_consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value__Output); + 'enforcing_consecutive_local_origin_failure': (_google_protobuf_UInt32Value__Output | null); /** * The % chance that a host will be actually ejected when an outlier status * is detected through success rate statistics for locally originated errors. @@ -270,13 +270,13 @@ export interface OutlierDetection__Output { * :ref:`split_external_local_origin_errors` * is set to true. */ - 'enforcing_local_origin_success_rate'?: (_google_protobuf_UInt32Value__Output); + 'enforcing_local_origin_success_rate': (_google_protobuf_UInt32Value__Output | null); /** * The failure percentage to use when determining failure percentage-based outlier detection. If * the failure percentage of a given host is greater than or equal to this value, it will be * ejected. Defaults to 85. */ - 'failure_percentage_threshold'?: (_google_protobuf_UInt32Value__Output); + 'failure_percentage_threshold': (_google_protobuf_UInt32Value__Output | null); /** * The % chance that a host will be actually ejected when an outlier status is detected through * failure percentage statistics. This setting can be used to disable ejection or to ramp it up @@ -285,30 +285,30 @@ export interface OutlierDetection__Output { * [#next-major-version: setting this without setting failure_percentage_threshold should be * invalid in v4.] */ - 'enforcing_failure_percentage'?: (_google_protobuf_UInt32Value__Output); + 'enforcing_failure_percentage': (_google_protobuf_UInt32Value__Output | null); /** * The % chance that a host will be actually ejected when an outlier status is detected through * local-origin failure percentage statistics. This setting can be used to disable ejection or to * ramp it up slowly. Defaults to 0. */ - 'enforcing_failure_percentage_local_origin'?: (_google_protobuf_UInt32Value__Output); + 'enforcing_failure_percentage_local_origin': (_google_protobuf_UInt32Value__Output | null); /** * The minimum number of hosts in a cluster in order to perform failure percentage-based ejection. * If the total number of hosts in the cluster is less than this value, failure percentage-based * ejection will not be performed. Defaults to 5. */ - 'failure_percentage_minimum_hosts'?: (_google_protobuf_UInt32Value__Output); + 'failure_percentage_minimum_hosts': (_google_protobuf_UInt32Value__Output | null); /** * The minimum number of total requests that must be collected in one interval (as defined by the * interval duration above) to perform failure percentage-based ejection for this host. If the * volume is lower than this setting, failure percentage-based ejection will not be performed for * this host. Defaults to 50. */ - 'failure_percentage_request_volume'?: (_google_protobuf_UInt32Value__Output); + 'failure_percentage_request_volume': (_google_protobuf_UInt32Value__Output | null); /** * The maximum time that a host is ejected for. See :ref:`base_ejection_time` * for more information. * Defaults to 300000ms or 300s. */ - 'max_ejection_time'?: (_google_protobuf_Duration__Output); + 'max_ejection_time': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts index 0f95c0816..f2dbd0608 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts @@ -10,7 +10,7 @@ export interface UpstreamBindConfig { /** * The address Envoy should bind to when establishing upstream connections. */ - 'source_address'?: (_envoy_config_core_v3_Address); + 'source_address'?: (_envoy_config_core_v3_Address | null); } /** @@ -21,5 +21,5 @@ export interface UpstreamBindConfig__Output { /** * The address Envoy should bind to when establishing upstream connections. */ - 'source_address'?: (_envoy_config_core_v3_Address__Output); + 'source_address': (_envoy_config_core_v3_Address__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts index 2e7f23894..483d1072d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts @@ -6,12 +6,12 @@ export interface UpstreamConnectionOptions { /** * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. */ - 'tcp_keepalive'?: (_envoy_config_core_v3_TcpKeepalive); + 'tcp_keepalive'?: (_envoy_config_core_v3_TcpKeepalive | null); } export interface UpstreamConnectionOptions__Output { /** * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. */ - 'tcp_keepalive'?: (_envoy_config_core_v3_TcpKeepalive__Output); + 'tcp_keepalive': (_envoy_config_core_v3_TcpKeepalive__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts index 4d5881baf..32c483344 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts @@ -10,12 +10,12 @@ import type { EnvoyInternalAddress as _envoy_config_core_v3_EnvoyInternalAddress * management servers. */ export interface Address { - 'socket_address'?: (_envoy_config_core_v3_SocketAddress); - 'pipe'?: (_envoy_config_core_v3_Pipe); + 'socket_address'?: (_envoy_config_core_v3_SocketAddress | null); + 'pipe'?: (_envoy_config_core_v3_Pipe | null); /** * [#not-implemented-hide:] */ - 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress); + 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress | null); 'address'?: "socket_address"|"pipe"|"envoy_internal_address"; } @@ -25,11 +25,11 @@ export interface Address { * management servers. */ export interface Address__Output { - 'socket_address'?: (_envoy_config_core_v3_SocketAddress__Output); - 'pipe'?: (_envoy_config_core_v3_Pipe__Output); + 'socket_address'?: (_envoy_config_core_v3_SocketAddress__Output | null); + 'pipe'?: (_envoy_config_core_v3_Pipe__Output | null); /** * [#not-implemented-hide:] */ - 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress__Output); + 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress__Output | null); 'address': "socket_address"|"pipe"|"envoy_internal_address"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts index 99afe9a26..b303a08d8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts @@ -70,7 +70,7 @@ export interface ApiConfigSource { /** * For REST APIs, the delay between successive polls. */ - 'refresh_delay'?: (_google_protobuf_Duration); + 'refresh_delay'?: (_google_protobuf_Duration | null); /** * Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, * services will be cycled through if any kind of failure occurs. @@ -79,12 +79,12 @@ export interface ApiConfigSource { /** * For REST APIs, the request timeout. If not set, a default value of 1s will be used. */ - 'request_timeout'?: (_google_protobuf_Duration); + 'request_timeout'?: (_google_protobuf_Duration | null); /** * For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be * rate limited. */ - 'rate_limit_settings'?: (_envoy_config_core_v3_RateLimitSettings); + 'rate_limit_settings'?: (_envoy_config_core_v3_RateLimitSettings | null); /** * Skip the node identifier in subsequent discovery requests for streaming gRPC config types. */ @@ -120,7 +120,7 @@ export interface ApiConfigSource__Output { /** * For REST APIs, the delay between successive polls. */ - 'refresh_delay'?: (_google_protobuf_Duration__Output); + 'refresh_delay': (_google_protobuf_Duration__Output | null); /** * Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, * services will be cycled through if any kind of failure occurs. @@ -129,12 +129,12 @@ export interface ApiConfigSource__Output { /** * For REST APIs, the request timeout. If not set, a default value of 1s will be used. */ - 'request_timeout'?: (_google_protobuf_Duration__Output); + 'request_timeout': (_google_protobuf_Duration__Output | null); /** * For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be * rate limited. */ - 'rate_limit_settings'?: (_envoy_config_core_v3_RateLimitSettings__Output); + 'rate_limit_settings': (_envoy_config_core_v3_RateLimitSettings__Output | null); /** * Skip the node identifier in subsequent discovery requests for streaming gRPC config types. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts index 476ea851f..aa152155b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AsyncDataSource.ts @@ -10,11 +10,11 @@ export interface AsyncDataSource { /** * Local async data source. */ - 'local'?: (_envoy_config_core_v3_DataSource); + 'local'?: (_envoy_config_core_v3_DataSource | null); /** * Remote async data source. */ - 'remote'?: (_envoy_config_core_v3_RemoteDataSource); + 'remote'?: (_envoy_config_core_v3_RemoteDataSource | null); 'specifier'?: "local"|"remote"; } @@ -25,10 +25,10 @@ export interface AsyncDataSource__Output { /** * Local async data source. */ - 'local'?: (_envoy_config_core_v3_DataSource__Output); + 'local'?: (_envoy_config_core_v3_DataSource__Output | null); /** * Remote async data source. */ - 'remote'?: (_envoy_config_core_v3_RemoteDataSource__Output); + 'remote'?: (_envoy_config_core_v3_RemoteDataSource__Output | null); 'specifier': "local"|"remote"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts index 9878375d6..b85b8d5de 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts @@ -11,7 +11,7 @@ export interface BackoffStrategy { * be greater than zero and less than or equal to :ref:`max_interval * `. */ - 'base_interval'?: (_google_protobuf_Duration); + 'base_interval'?: (_google_protobuf_Duration | null); /** * Specifies the maximum interval between retries. This parameter is optional, * but must be greater than or equal to the :ref:`base_interval @@ -19,7 +19,7 @@ export interface BackoffStrategy { * is 10 times the :ref:`base_interval * `. */ - 'max_interval'?: (_google_protobuf_Duration); + 'max_interval'?: (_google_protobuf_Duration | null); } /** @@ -31,7 +31,7 @@ export interface BackoffStrategy__Output { * be greater than zero and less than or equal to :ref:`max_interval * `. */ - 'base_interval'?: (_google_protobuf_Duration__Output); + 'base_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the maximum interval between retries. This parameter is optional, * but must be greater than or equal to the :ref:`base_interval @@ -39,5 +39,5 @@ export interface BackoffStrategy__Output { * is 10 times the :ref:`base_interval * `. */ - 'max_interval'?: (_google_protobuf_Duration__Output); + 'max_interval': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts index eb2bc7626..db6b1f654 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts @@ -8,7 +8,7 @@ export interface BindConfig { /** * The address to bind to when creating a socket. */ - 'source_address'?: (_envoy_config_core_v3_SocketAddress); + 'source_address'?: (_envoy_config_core_v3_SocketAddress | null); /** * Whether to set the *IP_FREEBIND* option when creating the socket. When this * flag is set to true, allows the :ref:`source_address @@ -18,7 +18,7 @@ export interface BindConfig { * flag is not set (default), the socket is not modified, i.e. the option is * neither enabled nor disabled. */ - 'freebind'?: (_google_protobuf_BoolValue); + 'freebind'?: (_google_protobuf_BoolValue | null); /** * Additional socket options that may not be present in Envoy source code or * precompiled binaries. @@ -30,7 +30,7 @@ export interface BindConfig__Output { /** * The address to bind to when creating a socket. */ - 'source_address'?: (_envoy_config_core_v3_SocketAddress__Output); + 'source_address': (_envoy_config_core_v3_SocketAddress__Output | null); /** * Whether to set the *IP_FREEBIND* option when creating the socket. When this * flag is set to true, allows the :ref:`source_address @@ -40,7 +40,7 @@ export interface BindConfig__Output { * flag is not set (default), the socket is not modified, i.e. the option is * neither enabled nor disabled. */ - 'freebind'?: (_google_protobuf_BoolValue__Output); + 'freebind': (_google_protobuf_BoolValue__Output | null); /** * Additional socket options that may not be present in Envoy source code or * precompiled binaries. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts index a3a33e4a3..1a86e918e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BuildVersion.ts @@ -11,12 +11,12 @@ export interface BuildVersion { /** * SemVer version of extension. */ - 'version'?: (_envoy_type_v3_SemanticVersion); + 'version'?: (_envoy_type_v3_SemanticVersion | null); /** * Free-form build information. * Envoy defines several well known keys in the source/common/version/version.h file */ - 'metadata'?: (_google_protobuf_Struct); + 'metadata'?: (_google_protobuf_Struct | null); } /** @@ -27,10 +27,10 @@ export interface BuildVersion__Output { /** * SemVer version of extension. */ - 'version'?: (_envoy_type_v3_SemanticVersion__Output); + 'version': (_envoy_type_v3_SemanticVersion__Output | null); /** * Free-form build information. * Envoy defines several well known keys in the source/common/version/version.h file */ - 'metadata'?: (_google_protobuf_Struct__Output); + 'metadata': (_google_protobuf_Struct__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts index c4a01b0da..df4ff39ff 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts @@ -14,7 +14,7 @@ export interface CidrRange { /** * Length of prefix, e.g. 0, 32. */ - 'prefix_len'?: (_google_protobuf_UInt32Value); + 'prefix_len'?: (_google_protobuf_UInt32Value | null); } /** @@ -29,5 +29,5 @@ export interface CidrRange__Output { /** * Length of prefix, e.g. 0, 32. */ - 'prefix_len'?: (_google_protobuf_UInt32Value__Output); + 'prefix_len': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts index 9462f4844..aaf4d9564 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts @@ -36,12 +36,12 @@ export interface ConfigSource { /** * API configuration source. */ - 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource); + 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource | null); /** * When set, ADS will be used to fetch resources. The ADS API configuration * source in the bootstrap configuration is used. */ - 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource); + 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource | null); /** * When this timeout is specified, Envoy will wait no longer than the specified time for first * config response on this xDS subscription during the :ref:`initialization process @@ -51,7 +51,7 @@ export interface ConfigSource { * means no timeout - Envoy will wait indefinitely for the first xDS config (unless another * timeout applies). The default is 15s. */ - 'initial_fetch_timeout'?: (_google_protobuf_Duration); + 'initial_fetch_timeout'?: (_google_protobuf_Duration | null); /** * [#not-implemented-hide:] * When set, the client will access the resources from the same server it got the @@ -65,7 +65,7 @@ export interface ConfigSource { * this field can implicitly mean to use the same stream in the case where the ConfigSource * is provided via ADS and the specified data can also be obtained via ADS.] */ - 'self'?: (_envoy_config_core_v3_SelfConfigSource); + 'self'?: (_envoy_config_core_v3_SelfConfigSource | null); /** * API version for xDS resources. This implies the type URLs that the client * will request for resources and the resource type that the client will in @@ -111,12 +111,12 @@ export interface ConfigSource__Output { /** * API configuration source. */ - 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource__Output); + 'api_config_source'?: (_envoy_config_core_v3_ApiConfigSource__Output | null); /** * When set, ADS will be used to fetch resources. The ADS API configuration * source in the bootstrap configuration is used. */ - 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource__Output); + 'ads'?: (_envoy_config_core_v3_AggregatedConfigSource__Output | null); /** * When this timeout is specified, Envoy will wait no longer than the specified time for first * config response on this xDS subscription during the :ref:`initialization process @@ -126,7 +126,7 @@ export interface ConfigSource__Output { * means no timeout - Envoy will wait indefinitely for the first xDS config (unless another * timeout applies). The default is 15s. */ - 'initial_fetch_timeout'?: (_google_protobuf_Duration__Output); + 'initial_fetch_timeout': (_google_protobuf_Duration__Output | null); /** * [#not-implemented-hide:] * When set, the client will access the resources from the same server it got the @@ -140,7 +140,7 @@ export interface ConfigSource__Output { * this field can implicitly mean to use the same stream in the case where the ConfigSource * is provided via ADS and the specified data can also be obtained via ADS.] */ - 'self'?: (_envoy_config_core_v3_SelfConfigSource__Output); + 'self'?: (_envoy_config_core_v3_SelfConfigSource__Output | null); /** * API version for xDS resources. This implies the type URLs that the client * will request for resources and the resource type that the client will in diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts index 72aedc6ab..6226b97c8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EventServiceConfig.ts @@ -10,7 +10,7 @@ export interface EventServiceConfig { /** * Specifies the gRPC service that hosts the event reporting service. */ - 'grpc_service'?: (_envoy_config_core_v3_GrpcService); + 'grpc_service'?: (_envoy_config_core_v3_GrpcService | null); 'config_source_specifier'?: "grpc_service"; } @@ -22,6 +22,6 @@ export interface EventServiceConfig__Output { /** * Specifies the gRPC service that hosts the event reporting service. */ - 'grpc_service'?: (_envoy_config_core_v3_GrpcService__Output); + 'grpc_service'?: (_envoy_config_core_v3_GrpcService__Output | null); 'config_source_specifier': "grpc_service"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts index 0c7e0abd3..5f730bd22 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts @@ -31,7 +31,7 @@ export interface Extension { * of other extensions and the Envoy API. * This field is not set when extension did not provide version information. */ - 'version'?: (_envoy_config_core_v3_BuildVersion); + 'version'?: (_envoy_config_core_v3_BuildVersion | null); /** * Indicates that the extension is present but was disabled via dynamic configuration. */ @@ -67,7 +67,7 @@ export interface Extension__Output { * of other extensions and the Envoy API. * This field is not set when extension did not provide version information. */ - 'version'?: (_envoy_config_core_v3_BuildVersion__Output); + 'version': (_envoy_config_core_v3_BuildVersion__Output | null); /** * Indicates that the extension is present but was disabled via dynamic configuration. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts index 085324b6d..805762ef5 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts @@ -17,13 +17,13 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ * filter chain with a disabled extension filter rejects all incoming streams. */ export interface ExtensionConfigSource { - 'config_source'?: (_envoy_config_core_v3_ConfigSource); + 'config_source'?: (_envoy_config_core_v3_ConfigSource | null); /** * Optional default configuration to use as the initial configuration if * there is a failure to receive the initial extension configuration or if * `apply_default_config_without_warming` flag is set. */ - 'default_config'?: (_google_protobuf_Any); + 'default_config'?: (_google_protobuf_Any | null); /** * Use the default config as the initial configuration without warming and * waiting for the first discovery response. Requires the default configuration @@ -51,13 +51,13 @@ export interface ExtensionConfigSource { * filter chain with a disabled extension filter rejects all incoming streams. */ export interface ExtensionConfigSource__Output { - 'config_source'?: (_envoy_config_core_v3_ConfigSource__Output); + 'config_source': (_envoy_config_core_v3_ConfigSource__Output | null); /** * Optional default configuration to use as the initial configuration if * there is a failure to receive the initial extension configuration or if * `apply_default_config_without_warming` flag is set. */ - 'default_config'?: (_google_protobuf_Any__Output); + 'default_config': (_google_protobuf_Any__Output | null); /** * Use the default config as the initial configuration without warming and * waiting for the first discovery response. Requires the default configuration diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts index b0486cc37..1f0376a1c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcProtocolOptions.ts @@ -6,12 +6,12 @@ import type { Http2ProtocolOptions as _envoy_config_core_v3_Http2ProtocolOptions * [#not-implemented-hide:] */ export interface GrpcProtocolOptions { - 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions); + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); } /** * [#not-implemented-hide:] */ export interface GrpcProtocolOptions__Output { - 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions__Output); + 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts index 24249309b..0e55d018b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts @@ -22,7 +22,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials { * Google Compute Engine credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 */ - 'google_compute_engine'?: (_google_protobuf_Empty); + 'google_compute_engine'?: (_google_protobuf_Empty | null); /** * Google refresh token credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a96901c997b91bc6513b08491e0dca37c. @@ -32,24 +32,24 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials { * Service Account JWT Access credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a92a9f959d6102461f66ee973d8e9d3aa. */ - 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials); + 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials | null); /** * Google IAM credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a9fc1fc101b41e680d47028166e76f9d0. */ - 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials); + 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials | null); /** * Custom authenticator credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a823c6a4b19ffc71fb33e90154ee2ad07. * https://grpc.io/docs/guides/auth.html#extending-grpc-to-support-other-authentication-mechanisms. */ - 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin); + 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin | null); /** * Custom security token service which implements OAuth 2.0 token exchange. * https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 * See https://github.com/grpc/grpc/pull/19587. */ - 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService); + 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService | null); 'credential_specifier'?: "access_token"|"google_compute_engine"|"google_refresh_token"|"service_account_jwt_access"|"google_iam"|"from_plugin"|"sts_service"; } @@ -66,7 +66,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials__O * Google Compute Engine credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 */ - 'google_compute_engine'?: (_google_protobuf_Empty__Output); + 'google_compute_engine'?: (_google_protobuf_Empty__Output | null); /** * Google refresh token credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a96901c997b91bc6513b08491e0dca37c. @@ -76,24 +76,24 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials__O * Service Account JWT Access credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a92a9f959d6102461f66ee973d8e9d3aa. */ - 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output); + 'service_account_jwt_access'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_ServiceAccountJWTAccessCredentials__Output | null); /** * Google IAM credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a9fc1fc101b41e680d47028166e76f9d0. */ - 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output); + 'google_iam'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials__Output | null); /** * Custom authenticator credentials. * https://grpc.io/grpc/cpp/namespacegrpc.html#a823c6a4b19ffc71fb33e90154ee2ad07. * https://grpc.io/docs/guides/auth.html#extending-grpc-to-support-other-authentication-mechanisms. */ - 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output); + 'from_plugin'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output | null); /** * Custom security token service which implements OAuth 2.0 token exchange. * https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16 * See https://github.com/grpc/grpc/pull/19587. */ - 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService__Output); + 'sts_service'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_StsService__Output | null); 'credential_specifier': "access_token"|"google_compute_engine"|"google_refresh_token"|"service_account_jwt_access"|"google_iam"|"from_plugin"|"sts_service"; } @@ -114,7 +114,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Outpu /** * See grpc_types.h GRPC_ARG #defines for keys that work here. */ - 'args'?: ({[key: string]: _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value__Output}); + 'args': ({[key: string]: _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value__Output}); } /** @@ -122,12 +122,12 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Outpu * credential types. */ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials { - 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials); + 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials | null); /** * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 */ - 'google_default'?: (_google_protobuf_Empty); - 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials); + 'google_default'?: (_google_protobuf_Empty | null); + 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials | null); 'credential_specifier'?: "ssl_credentials"|"google_default"|"local_credentials"; } @@ -136,12 +136,12 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials * credential types. */ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output { - 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials__Output); + 'ssl_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials__Output | null); /** * https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 */ - 'google_default'?: (_google_protobuf_Empty__Output); - 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output); + 'google_default'?: (_google_protobuf_Empty__Output | null); + 'local_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredentials__Output | null); 'credential_specifier': "ssl_credentials"|"google_default"|"local_credentials"; } @@ -183,7 +183,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc { * :ref:`channel_credentials `. */ 'target_uri'?: (string); - 'channel_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials); + 'channel_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials | null); /** * A set of call credentials that can be composed with `channel credentials * `_. @@ -211,16 +211,16 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc { * Additional configuration for site-specific customizations of the Google * gRPC library. */ - 'config'?: (_google_protobuf_Struct); + 'config'?: (_google_protobuf_Struct | null); /** * How many bytes each stream can buffer internally. * If not set an implementation defined default is applied (1MiB). */ - 'per_stream_buffer_limit_bytes'?: (_google_protobuf_UInt32Value); + 'per_stream_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); /** * Custom channels args. */ - 'channel_args'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs); + 'channel_args'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs | null); } /** @@ -233,7 +233,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc__Output { * :ref:`channel_credentials `. */ 'target_uri': (string); - 'channel_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output); + 'channel_credentials': (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output | null); /** * A set of call credentials that can be composed with `channel credentials * `_. @@ -261,16 +261,16 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc__Output { * Additional configuration for site-specific customizations of the Google * gRPC library. */ - 'config'?: (_google_protobuf_Struct__Output); + 'config': (_google_protobuf_Struct__Output | null); /** * How many bytes each stream can buffer internally. * If not set an implementation defined default is applied (1MiB). */ - 'per_stream_buffer_limit_bytes'?: (_google_protobuf_UInt32Value__Output); + 'per_stream_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); /** * Custom channels args. */ - 'channel_args'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Output); + 'channel_args': (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs__Output | null); } export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_GoogleIAMCredentials { @@ -299,13 +299,13 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredent export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin { 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); 'config_type'?: "typed_config"; } export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output { 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); 'config_type': "typed_config"; } @@ -326,15 +326,15 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials { /** * PEM encoded server root certificates. */ - 'root_certs'?: (_envoy_config_core_v3_DataSource); + 'root_certs'?: (_envoy_config_core_v3_DataSource | null); /** * PEM encoded client private key. */ - 'private_key'?: (_envoy_config_core_v3_DataSource); + 'private_key'?: (_envoy_config_core_v3_DataSource | null); /** * PEM encoded client certificate chain. */ - 'cert_chain'?: (_envoy_config_core_v3_DataSource); + 'cert_chain'?: (_envoy_config_core_v3_DataSource | null); } /** @@ -344,15 +344,15 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_SslCredentials__Ou /** * PEM encoded server root certificates. */ - 'root_certs'?: (_envoy_config_core_v3_DataSource__Output); + 'root_certs': (_envoy_config_core_v3_DataSource__Output | null); /** * PEM encoded client private key. */ - 'private_key'?: (_envoy_config_core_v3_DataSource__Output); + 'private_key': (_envoy_config_core_v3_DataSource__Output | null); /** * PEM encoded client certificate chain. */ - 'cert_chain'?: (_envoy_config_core_v3_DataSource__Output); + 'cert_chain': (_envoy_config_core_v3_DataSource__Output | null); } /** @@ -494,18 +494,18 @@ export interface GrpcService { * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc); + 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc | null); /** * `Google C++ gRPC client `_ * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc); + 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc | null); /** * The timeout for the gRPC request. This is the timeout for a specific * request. */ - 'timeout'?: (_google_protobuf_Duration); + 'timeout'?: (_google_protobuf_Duration | null); /** * Additional metadata to include in streams initiated to the GrpcService. This can be used for * scenarios in which additional ad hoc authorization headers (e.g. ``x-foo-bar: baz-key``) are to @@ -528,18 +528,18 @@ export interface GrpcService__Output { * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc__Output); + 'envoy_grpc'?: (_envoy_config_core_v3_GrpcService_EnvoyGrpc__Output | null); /** * `Google C++ gRPC client `_ * See the :ref:`gRPC services overview ` * documentation for discussion on gRPC client selection. */ - 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc__Output); + 'google_grpc'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc__Output | null); /** * The timeout for the gRPC request. This is the timeout for a specific * request. */ - 'timeout'?: (_google_protobuf_Duration__Output); + 'timeout': (_google_protobuf_Duration__Output | null); /** * Additional metadata to include in streams initiated to the GrpcService. This can be used for * scenarios in which additional ad hoc authorization headers (e.g. ``x-foo-bar: baz-key``) are to diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts index 4a0fe7120..29f9d6695 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts @@ -10,12 +10,12 @@ export interface HeaderValueOption { /** * Header name/value pair that this option applies to. */ - 'header'?: (_envoy_config_core_v3_HeaderValue); + 'header'?: (_envoy_config_core_v3_HeaderValue | null); /** * Should the value be appended? If true (default), the value is appended to * existing values. Otherwise it replaces any existing values. */ - 'append'?: (_google_protobuf_BoolValue); + 'append'?: (_google_protobuf_BoolValue | null); } /** @@ -25,10 +25,10 @@ export interface HeaderValueOption__Output { /** * Header name/value pair that this option applies to. */ - 'header'?: (_envoy_config_core_v3_HeaderValue__Output); + 'header': (_envoy_config_core_v3_HeaderValue__Output | null); /** * Should the value be appended? If true (default), the value is appended to * existing values. Otherwise it replaces any existing values. */ - 'append'?: (_google_protobuf_BoolValue__Output); + 'append': (_google_protobuf_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts index 3a7320a08..6dbbf6f4d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts @@ -20,7 +20,7 @@ export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck { * The registered name of the custom health checker. */ 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * A custom health checker specific configuration which depends on the custom health checker * being instantiated. See :api:`envoy/config/health_checker` for reference. @@ -36,7 +36,7 @@ export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output { * The registered name of the custom health checker. */ 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * A custom health checker specific configuration which depends on the custom health checker * being instantiated. See :api:`envoy/config/health_checker` for reference. @@ -111,11 +111,11 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { /** * [#not-implemented-hide:] HTTP specific payload. */ - 'send'?: (_envoy_config_core_v3_HealthCheck_Payload); + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload | null); /** * [#not-implemented-hide:] HTTP specific response. */ - 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload); + 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload | null); /** * Specifies a list of HTTP headers that should be added to each request that is sent to the * health checked cluster. For more information, including details on header value syntax, see @@ -145,7 +145,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { * `. See the :ref:`architecture overview * ` for more information. */ - 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher); + 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher | null); } /** @@ -167,11 +167,11 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { /** * [#not-implemented-hide:] HTTP specific payload. */ - 'send'?: (_envoy_config_core_v3_HealthCheck_Payload__Output); + 'send': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); /** * [#not-implemented-hide:] HTTP specific response. */ - 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload__Output); + 'receive': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); /** * Specifies a list of HTTP headers that should be added to each request that is sent to the * health checked cluster. For more information, including details on header value syntax, see @@ -201,7 +201,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { * `. See the :ref:`architecture overview * ` for more information. */ - 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher__Output); + 'service_name_matcher': (_envoy_type_matcher_v3_StringMatcher__Output | null); } /** @@ -258,7 +258,7 @@ export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck { /** * Empty payloads imply a connect-only health check. */ - 'send'?: (_envoy_config_core_v3_HealthCheck_Payload); + 'send'?: (_envoy_config_core_v3_HealthCheck_Payload | null); /** * When checking the response, “fuzzy” matching is performed such that each * binary block must be found, and in the order specified, but not @@ -271,7 +271,7 @@ export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output { /** * Empty payloads imply a connect-only health check. */ - 'send'?: (_envoy_config_core_v3_HealthCheck_Payload__Output); + 'send': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); /** * When checking the response, “fuzzy” matching is performed such that each * binary block must be found, and in the order specified, but not @@ -320,48 +320,48 @@ export interface HealthCheck { * The time to wait for a health check response. If the timeout is reached the * health check attempt will be considered a failure. */ - 'timeout'?: (_google_protobuf_Duration); + 'timeout'?: (_google_protobuf_Duration | null); /** * The interval between health checks. */ - 'interval'?: (_google_protobuf_Duration); + 'interval'?: (_google_protobuf_Duration | null); /** * An optional jitter amount in milliseconds. If specified, during every * interval Envoy will add interval_jitter to the wait time. */ - 'interval_jitter'?: (_google_protobuf_Duration); + 'interval_jitter'?: (_google_protobuf_Duration | null); /** * The number of unhealthy health checks required before a host is marked * unhealthy. Note that for *http* health checking if a host responds with 503 * this threshold is ignored and the host is considered unhealthy immediately. */ - 'unhealthy_threshold'?: (_google_protobuf_UInt32Value); + 'unhealthy_threshold'?: (_google_protobuf_UInt32Value | null); /** * The number of healthy health checks required before a host is marked * healthy. Note that during startup, only a single successful health check is * required to mark a host healthy. */ - 'healthy_threshold'?: (_google_protobuf_UInt32Value); + 'healthy_threshold'?: (_google_protobuf_UInt32Value | null); /** * [#not-implemented-hide:] Non-serving port for health checking. */ - 'alt_port'?: (_google_protobuf_UInt32Value); + 'alt_port'?: (_google_protobuf_UInt32Value | null); /** * Reuse health check connection between health checks. Default is true. */ - 'reuse_connection'?: (_google_protobuf_BoolValue); + 'reuse_connection'?: (_google_protobuf_BoolValue | null); /** * HTTP health check. */ - 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck); + 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck | null); /** * TCP health check. */ - 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck); + 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck | null); /** * gRPC health check. */ - 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck); + 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck | null); /** * The "no traffic interval" is a special health check interval that is used when a cluster has * never had traffic routed to it. This lower interval allows cluster information to be kept up to @@ -372,11 +372,11 @@ export interface HealthCheck { * * The default value for "no traffic interval" is 60 seconds. */ - 'no_traffic_interval'?: (_google_protobuf_Duration); + 'no_traffic_interval'?: (_google_protobuf_Duration | null); /** * Custom health check. */ - 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck); + 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck | null); /** * The "unhealthy interval" is a health check interval that is used for hosts that are marked as * unhealthy. As soon as the host is marked as healthy, Envoy will shift back to using the @@ -384,7 +384,7 @@ export interface HealthCheck { * * The default value for "unhealthy interval" is the same as "interval". */ - 'unhealthy_interval'?: (_google_protobuf_Duration); + 'unhealthy_interval'?: (_google_protobuf_Duration | null); /** * The "unhealthy edge interval" is a special health check interval that is used for the first * health check right after a host is marked as unhealthy. For subsequent health checks @@ -393,7 +393,7 @@ export interface HealthCheck { * * The default value for "unhealthy edge interval" is the same as "unhealthy interval". */ - 'unhealthy_edge_interval'?: (_google_protobuf_Duration); + 'unhealthy_edge_interval'?: (_google_protobuf_Duration | null); /** * The "healthy edge interval" is a special health check interval that is used for the first * health check right after a host is marked as healthy. For subsequent health checks @@ -401,7 +401,7 @@ export interface HealthCheck { * * The default value for "healthy edge interval" is the same as the default interval. */ - 'healthy_edge_interval'?: (_google_protobuf_Duration); + 'healthy_edge_interval'?: (_google_protobuf_Duration | null); /** * Specifies the path to the :ref:`health check event log `. * If empty, no event log will be written. @@ -427,17 +427,17 @@ export interface HealthCheck { * checking after for a random time in ms between 0 and initial_jitter. This only * applies to the first health check. */ - 'initial_jitter'?: (_google_protobuf_Duration); + 'initial_jitter'?: (_google_protobuf_Duration | null); /** * This allows overriding the cluster TLS settings, just for health check connections. */ - 'tls_options'?: (_envoy_config_core_v3_HealthCheck_TlsOptions); + 'tls_options'?: (_envoy_config_core_v3_HealthCheck_TlsOptions | null); /** * [#not-implemented-hide:] * The gRPC service for the health check event service. * If empty, health check events won't be sent to a remote endpoint. */ - 'event_service'?: (_envoy_config_core_v3_EventServiceConfig); + 'event_service'?: (_envoy_config_core_v3_EventServiceConfig | null); /** * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's * :ref:`tranport socket matches `. @@ -470,7 +470,7 @@ export interface HealthCheck { * the cluster's :ref:`transport socket ` * will be used for health check socket configuration. */ - 'transport_socket_match_criteria'?: (_google_protobuf_Struct); + 'transport_socket_match_criteria'?: (_google_protobuf_Struct | null); /** * The "no traffic healthy interval" is a special health check interval that * is used for hosts that are currently passing active health checking @@ -486,7 +486,7 @@ export interface HealthCheck { * If no_traffic_healthy_interval is not set, it will default to the * no traffic interval and send that interval regardless of health state. */ - 'no_traffic_healthy_interval'?: (_google_protobuf_Duration); + 'no_traffic_healthy_interval'?: (_google_protobuf_Duration | null); 'health_checker'?: "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; } @@ -498,48 +498,48 @@ export interface HealthCheck__Output { * The time to wait for a health check response. If the timeout is reached the * health check attempt will be considered a failure. */ - 'timeout'?: (_google_protobuf_Duration__Output); + 'timeout': (_google_protobuf_Duration__Output | null); /** * The interval between health checks. */ - 'interval'?: (_google_protobuf_Duration__Output); + 'interval': (_google_protobuf_Duration__Output | null); /** * An optional jitter amount in milliseconds. If specified, during every * interval Envoy will add interval_jitter to the wait time. */ - 'interval_jitter'?: (_google_protobuf_Duration__Output); + 'interval_jitter': (_google_protobuf_Duration__Output | null); /** * The number of unhealthy health checks required before a host is marked * unhealthy. Note that for *http* health checking if a host responds with 503 * this threshold is ignored and the host is considered unhealthy immediately. */ - 'unhealthy_threshold'?: (_google_protobuf_UInt32Value__Output); + 'unhealthy_threshold': (_google_protobuf_UInt32Value__Output | null); /** * The number of healthy health checks required before a host is marked * healthy. Note that during startup, only a single successful health check is * required to mark a host healthy. */ - 'healthy_threshold'?: (_google_protobuf_UInt32Value__Output); + 'healthy_threshold': (_google_protobuf_UInt32Value__Output | null); /** * [#not-implemented-hide:] Non-serving port for health checking. */ - 'alt_port'?: (_google_protobuf_UInt32Value__Output); + 'alt_port': (_google_protobuf_UInt32Value__Output | null); /** * Reuse health check connection between health checks. Default is true. */ - 'reuse_connection'?: (_google_protobuf_BoolValue__Output); + 'reuse_connection': (_google_protobuf_BoolValue__Output | null); /** * HTTP health check. */ - 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output); + 'http_health_check'?: (_envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output | null); /** * TCP health check. */ - 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output); + 'tcp_health_check'?: (_envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output | null); /** * gRPC health check. */ - 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output); + 'grpc_health_check'?: (_envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output | null); /** * The "no traffic interval" is a special health check interval that is used when a cluster has * never had traffic routed to it. This lower interval allows cluster information to be kept up to @@ -550,11 +550,11 @@ export interface HealthCheck__Output { * * The default value for "no traffic interval" is 60 seconds. */ - 'no_traffic_interval'?: (_google_protobuf_Duration__Output); + 'no_traffic_interval': (_google_protobuf_Duration__Output | null); /** * Custom health check. */ - 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output); + 'custom_health_check'?: (_envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output | null); /** * The "unhealthy interval" is a health check interval that is used for hosts that are marked as * unhealthy. As soon as the host is marked as healthy, Envoy will shift back to using the @@ -562,7 +562,7 @@ export interface HealthCheck__Output { * * The default value for "unhealthy interval" is the same as "interval". */ - 'unhealthy_interval'?: (_google_protobuf_Duration__Output); + 'unhealthy_interval': (_google_protobuf_Duration__Output | null); /** * The "unhealthy edge interval" is a special health check interval that is used for the first * health check right after a host is marked as unhealthy. For subsequent health checks @@ -571,7 +571,7 @@ export interface HealthCheck__Output { * * The default value for "unhealthy edge interval" is the same as "unhealthy interval". */ - 'unhealthy_edge_interval'?: (_google_protobuf_Duration__Output); + 'unhealthy_edge_interval': (_google_protobuf_Duration__Output | null); /** * The "healthy edge interval" is a special health check interval that is used for the first * health check right after a host is marked as healthy. For subsequent health checks @@ -579,7 +579,7 @@ export interface HealthCheck__Output { * * The default value for "healthy edge interval" is the same as the default interval. */ - 'healthy_edge_interval'?: (_google_protobuf_Duration__Output); + 'healthy_edge_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the path to the :ref:`health check event log `. * If empty, no event log will be written. @@ -605,17 +605,17 @@ export interface HealthCheck__Output { * checking after for a random time in ms between 0 and initial_jitter. This only * applies to the first health check. */ - 'initial_jitter'?: (_google_protobuf_Duration__Output); + 'initial_jitter': (_google_protobuf_Duration__Output | null); /** * This allows overriding the cluster TLS settings, just for health check connections. */ - 'tls_options'?: (_envoy_config_core_v3_HealthCheck_TlsOptions__Output); + 'tls_options': (_envoy_config_core_v3_HealthCheck_TlsOptions__Output | null); /** * [#not-implemented-hide:] * The gRPC service for the health check event service. * If empty, health check events won't be sent to a remote endpoint. */ - 'event_service'?: (_envoy_config_core_v3_EventServiceConfig__Output); + 'event_service': (_envoy_config_core_v3_EventServiceConfig__Output | null); /** * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's * :ref:`tranport socket matches `. @@ -648,7 +648,7 @@ export interface HealthCheck__Output { * the cluster's :ref:`transport socket ` * will be used for health check socket configuration. */ - 'transport_socket_match_criteria'?: (_google_protobuf_Struct__Output); + 'transport_socket_match_criteria': (_google_protobuf_Struct__Output | null); /** * The "no traffic healthy interval" is a special health check interval that * is used for hosts that are currently passing active health checking @@ -664,6 +664,6 @@ export interface HealthCheck__Output { * If no_traffic_healthy_interval is not set, it will default to the * no traffic interval and send that interval regardless of health state. */ - 'no_traffic_healthy_interval'?: (_google_protobuf_Duration__Output); + 'no_traffic_healthy_interval': (_google_protobuf_Duration__Output | null); 'health_checker': "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts index 982f58b0e..89889d390 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts @@ -10,7 +10,7 @@ export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat { * Note that while this results in most headers following conventional casing, certain headers * are not covered. For example, the "TE" header will be formatted as "Te". */ - 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords); + 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords | null); 'header_format'?: "proper_case_words"; } @@ -22,7 +22,7 @@ export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Out * Note that while this results in most headers following conventional casing, certain headers * are not covered. For example, the "TE" header will be formatted as "Te". */ - 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output); + 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output | null); 'header_format': "proper_case_words"; } @@ -42,7 +42,7 @@ export interface Http1ProtocolOptions { * envoy as their HTTP proxy. In Unix, for example, this is typically done by setting the * *http_proxy* environment variable. */ - 'allow_absolute_url'?: (_google_protobuf_BoolValue); + 'allow_absolute_url'?: (_google_protobuf_BoolValue | null); /** * Handle incoming HTTP/1.0 and HTTP 0.9 requests. * This is off by default, and not fully standards compliant. There is support for pre-HTTP/1.1 @@ -60,7 +60,7 @@ export interface Http1ProtocolOptions { * Describes how the keys for response headers should be formatted. By default, all header keys * are lower cased. */ - 'header_key_format'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat); + 'header_key_format'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat | null); /** * Enables trailers for HTTP/1. By default the HTTP/1 codec drops proxied trailers. * @@ -92,7 +92,7 @@ export interface Http1ProtocolOptions { * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging * `. */ - 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue); + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); } /** @@ -105,7 +105,7 @@ export interface Http1ProtocolOptions__Output { * envoy as their HTTP proxy. In Unix, for example, this is typically done by setting the * *http_proxy* environment variable. */ - 'allow_absolute_url'?: (_google_protobuf_BoolValue__Output); + 'allow_absolute_url': (_google_protobuf_BoolValue__Output | null); /** * Handle incoming HTTP/1.0 and HTTP 0.9 requests. * This is off by default, and not fully standards compliant. There is support for pre-HTTP/1.1 @@ -123,7 +123,7 @@ export interface Http1ProtocolOptions__Output { * Describes how the keys for response headers should be formatted. By default, all header keys * are lower cased. */ - 'header_key_format'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Output); + 'header_key_format': (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Output | null); /** * Enables trailers for HTTP/1. By default the HTTP/1 codec drops proxied trailers. * @@ -155,5 +155,5 @@ export interface Http1ProtocolOptions__Output { * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging * `. */ - 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue__Output); + 'override_stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts index e379d44be..52908c961 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts @@ -12,11 +12,11 @@ export interface _envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter { /** * The 16 bit parameter identifier. */ - 'identifier'?: (_google_protobuf_UInt32Value); + 'identifier'?: (_google_protobuf_UInt32Value | null); /** * The 32 bit parameter value. */ - 'value'?: (_google_protobuf_UInt32Value); + 'value'?: (_google_protobuf_UInt32Value | null); } /** @@ -27,11 +27,11 @@ export interface _envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter__O /** * The 16 bit parameter identifier. */ - 'identifier'?: (_google_protobuf_UInt32Value__Output); + 'identifier': (_google_protobuf_UInt32Value__Output | null); /** * The 32 bit parameter value. */ - 'value'?: (_google_protobuf_UInt32Value__Output); + 'value': (_google_protobuf_UInt32Value__Output | null); } /** @@ -44,7 +44,7 @@ export interface Http2ProtocolOptions { * range from 0 to 4294967295 (2^32 - 1) and defaults to 4096. 0 effectively disables header * compression. */ - 'hpack_table_size'?: (_google_protobuf_UInt32Value); + 'hpack_table_size'?: (_google_protobuf_UInt32Value | null); /** * `Maximum concurrent streams `_ * allowed for peer on one HTTP/2 connection. Valid values range from 1 to 2147483647 (2^31 - 1) @@ -54,7 +54,7 @@ export interface Http2ProtocolOptions { * on a single connection. If the limit is reached, Envoy may queue requests or establish * additional connections (as allowed per circuit breaker limits). */ - 'max_concurrent_streams'?: (_google_protobuf_UInt32Value); + 'max_concurrent_streams'?: (_google_protobuf_UInt32Value | null); /** * `Initial stream-level flow-control window * `_ size. Valid values range from 65535 @@ -68,12 +68,12 @@ export interface Http2ProtocolOptions { * HTTP/2 codec buffers. Once the buffer reaches this pointer, watermark callbacks will fire to * stop the flow of data to the codec buffers. */ - 'initial_stream_window_size'?: (_google_protobuf_UInt32Value); + 'initial_stream_window_size'?: (_google_protobuf_UInt32Value | null); /** * Similar to *initial_stream_window_size*, but for connection-level flow-control * window. Currently, this has the same minimum/maximum/default as *initial_stream_window_size*. */ - 'initial_connection_window_size'?: (_google_protobuf_UInt32Value); + 'initial_connection_window_size'?: (_google_protobuf_UInt32Value | null); /** * Allows proxying Websocket and other upgrades over H2 connect. */ @@ -95,7 +95,7 @@ export interface Http2ProtocolOptions { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_outbound_frames'?: (_google_protobuf_UInt32Value); + 'max_outbound_frames'?: (_google_protobuf_UInt32Value | null); /** * Limit the number of pending outbound downstream frames of types PING, SETTINGS and RST_STREAM, * preventing high memory utilization when receiving continuous stream of these frames. Exceeding @@ -105,7 +105,7 @@ export interface Http2ProtocolOptions { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_outbound_control_frames'?: (_google_protobuf_UInt32Value); + 'max_outbound_control_frames'?: (_google_protobuf_UInt32Value | null); /** * Limit the number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA with an * empty payload and no end stream flag. Those frames have no legitimate use and are abusive, but @@ -116,7 +116,7 @@ export interface Http2ProtocolOptions { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_consecutive_inbound_frames_with_empty_payload'?: (_google_protobuf_UInt32Value); + 'max_consecutive_inbound_frames_with_empty_payload'?: (_google_protobuf_UInt32Value | null); /** * Limit the number of inbound PRIORITY frames allowed per each opened stream. If the number * of PRIORITY frames received over the lifetime of connection exceeds the value calculated @@ -132,7 +132,7 @@ export interface Http2ProtocolOptions { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_inbound_priority_frames_per_stream'?: (_google_protobuf_UInt32Value); + 'max_inbound_priority_frames_per_stream'?: (_google_protobuf_UInt32Value | null); /** * Limit the number of inbound WINDOW_UPDATE frames allowed per DATA frame sent. If the number * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated @@ -151,7 +151,7 @@ export interface Http2ProtocolOptions { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_inbound_window_update_frames_per_data_frame_sent'?: (_google_protobuf_UInt32Value); + 'max_inbound_window_update_frames_per_data_frame_sent'?: (_google_protobuf_UInt32Value | null); /** * Allows invalid HTTP messaging and headers. When this option is disabled (default), then * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, @@ -206,12 +206,12 @@ export interface Http2ProtocolOptions { * * See `RFC7540, sec. 8.1 `_ for details. */ - 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue); + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); /** * Send HTTP/2 PING frames to verify that the connection is still healthy. If the remote peer * does not respond within the configured timeout, the connection will be aborted. */ - 'connection_keepalive'?: (_envoy_config_core_v3_KeepaliveSettings); + 'connection_keepalive'?: (_envoy_config_core_v3_KeepaliveSettings | null); } /** @@ -224,7 +224,7 @@ export interface Http2ProtocolOptions__Output { * range from 0 to 4294967295 (2^32 - 1) and defaults to 4096. 0 effectively disables header * compression. */ - 'hpack_table_size'?: (_google_protobuf_UInt32Value__Output); + 'hpack_table_size': (_google_protobuf_UInt32Value__Output | null); /** * `Maximum concurrent streams `_ * allowed for peer on one HTTP/2 connection. Valid values range from 1 to 2147483647 (2^31 - 1) @@ -234,7 +234,7 @@ export interface Http2ProtocolOptions__Output { * on a single connection. If the limit is reached, Envoy may queue requests or establish * additional connections (as allowed per circuit breaker limits). */ - 'max_concurrent_streams'?: (_google_protobuf_UInt32Value__Output); + 'max_concurrent_streams': (_google_protobuf_UInt32Value__Output | null); /** * `Initial stream-level flow-control window * `_ size. Valid values range from 65535 @@ -248,12 +248,12 @@ export interface Http2ProtocolOptions__Output { * HTTP/2 codec buffers. Once the buffer reaches this pointer, watermark callbacks will fire to * stop the flow of data to the codec buffers. */ - 'initial_stream_window_size'?: (_google_protobuf_UInt32Value__Output); + 'initial_stream_window_size': (_google_protobuf_UInt32Value__Output | null); /** * Similar to *initial_stream_window_size*, but for connection-level flow-control * window. Currently, this has the same minimum/maximum/default as *initial_stream_window_size*. */ - 'initial_connection_window_size'?: (_google_protobuf_UInt32Value__Output); + 'initial_connection_window_size': (_google_protobuf_UInt32Value__Output | null); /** * Allows proxying Websocket and other upgrades over H2 connect. */ @@ -275,7 +275,7 @@ export interface Http2ProtocolOptions__Output { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_outbound_frames'?: (_google_protobuf_UInt32Value__Output); + 'max_outbound_frames': (_google_protobuf_UInt32Value__Output | null); /** * Limit the number of pending outbound downstream frames of types PING, SETTINGS and RST_STREAM, * preventing high memory utilization when receiving continuous stream of these frames. Exceeding @@ -285,7 +285,7 @@ export interface Http2ProtocolOptions__Output { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_outbound_control_frames'?: (_google_protobuf_UInt32Value__Output); + 'max_outbound_control_frames': (_google_protobuf_UInt32Value__Output | null); /** * Limit the number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA with an * empty payload and no end stream flag. Those frames have no legitimate use and are abusive, but @@ -296,7 +296,7 @@ export interface Http2ProtocolOptions__Output { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_consecutive_inbound_frames_with_empty_payload'?: (_google_protobuf_UInt32Value__Output); + 'max_consecutive_inbound_frames_with_empty_payload': (_google_protobuf_UInt32Value__Output | null); /** * Limit the number of inbound PRIORITY frames allowed per each opened stream. If the number * of PRIORITY frames received over the lifetime of connection exceeds the value calculated @@ -312,7 +312,7 @@ export interface Http2ProtocolOptions__Output { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_inbound_priority_frames_per_stream'?: (_google_protobuf_UInt32Value__Output); + 'max_inbound_priority_frames_per_stream': (_google_protobuf_UInt32Value__Output | null); /** * Limit the number of inbound WINDOW_UPDATE frames allowed per DATA frame sent. If the number * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated @@ -331,7 +331,7 @@ export interface Http2ProtocolOptions__Output { * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ - 'max_inbound_window_update_frames_per_data_frame_sent'?: (_google_protobuf_UInt32Value__Output); + 'max_inbound_window_update_frames_per_data_frame_sent': (_google_protobuf_UInt32Value__Output | null); /** * Allows invalid HTTP messaging and headers. When this option is disabled (default), then * the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, @@ -386,10 +386,10 @@ export interface Http2ProtocolOptions__Output { * * See `RFC7540, sec. 8.1 `_ for details. */ - 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue__Output); + 'override_stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); /** * Send HTTP/2 PING frames to verify that the connection is still healthy. If the remote peer * does not respond within the configured timeout, the connection will be aborted. */ - 'connection_keepalive'?: (_envoy_config_core_v3_KeepaliveSettings__Output); + 'connection_keepalive': (_envoy_config_core_v3_KeepaliveSettings__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts index 4a5efeebd..f689ba802 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts @@ -53,13 +53,13 @@ export interface HttpProtocolOptions { * is configured, this timeout is scaled for downstream connections according to the value for * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. */ - 'idle_timeout'?: (_google_protobuf_Duration); + 'idle_timeout'?: (_google_protobuf_Duration | null); /** * The maximum number of headers. If unconfigured, the default * maximum number of request headers allowed is 100. Requests that exceed this limit will receive * a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. */ - 'max_headers_count'?: (_google_protobuf_UInt32Value); + 'max_headers_count'?: (_google_protobuf_UInt32Value | null); /** * The maximum duration of a connection. The duration is defined as a period since a connection * was established. If not set, there is no max duration. When max_connection_duration is reached @@ -68,12 +68,12 @@ export interface HttpProtocolOptions { * `. * Note: not implemented for upstream connections. */ - 'max_connection_duration'?: (_google_protobuf_Duration); + 'max_connection_duration'?: (_google_protobuf_Duration | null); /** * Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be * reset independent of any other timeouts. If not specified, this value is not set. */ - 'max_stream_duration'?: (_google_protobuf_Duration); + 'max_stream_duration'?: (_google_protobuf_Duration | null); /** * Action to take when a client request with a header name containing underscore characters is received. * If this setting is not specified, the value defaults to ALLOW. @@ -104,13 +104,13 @@ export interface HttpProtocolOptions__Output { * is configured, this timeout is scaled for downstream connections according to the value for * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. */ - 'idle_timeout'?: (_google_protobuf_Duration__Output); + 'idle_timeout': (_google_protobuf_Duration__Output | null); /** * The maximum number of headers. If unconfigured, the default * maximum number of request headers allowed is 100. Requests that exceed this limit will receive * a 431 response for HTTP/1.x and cause a stream reset for HTTP/2. */ - 'max_headers_count'?: (_google_protobuf_UInt32Value__Output); + 'max_headers_count': (_google_protobuf_UInt32Value__Output | null); /** * The maximum duration of a connection. The duration is defined as a period since a connection * was established. If not set, there is no max duration. When max_connection_duration is reached @@ -119,12 +119,12 @@ export interface HttpProtocolOptions__Output { * `. * Note: not implemented for upstream connections. */ - 'max_connection_duration'?: (_google_protobuf_Duration__Output); + 'max_connection_duration': (_google_protobuf_Duration__Output | null); /** * Total duration to keep alive an HTTP request/response stream. If the time limit is reached the stream will be * reset independent of any other timeouts. If not specified, this value is not set. */ - 'max_stream_duration'?: (_google_protobuf_Duration__Output); + 'max_stream_duration': (_google_protobuf_Duration__Output | null); /** * Action to take when a client request with a header name containing underscore characters is received. * If this setting is not specified, the value defaults to ALLOW. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts index 5da6ed4c0..0bac9ac51 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts @@ -30,7 +30,7 @@ export interface HttpUri { /** * Sets the maximum duration in milliseconds that a response can take to arrive upon request. */ - 'timeout'?: (_google_protobuf_Duration); + 'timeout'?: (_google_protobuf_Duration | null); /** * Specify how `uri` is to be fetched. Today, this requires an explicit * cluster, but in the future we may support dynamic cluster creation or @@ -68,7 +68,7 @@ export interface HttpUri__Output { /** * Sets the maximum duration in milliseconds that a response can take to arrive upon request. */ - 'timeout'?: (_google_protobuf_Duration__Output); + 'timeout': (_google_protobuf_Duration__Output | null); /** * Specify how `uri` is to be fetched. Today, this requires an explicit * cluster, but in the future we may support dynamic cluster creation or diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts index cb2e14c58..81344f864 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts @@ -7,34 +7,34 @@ export interface KeepaliveSettings { /** * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. */ - 'interval'?: (_google_protobuf_Duration); + 'interval'?: (_google_protobuf_Duration | null); /** * How long to wait for a response to a keepalive PING. If a response is not received within this * time period, the connection will be aborted. */ - 'timeout'?: (_google_protobuf_Duration); + 'timeout'?: (_google_protobuf_Duration | null); /** * A random jitter amount as a percentage of interval that will be added to each interval. * A value of zero means there will be no jitter. * The default value is 15%. */ - 'interval_jitter'?: (_envoy_type_v3_Percent); + 'interval_jitter'?: (_envoy_type_v3_Percent | null); } export interface KeepaliveSettings__Output { /** * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. */ - 'interval'?: (_google_protobuf_Duration__Output); + 'interval': (_google_protobuf_Duration__Output | null); /** * How long to wait for a response to a keepalive PING. If a response is not received within this * time period, the connection will be aborted. */ - 'timeout'?: (_google_protobuf_Duration__Output); + 'timeout': (_google_protobuf_Duration__Output | null); /** * A random jitter amount as a percentage of interval that will be added to each interval. * A value of zero means there will be no jitter. * The default value is 15%. */ - 'interval_jitter'?: (_envoy_type_v3_Percent__Output); + 'interval_jitter': (_envoy_type_v3_Percent__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts index 6d7181f18..8d1811c67 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts @@ -63,5 +63,5 @@ export interface Metadata__Output { * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* * namespace is reserved for Envoy's built-in filters. */ - 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct__Output}); + 'filter_metadata': ({[key: string]: _google_protobuf_Struct__Output}); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts index 717263976..7218b802e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts @@ -41,11 +41,11 @@ export interface Node { * Opaque metadata extending the node identifier. Envoy will pass this * directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct); + 'metadata'?: (_google_protobuf_Struct | null); /** * Locality specifying where the Envoy instance is running. */ - 'locality'?: (_envoy_config_core_v3_Locality); + 'locality'?: (_envoy_config_core_v3_Locality | null); /** * Free-form string that identifies the entity requesting config. * E.g. "envoy" or "grpc" @@ -59,7 +59,7 @@ export interface Node { /** * Structured version of the entity requesting config. */ - 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion); + 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion | null); /** * List of extensions and their versions supported by the node. */ @@ -117,11 +117,11 @@ export interface Node__Output { * Opaque metadata extending the node identifier. Envoy will pass this * directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct__Output); + 'metadata': (_google_protobuf_Struct__Output | null); /** * Locality specifying where the Envoy instance is running. */ - 'locality'?: (_envoy_config_core_v3_Locality__Output); + 'locality': (_envoy_config_core_v3_Locality__Output | null); /** * Free-form string that identifies the entity requesting config. * E.g. "envoy" or "grpc" @@ -135,7 +135,7 @@ export interface Node__Output { /** * Structured version of the entity requesting config. */ - 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion__Output); + 'user_agent_build_version'?: (_envoy_config_core_v3_BuildVersion__Output | null); /** * List of extensions and their versions supported by the node. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts index 8f4093fd4..bf002d99c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RateLimitSettings.ts @@ -11,12 +11,12 @@ export interface RateLimitSettings { * Maximum number of tokens to be used for rate limiting discovery request calls. If not set, a * default value of 100 will be used. */ - 'max_tokens'?: (_google_protobuf_UInt32Value); + 'max_tokens'?: (_google_protobuf_UInt32Value | null); /** * Rate at which tokens will be filled per second. If not set, a default fill rate of 10 tokens * per second will be used. */ - 'fill_rate'?: (_google_protobuf_DoubleValue); + 'fill_rate'?: (_google_protobuf_DoubleValue | null); } /** @@ -27,10 +27,10 @@ export interface RateLimitSettings__Output { * Maximum number of tokens to be used for rate limiting discovery request calls. If not set, a * default value of 100 will be used. */ - 'max_tokens'?: (_google_protobuf_UInt32Value__Output); + 'max_tokens': (_google_protobuf_UInt32Value__Output | null); /** * Rate at which tokens will be filled per second. If not set, a default fill rate of 10 tokens * per second will be used. */ - 'fill_rate'?: (_google_protobuf_DoubleValue__Output); + 'fill_rate': (_google_protobuf_DoubleValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts index 99634015f..917304d97 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RemoteDataSource.ts @@ -10,7 +10,7 @@ export interface RemoteDataSource { /** * The HTTP URI to fetch the remote data. */ - 'http_uri'?: (_envoy_config_core_v3_HttpUri); + 'http_uri'?: (_envoy_config_core_v3_HttpUri | null); /** * SHA256 string for verifying data. */ @@ -18,7 +18,7 @@ export interface RemoteDataSource { /** * Retry policy for fetching remote data. */ - 'retry_policy'?: (_envoy_config_core_v3_RetryPolicy); + 'retry_policy'?: (_envoy_config_core_v3_RetryPolicy | null); } /** @@ -28,7 +28,7 @@ export interface RemoteDataSource__Output { /** * The HTTP URI to fetch the remote data. */ - 'http_uri'?: (_envoy_config_core_v3_HttpUri__Output); + 'http_uri': (_envoy_config_core_v3_HttpUri__Output | null); /** * SHA256 string for verifying data. */ @@ -36,5 +36,5 @@ export interface RemoteDataSource__Output { /** * Retry policy for fetching remote data. */ - 'retry_policy'?: (_envoy_config_core_v3_RetryPolicy__Output); + 'retry_policy': (_envoy_config_core_v3_RetryPolicy__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts index 6d0d9927b..def6f7311 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts @@ -12,12 +12,12 @@ export interface RetryPolicy { * This parameter is optional, in which case the default base interval is 1000 milliseconds. The * default maximum interval is 10 times the base interval. */ - 'retry_back_off'?: (_envoy_config_core_v3_BackoffStrategy); + 'retry_back_off'?: (_envoy_config_core_v3_BackoffStrategy | null); /** * Specifies the allowed number of retries. This parameter is optional and * defaults to 1. */ - 'num_retries'?: (_google_protobuf_UInt32Value); + 'num_retries'?: (_google_protobuf_UInt32Value | null); } /** @@ -29,10 +29,10 @@ export interface RetryPolicy__Output { * This parameter is optional, in which case the default base interval is 1000 milliseconds. The * default maximum interval is 10 times the base interval. */ - 'retry_back_off'?: (_envoy_config_core_v3_BackoffStrategy__Output); + 'retry_back_off': (_envoy_config_core_v3_BackoffStrategy__Output | null); /** * Specifies the allowed number of retries. This parameter is optional and * defaults to 1. */ - 'num_retries'?: (_google_protobuf_UInt32Value__Output); + 'num_retries': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts index 413a4f931..b6df8d617 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFeatureFlag.ts @@ -9,7 +9,7 @@ export interface RuntimeFeatureFlag { /** * Default value if runtime value is not available. */ - 'default_value'?: (_google_protobuf_BoolValue); + 'default_value'?: (_google_protobuf_BoolValue | null); /** * Runtime key to get value for comparison. This value is used if defined. The boolean value must * be represented via its @@ -25,7 +25,7 @@ export interface RuntimeFeatureFlag__Output { /** * Default value if runtime value is not available. */ - 'default_value'?: (_google_protobuf_BoolValue__Output); + 'default_value': (_google_protobuf_BoolValue__Output | null); /** * Runtime key to get value for comparison. This value is used if defined. The boolean value must * be represented via its diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts index 5610f7bdd..619bfd484 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts @@ -18,7 +18,7 @@ export interface RuntimeFractionalPercent { /** * Default value if the runtime value's for the numerator/denominator keys are not available. */ - 'default_value'?: (_envoy_type_v3_FractionalPercent); + 'default_value'?: (_envoy_type_v3_FractionalPercent | null); /** * Runtime key for a YAML representation of a FractionalPercent. */ @@ -41,7 +41,7 @@ export interface RuntimeFractionalPercent__Output { /** * Default value if the runtime value's for the numerator/denominator keys are not available. */ - 'default_value'?: (_envoy_type_v3_FractionalPercent__Output); + 'default_value': (_envoy_type_v3_FractionalPercent__Output | null); /** * Runtime key for a YAML representation of a FractionalPercent. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts index b317b5aa2..1dbe6ea4a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimePercent.ts @@ -9,7 +9,7 @@ export interface RuntimePercent { /** * Default value if runtime value is not available. */ - 'default_value'?: (_envoy_type_v3_Percent); + 'default_value'?: (_envoy_type_v3_Percent | null); /** * Runtime key to get value for comparison. This value is used if defined. */ @@ -23,7 +23,7 @@ export interface RuntimePercent__Output { /** * Default value if runtime value is not available. */ - 'default_value'?: (_envoy_type_v3_Percent__Output); + 'default_value': (_envoy_type_v3_Percent__Output | null); /** * Runtime key to get value for comparison. This value is used if defined. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts index 41bd5e539..ae3003189 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts @@ -53,7 +53,7 @@ export interface SubstitutionFormatString { * "message": "My error message" * } */ - 'json_format'?: (_google_protobuf_Struct); + 'json_format'?: (_google_protobuf_Struct | null); /** * If set to true, when command operators are evaluated to null, * @@ -91,7 +91,7 @@ export interface SubstitutionFormatString { * * upstream connect error:503:path=/foo */ - 'text_format_source'?: (_envoy_config_core_v3_DataSource); + 'text_format_source'?: (_envoy_config_core_v3_DataSource | null); /** * Specifies a collection of Formatter plugins that can be called from the access log configuration. * See the formatters extensions documentation for details. @@ -149,7 +149,7 @@ export interface SubstitutionFormatString__Output { * "message": "My error message" * } */ - 'json_format'?: (_google_protobuf_Struct__Output); + 'json_format'?: (_google_protobuf_Struct__Output | null); /** * If set to true, when command operators are evaluated to null, * @@ -187,7 +187,7 @@ export interface SubstitutionFormatString__Output { * * upstream connect error:503:path=/foo */ - 'text_format_source'?: (_envoy_config_core_v3_DataSource__Output); + 'text_format_source'?: (_envoy_config_core_v3_DataSource__Output | null); /** * Specifies a collection of Formatter plugins that can be called from the access log configuration. * See the formatters extensions documentation for details. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts index a1bac359e..1ad81091d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TcpKeepalive.ts @@ -8,18 +8,18 @@ export interface TcpKeepalive { * the connection is dead. Default is to use the OS level configuration (unless * overridden, Linux defaults to 9.) */ - 'keepalive_probes'?: (_google_protobuf_UInt32Value); + 'keepalive_probes'?: (_google_protobuf_UInt32Value | null); /** * The number of seconds a connection needs to be idle before keep-alive probes * start being sent. Default is to use the OS level configuration (unless * overridden, Linux defaults to 7200s (i.e., 2 hours.) */ - 'keepalive_time'?: (_google_protobuf_UInt32Value); + 'keepalive_time'?: (_google_protobuf_UInt32Value | null); /** * The number of seconds between keep-alive probes. Default is to use the OS * level configuration (unless overridden, Linux defaults to 75s.) */ - 'keepalive_interval'?: (_google_protobuf_UInt32Value); + 'keepalive_interval'?: (_google_protobuf_UInt32Value | null); } export interface TcpKeepalive__Output { @@ -28,16 +28,16 @@ export interface TcpKeepalive__Output { * the connection is dead. Default is to use the OS level configuration (unless * overridden, Linux defaults to 9.) */ - 'keepalive_probes'?: (_google_protobuf_UInt32Value__Output); + 'keepalive_probes': (_google_protobuf_UInt32Value__Output | null); /** * The number of seconds a connection needs to be idle before keep-alive probes * start being sent. Default is to use the OS level configuration (unless * overridden, Linux defaults to 7200s (i.e., 2 hours.) */ - 'keepalive_time'?: (_google_protobuf_UInt32Value__Output); + 'keepalive_time': (_google_protobuf_UInt32Value__Output | null); /** * The number of seconds between keep-alive probes. Default is to use the OS * level configuration (unless overridden, Linux defaults to 75s.) */ - 'keepalive_interval'?: (_google_protobuf_UInt32Value__Output); + 'keepalive_interval': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts index b14402783..0031ad52f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts @@ -14,7 +14,7 @@ export interface TransportSocket { * socket implementation. */ 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * Implementation specific configuration which depends on the implementation being instantiated. * See the supported transport socket implementations for further documentation. @@ -34,7 +34,7 @@ export interface TransportSocket__Output { * socket implementation. */ 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Implementation specific configuration which depends on the implementation being instantiated. * See the supported transport socket implementations for further documentation. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts index 788826a1b..d653f9373 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts @@ -19,7 +19,7 @@ export interface TypedExtensionConfig { * :ref:`extension configuration overview * ` for further details. */ - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); } /** @@ -39,5 +39,5 @@ export interface TypedExtensionConfig__Output { * :ref:`extension configuration overview * ` for further details. */ - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config': (_google_protobuf_Any__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts index a092f0dde..03ceb570e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts @@ -17,7 +17,7 @@ export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOver /** * Percentage of traffic that should be dropped for the category. */ - 'drop_percentage'?: (_envoy_type_v3_FractionalPercent); + 'drop_percentage'?: (_envoy_type_v3_FractionalPercent | null); } /** @@ -31,7 +31,7 @@ export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOver /** * Percentage of traffic that should be dropped for the category. */ - 'drop_percentage'?: (_envoy_type_v3_FractionalPercent__Output); + 'drop_percentage': (_envoy_type_v3_FractionalPercent__Output | null); } /** @@ -78,14 +78,14 @@ export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy { * Read more at :ref:`priority levels ` and * :ref:`localities `. */ - 'overprovisioning_factor'?: (_google_protobuf_UInt32Value); + 'overprovisioning_factor'?: (_google_protobuf_UInt32Value | null); /** * The max time until which the endpoints from this assignment can be used. * If no new assignments are received before this time expires the endpoints * are considered stale and should be marked unhealthy. * Defaults to 0 which means endpoints never go stale. */ - 'endpoint_stale_after'?: (_google_protobuf_Duration); + 'endpoint_stale_after'?: (_google_protobuf_Duration | null); } /** @@ -132,14 +132,14 @@ export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output * Read more at :ref:`priority levels ` and * :ref:`localities `. */ - 'overprovisioning_factor'?: (_google_protobuf_UInt32Value__Output); + 'overprovisioning_factor': (_google_protobuf_UInt32Value__Output | null); /** * The max time until which the endpoints from this assignment can be used. * If no new assignments are received before this time expires the endpoints * are considered stale and should be marked unhealthy. * Defaults to 0 which means endpoints never go stale. */ - 'endpoint_stale_after'?: (_google_protobuf_Duration__Output); + 'endpoint_stale_after': (_google_protobuf_Duration__Output | null); } /** @@ -169,7 +169,7 @@ export interface ClusterLoadAssignment { /** * Load balancing policy settings. */ - 'policy'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy); + 'policy'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy | null); /** * Map of named endpoints that can be referenced in LocalityLbEndpoints. * [#not-implemented-hide:] @@ -204,10 +204,10 @@ export interface ClusterLoadAssignment__Output { /** * Load balancing policy settings. */ - 'policy'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output); + 'policy': (_envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output | null); /** * Map of named endpoints that can be referenced in LocalityLbEndpoints. * [#not-implemented-hide:] */ - 'named_endpoints'?: ({[key: string]: _envoy_config_endpoint_v3_Endpoint__Output}); + 'named_endpoints': ({[key: string]: _envoy_config_endpoint_v3_Endpoint__Output}); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts index 4d15bee14..db820b0b3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts @@ -56,7 +56,7 @@ export interface ClusterStats { * and the *LoadStatsResponse* message sent from the management server, this may be longer than * the requested load reporting interval in the *LoadStatsResponse*. */ - 'load_report_interval'?: (_google_protobuf_Duration); + 'load_report_interval'?: (_google_protobuf_Duration | null); /** * Information about deliberately dropped requests for each category specified * in the DropOverload policy. @@ -100,7 +100,7 @@ export interface ClusterStats__Output { * and the *LoadStatsResponse* message sent from the management server, this may be longer than * the requested load reporting interval in the *LoadStatsResponse*. */ - 'load_report_interval'?: (_google_protobuf_Duration__Output); + 'load_report_interval': (_google_protobuf_Duration__Output | null); /** * Information about deliberately dropped requests for each category specified * in the DropOverload policy. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts index 7e1a7b346..c01987cce 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts @@ -63,7 +63,7 @@ export interface Endpoint { * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, * and will be resolved via DNS. */ - 'address'?: (_envoy_config_core_v3_Address); + 'address'?: (_envoy_config_core_v3_Address | null); /** * The optional health check configuration is used as configuration for the * health checker to contact the health checked host. @@ -73,7 +73,7 @@ export interface Endpoint { * This takes into effect only for upstream clusters with * :ref:`active health checking ` enabled. */ - 'health_check_config'?: (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig); + 'health_check_config'?: (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig | null); /** * The hostname associated with this endpoint. This hostname is not used for routing or address * resolution. If provided, it will be associated with the endpoint, and can be used for features @@ -98,7 +98,7 @@ export interface Endpoint__Output { * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, * and will be resolved via DNS. */ - 'address'?: (_envoy_config_core_v3_Address__Output); + 'address': (_envoy_config_core_v3_Address__Output | null); /** * The optional health check configuration is used as configuration for the * health checker to contact the health checked host. @@ -108,7 +108,7 @@ export interface Endpoint__Output { * This takes into effect only for upstream clusters with * :ref:`active health checking ` enabled. */ - 'health_check_config'?: (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output); + 'health_check_config': (_envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output | null); /** * The hostname associated with this endpoint. This hostname is not used for routing or address * resolution. If provided, it will be associated with the endpoint, and can be used for features diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts index 449dd8aa3..3952a6928 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts @@ -10,7 +10,7 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a * [#next-free-field: 6] */ export interface LbEndpoint { - 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint); + 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint | null); /** * Optional health status when known and supplied by EDS server. */ @@ -24,7 +24,7 @@ export interface LbEndpoint { * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. */ - 'metadata'?: (_envoy_config_core_v3_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * The optional load balancing weight of the upstream host; at least 1. * Envoy uses the load balancing weight in some of the built in load @@ -36,7 +36,7 @@ export interface LbEndpoint { * weight in a locality. The sum of the weights of all endpoints in the * endpoint's locality must not exceed uint32_t maximal value (4294967295). */ - 'load_balancing_weight'?: (_google_protobuf_UInt32Value); + 'load_balancing_weight'?: (_google_protobuf_UInt32Value | null); /** * [#not-implemented-hide:] */ @@ -52,7 +52,7 @@ export interface LbEndpoint { * [#next-free-field: 6] */ export interface LbEndpoint__Output { - 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint__Output); + 'endpoint'?: (_envoy_config_endpoint_v3_Endpoint__Output | null); /** * Optional health status when known and supplied by EDS server. */ @@ -66,7 +66,7 @@ export interface LbEndpoint__Output { * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. */ - 'metadata'?: (_envoy_config_core_v3_Metadata__Output); + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * The optional load balancing weight of the upstream host; at least 1. * Envoy uses the load balancing weight in some of the built in load @@ -78,7 +78,7 @@ export interface LbEndpoint__Output { * weight in a locality. The sum of the weights of all endpoints in the * endpoint's locality must not exceed uint32_t maximal value (4294967295). */ - 'load_balancing_weight'?: (_google_protobuf_UInt32Value__Output); + 'load_balancing_weight': (_google_protobuf_UInt32Value__Output | null); /** * [#not-implemented-hide:] */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts index 9924d2466..22f053ed0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts @@ -15,7 +15,7 @@ export interface LocalityLbEndpoints { /** * Identifies location of where the upstream hosts run. */ - 'locality'?: (_envoy_config_core_v3_Locality); + 'locality'?: (_envoy_config_core_v3_Locality | null); /** * The group of endpoints belonging to the locality specified. */ @@ -33,7 +33,7 @@ export interface LocalityLbEndpoints { * specified when locality weighted load balancing is enabled, the locality is * assigned no load. */ - 'load_balancing_weight'?: (_google_protobuf_UInt32Value); + 'load_balancing_weight'?: (_google_protobuf_UInt32Value | null); /** * Optional: the priority for this LocalityLbEndpoints. If unspecified this will * default to the highest priority (0). @@ -54,7 +54,7 @@ export interface LocalityLbEndpoints { * to determine where to route the requests. * [#not-implemented-hide:] */ - 'proximity'?: (_google_protobuf_UInt32Value); + 'proximity'?: (_google_protobuf_UInt32Value | null); } /** @@ -68,7 +68,7 @@ export interface LocalityLbEndpoints__Output { /** * Identifies location of where the upstream hosts run. */ - 'locality'?: (_envoy_config_core_v3_Locality__Output); + 'locality': (_envoy_config_core_v3_Locality__Output | null); /** * The group of endpoints belonging to the locality specified. */ @@ -86,7 +86,7 @@ export interface LocalityLbEndpoints__Output { * specified when locality weighted load balancing is enabled, the locality is * assigned no load. */ - 'load_balancing_weight'?: (_google_protobuf_UInt32Value__Output); + 'load_balancing_weight': (_google_protobuf_UInt32Value__Output | null); /** * Optional: the priority for this LocalityLbEndpoints. If unspecified this will * default to the highest priority (0). @@ -107,5 +107,5 @@ export interface LocalityLbEndpoints__Output { * to determine where to route the requests. * [#not-implemented-hide:] */ - 'proximity'?: (_google_protobuf_UInt32Value__Output); + 'proximity': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts index 8fe66d6c4..ab06afb39 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamEndpointStats.ts @@ -12,7 +12,7 @@ export interface UpstreamEndpointStats { /** * Upstream host address. */ - 'address'?: (_envoy_config_core_v3_Address); + 'address'?: (_envoy_config_core_v3_Address | null); /** * The total number of requests successfully completed by the endpoints in the * locality. These include non-5xx responses for HTTP, where errors @@ -45,7 +45,7 @@ export interface UpstreamEndpointStats { * Opaque and implementation dependent metadata of the * endpoint. Envoy will pass this directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct); + 'metadata'?: (_google_protobuf_Struct | null); /** * The total number of requests that were issued to this endpoint * since the last report. A single TCP connection, HTTP or gRPC @@ -61,7 +61,7 @@ export interface UpstreamEndpointStats__Output { /** * Upstream host address. */ - 'address'?: (_envoy_config_core_v3_Address__Output); + 'address': (_envoy_config_core_v3_Address__Output | null); /** * The total number of requests successfully completed by the endpoints in the * locality. These include non-5xx responses for HTTP, where errors @@ -94,7 +94,7 @@ export interface UpstreamEndpointStats__Output { * Opaque and implementation dependent metadata of the * endpoint. Envoy will pass this directly to the management server. */ - 'metadata'?: (_google_protobuf_Struct__Output); + 'metadata': (_google_protobuf_Struct__Output | null); /** * The total number of requests that were issued to this endpoint * since the last report. A single TCP connection, HTTP or gRPC diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts index 3eb8cc4eb..f00607d00 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts @@ -16,7 +16,7 @@ export interface UpstreamLocalityStats { * Name of zone, region and optionally endpoint group these metrics were * collected from. Zone and region names could be empty if unknown. */ - 'locality'?: (_envoy_config_core_v3_Locality); + 'locality'?: (_envoy_config_core_v3_Locality | null); /** * The total number of requests successfully completed by the endpoints in the * locality. @@ -65,7 +65,7 @@ export interface UpstreamLocalityStats__Output { * Name of zone, region and optionally endpoint group these metrics were * collected from. Zone and region names could be empty if unknown. */ - 'locality'?: (_envoy_config_core_v3_Locality__Output); + 'locality': (_envoy_config_core_v3_Locality__Output | null); /** * The total number of requests successfully completed by the endpoints in the * locality. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts index 508236641..4977911fb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts @@ -17,7 +17,7 @@ export interface ApiListener { * and http_connection_manager.proto depends on rds.proto, which is in the same directory as * lds.proto, so lds.proto cannot depend on this file.] */ - 'api_listener'?: (_google_protobuf_Any); + 'api_listener'?: (_google_protobuf_Any | null); } /** @@ -35,5 +35,5 @@ export interface ApiListener__Output { * and http_connection_manager.proto depends on rds.proto, which is in the same directory as * lds.proto, so lds.proto cannot depend on this file.] */ - 'api_listener'?: (_google_protobuf_Any__Output); + 'api_listener': (_google_protobuf_Any__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts index a3d26c904..3e38fbc00 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts @@ -16,14 +16,14 @@ export interface Filter { * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. */ - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * Configuration source specifier for an extension configuration discovery * service. In case of a failure and without the default configuration, the * listener closes the connections. * [#not-implemented-hide:] */ - 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource); + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource | null); 'config_type'?: "typed_config"|"config_discovery"; } @@ -40,13 +40,13 @@ export interface Filter__Output { * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. */ - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Configuration source specifier for an extension configuration discovery * service. In case of a failure and without the default configuration, the * listener closes the connections. * [#not-implemented-hide:] */ - 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output); + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output | null); 'config_type': "typed_config"|"config_discovery"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts index e7f0adb7c..d72fa824c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts @@ -23,7 +23,7 @@ export interface _envoy_config_listener_v3_FilterChain_OnDemandConfiguration { * Upon failure or timeout, all connections related to this filter chain will be closed. * Rebuilding will start again on the next new connection. */ - 'rebuild_timeout'?: (_google_protobuf_Duration); + 'rebuild_timeout'?: (_google_protobuf_Duration | null); } /** @@ -42,7 +42,7 @@ export interface _envoy_config_listener_v3_FilterChain_OnDemandConfiguration__Ou * Upon failure or timeout, all connections related to this filter chain will be closed. * Rebuilding will start again on the next new connection. */ - 'rebuild_timeout'?: (_google_protobuf_Duration__Output); + 'rebuild_timeout': (_google_protobuf_Duration__Output | null); } /** @@ -54,7 +54,7 @@ export interface FilterChain { /** * The criteria to use when matching a connection to this filter chain. */ - 'filter_chain_match'?: (_envoy_config_listener_v3_FilterChainMatch); + 'filter_chain_match'?: (_envoy_config_listener_v3_FilterChainMatch | null); /** * A list of individual network filters that make up the filter chain for * connections established with the listener. Order matters as the filters are @@ -74,11 +74,11 @@ export interface FilterChain { * :ref:`PROXY protocol listener filter ` * explicitly instead. */ - 'use_proxy_proto'?: (_google_protobuf_BoolValue); + 'use_proxy_proto'?: (_google_protobuf_BoolValue | null); /** * [#not-implemented-hide:] filter chain metadata. */ - 'metadata'?: (_envoy_config_core_v3_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * Optional custom transport socket implementation to use for downstream connections. * To setup TLS, set a transport socket with name `tls` and @@ -86,7 +86,7 @@ export interface FilterChain { * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ - 'transport_socket'?: (_envoy_config_core_v3_TransportSocket); + 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); /** * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter @@ -98,13 +98,13 @@ export interface FilterChain { * If this field is not empty, the filter chain will be built on-demand. * Otherwise, the filter chain will be built normally and block listener warming. */ - 'on_demand_configuration'?: (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration); + 'on_demand_configuration'?: (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration | null); /** * If present and nonzero, the amount of time to allow incoming connections to complete any * transport socket negotiations. If this expires before the transport reports connection * establishment, the connection is summarily closed. */ - 'transport_socket_connect_timeout'?: (_google_protobuf_Duration); + 'transport_socket_connect_timeout'?: (_google_protobuf_Duration | null); } /** @@ -116,7 +116,7 @@ export interface FilterChain__Output { /** * The criteria to use when matching a connection to this filter chain. */ - 'filter_chain_match'?: (_envoy_config_listener_v3_FilterChainMatch__Output); + 'filter_chain_match': (_envoy_config_listener_v3_FilterChainMatch__Output | null); /** * A list of individual network filters that make up the filter chain for * connections established with the listener. Order matters as the filters are @@ -136,11 +136,11 @@ export interface FilterChain__Output { * :ref:`PROXY protocol listener filter ` * explicitly instead. */ - 'use_proxy_proto'?: (_google_protobuf_BoolValue__Output); + 'use_proxy_proto': (_google_protobuf_BoolValue__Output | null); /** * [#not-implemented-hide:] filter chain metadata. */ - 'metadata'?: (_envoy_config_core_v3_Metadata__Output); + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * Optional custom transport socket implementation to use for downstream connections. * To setup TLS, set a transport socket with name `tls` and @@ -148,7 +148,7 @@ export interface FilterChain__Output { * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ - 'transport_socket'?: (_envoy_config_core_v3_TransportSocket__Output); + 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); /** * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter @@ -160,11 +160,11 @@ export interface FilterChain__Output { * If this field is not empty, the filter chain will be built on-demand. * Otherwise, the filter chain will be built normally and block listener warming. */ - 'on_demand_configuration'?: (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration__Output); + 'on_demand_configuration': (_envoy_config_listener_v3_FilterChain_OnDemandConfiguration__Output | null); /** * If present and nonzero, the amount of time to allow incoming connections to complete any * transport socket negotiations. If this expires before the transport reports connection * establishment, the connection is summarily closed. */ - 'transport_socket_connect_timeout'?: (_google_protobuf_Duration__Output); + 'transport_socket_connect_timeout': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts index 1170232ae..042df6dea 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts @@ -78,7 +78,7 @@ export interface FilterChainMatch { /** * [#not-implemented-hide:] */ - 'suffix_len'?: (_google_protobuf_UInt32Value); + 'suffix_len'?: (_google_protobuf_UInt32Value | null); /** * The criteria is satisfied if the source IP address of the downstream * connection is contained in at least one of the specified subnets. If the @@ -96,7 +96,7 @@ export interface FilterChainMatch { * Optional destination port to consider when use_original_dst is set on the * listener in determining a filter chain match. */ - 'destination_port'?: (_google_protobuf_UInt32Value); + 'destination_port'?: (_google_protobuf_UInt32Value | null); /** * If non-empty, a transport protocol to consider when determining a filter chain match. * This value will be compared against the transport protocol of a new connection, when @@ -211,7 +211,7 @@ export interface FilterChainMatch__Output { /** * [#not-implemented-hide:] */ - 'suffix_len'?: (_google_protobuf_UInt32Value__Output); + 'suffix_len': (_google_protobuf_UInt32Value__Output | null); /** * The criteria is satisfied if the source IP address of the downstream * connection is contained in at least one of the specified subnets. If the @@ -229,7 +229,7 @@ export interface FilterChainMatch__Output { * Optional destination port to consider when use_original_dst is set on the * listener in determining a filter chain match. */ - 'destination_port'?: (_google_protobuf_UInt32Value__Output); + 'destination_port': (_google_protobuf_UInt32Value__Output | null); /** * If non-empty, a transport protocol to consider when determining a filter chain match. * This value will be compared against the transport protocol of a new connection, when diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts index ebcac4381..8e0fc55f6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts @@ -21,7 +21,7 @@ export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig { /** * If specified, the listener will use the exact connection balancer. */ - 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance); + 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance | null); 'balance_type'?: "exact_balance"; } @@ -32,7 +32,7 @@ export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Out /** * If specified, the listener will use the exact connection balancer. */ - 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance__Output); + 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance__Output | null); 'balance_type': "exact_balance"; } @@ -48,7 +48,7 @@ export interface _envoy_config_listener_v3_Listener_DeprecatedV1 { * This is deprecated. Use :ref:`Listener.bind_to_port * ` */ - 'bind_to_port'?: (_google_protobuf_BoolValue); + 'bind_to_port'?: (_google_protobuf_BoolValue | null); } /** @@ -63,7 +63,7 @@ export interface _envoy_config_listener_v3_Listener_DeprecatedV1__Output { * This is deprecated. Use :ref:`Listener.bind_to_port * ` */ - 'bind_to_port'?: (_google_protobuf_BoolValue__Output); + 'bind_to_port': (_google_protobuf_BoolValue__Output | null); } // Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto @@ -119,7 +119,7 @@ export interface Listener { * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on * Linux as the actual port will be allocated by the OS. */ - 'address'?: (_envoy_config_core_v3_Address); + 'address'?: (_envoy_config_core_v3_Address | null); /** * A list of filter chains to consider for this listener. The * :ref:`FilterChain ` with the most specific @@ -137,20 +137,20 @@ export interface Listener { * original destination address. If there is no listener associated with the original destination * address, the connection is handled by the listener that receives it. Defaults to false. */ - 'use_original_dst'?: (_google_protobuf_BoolValue); + 'use_original_dst'?: (_google_protobuf_BoolValue | null); /** * Soft limit on size of the listener’s new connection read and write buffers. * If unspecified, an implementation defined default is applied (1MiB). */ - 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value); + 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); /** * Listener metadata. */ - 'metadata'?: (_envoy_config_core_v3_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * [#not-implemented-hide:] */ - 'deprecated_v1'?: (_envoy_config_listener_v3_Listener_DeprecatedV1); + 'deprecated_v1'?: (_envoy_config_listener_v3_Listener_DeprecatedV1 | null); /** * The type of draining to perform at a listener-wide level. */ @@ -183,7 +183,7 @@ export interface Listener { * When this flag is not set (default), the socket is not modified, i.e. the transparent option * is neither set nor reset. */ - 'transparent'?: (_google_protobuf_BoolValue); + 'transparent'?: (_google_protobuf_BoolValue | null); /** * Whether the listener should set the *IP_FREEBIND* socket option. When this * flag is set to true, listeners can be bound to an IP address that is not @@ -192,7 +192,7 @@ export interface Listener { * (default), the socket is not modified, i.e. the option is neither enabled * nor disabled. */ - 'freebind'?: (_google_protobuf_BoolValue); + 'freebind'?: (_google_protobuf_BoolValue | null); /** * Whether the listener should accept TCP Fast Open (TFO) connections. * When this flag is set to a value greater than 0, the option TCP_FASTOPEN is enabled on @@ -209,7 +209,7 @@ export interface Listener { * On macOS, only values of 0, 1, and unset are valid; other values may result in an error. * To set the queue length on macOS, set the net.inet.tcp.fastopen_backlog kernel parameter. */ - 'tcp_fast_open_queue_length'?: (_google_protobuf_UInt32Value); + 'tcp_fast_open_queue_length'?: (_google_protobuf_UInt32Value | null); /** * Additional socket options that may not be present in Envoy source code or * precompiled binaries. @@ -221,7 +221,7 @@ export interface Listener { * `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the * timeout. If not specified, a default timeout of 15s is used. */ - 'listener_filters_timeout'?: (_google_protobuf_Duration); + 'listener_filters_timeout'?: (_google_protobuf_Duration | null); /** * Specifies the intended direction of the traffic relative to the local Envoy. */ @@ -244,7 +244,7 @@ export interface Listener { * ` = "raw_udp_listener" for * creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". */ - 'udp_listener_config'?: (_envoy_config_listener_v3_UdpListenerConfig); + 'udp_listener_config'?: (_envoy_config_listener_v3_UdpListenerConfig | null); /** * Used to represent an API listener, which is used in non-proxy clients. The type of API * exposed to the non-proxy application depends on the type of API listener. @@ -263,13 +263,13 @@ export interface Listener { * socket listener and the various types of API listener. That way, a given Listener message * can structurally only contain the fields of the relevant type.] */ - 'api_listener'?: (_envoy_config_listener_v3_ApiListener); + 'api_listener'?: (_envoy_config_listener_v3_ApiListener | null); /** * The listener's connection balancer configuration, currently only applicable to TCP listeners. * If no configuration is specified, Envoy will not attempt to balance active connections between * worker threads. */ - 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig); + 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig | null); /** * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and * create one socket for each worker thread. This makes inbound connections @@ -298,24 +298,24 @@ export interface Listener { * If not present, treat it as "udp_default_writer". * [#not-implemented-hide:] */ - 'udp_writer_config'?: (_envoy_config_core_v3_TypedExtensionConfig); + 'udp_writer_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); /** * The maximum length a tcp listener's pending connections queue can grow to. If no value is * provided net.core.somaxconn will be used on Linux and 128 otherwise. */ - 'tcp_backlog_size'?: (_google_protobuf_UInt32Value); + 'tcp_backlog_size'?: (_google_protobuf_UInt32Value | null); /** * The default filter chain if none of the filter chain matches. If no default filter chain is supplied, * the connection will be closed. The filter chain match is ignored in this field. */ - 'default_filter_chain'?: (_envoy_config_listener_v3_FilterChain); + 'default_filter_chain'?: (_envoy_config_listener_v3_FilterChain | null); /** * Whether the listener should bind to the port. A listener that doesn't * bind can only receive connections redirected from other listeners that set * :ref:`use_original_dst ` * to true. Default is true. */ - 'bind_to_port'?: (_google_protobuf_BoolValue); + 'bind_to_port'?: (_google_protobuf_BoolValue | null); } /** @@ -333,7 +333,7 @@ export interface Listener__Output { * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on * Linux as the actual port will be allocated by the OS. */ - 'address'?: (_envoy_config_core_v3_Address__Output); + 'address': (_envoy_config_core_v3_Address__Output | null); /** * A list of filter chains to consider for this listener. The * :ref:`FilterChain ` with the most specific @@ -351,20 +351,20 @@ export interface Listener__Output { * original destination address. If there is no listener associated with the original destination * address, the connection is handled by the listener that receives it. Defaults to false. */ - 'use_original_dst'?: (_google_protobuf_BoolValue__Output); + 'use_original_dst': (_google_protobuf_BoolValue__Output | null); /** * Soft limit on size of the listener’s new connection read and write buffers. * If unspecified, an implementation defined default is applied (1MiB). */ - 'per_connection_buffer_limit_bytes'?: (_google_protobuf_UInt32Value__Output); + 'per_connection_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); /** * Listener metadata. */ - 'metadata'?: (_envoy_config_core_v3_Metadata__Output); + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * [#not-implemented-hide:] */ - 'deprecated_v1'?: (_envoy_config_listener_v3_Listener_DeprecatedV1__Output); + 'deprecated_v1': (_envoy_config_listener_v3_Listener_DeprecatedV1__Output | null); /** * The type of draining to perform at a listener-wide level. */ @@ -397,7 +397,7 @@ export interface Listener__Output { * When this flag is not set (default), the socket is not modified, i.e. the transparent option * is neither set nor reset. */ - 'transparent'?: (_google_protobuf_BoolValue__Output); + 'transparent': (_google_protobuf_BoolValue__Output | null); /** * Whether the listener should set the *IP_FREEBIND* socket option. When this * flag is set to true, listeners can be bound to an IP address that is not @@ -406,7 +406,7 @@ export interface Listener__Output { * (default), the socket is not modified, i.e. the option is neither enabled * nor disabled. */ - 'freebind'?: (_google_protobuf_BoolValue__Output); + 'freebind': (_google_protobuf_BoolValue__Output | null); /** * Whether the listener should accept TCP Fast Open (TFO) connections. * When this flag is set to a value greater than 0, the option TCP_FASTOPEN is enabled on @@ -423,7 +423,7 @@ export interface Listener__Output { * On macOS, only values of 0, 1, and unset are valid; other values may result in an error. * To set the queue length on macOS, set the net.inet.tcp.fastopen_backlog kernel parameter. */ - 'tcp_fast_open_queue_length'?: (_google_protobuf_UInt32Value__Output); + 'tcp_fast_open_queue_length': (_google_protobuf_UInt32Value__Output | null); /** * Additional socket options that may not be present in Envoy source code or * precompiled binaries. @@ -435,7 +435,7 @@ export interface Listener__Output { * `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the * timeout. If not specified, a default timeout of 15s is used. */ - 'listener_filters_timeout'?: (_google_protobuf_Duration__Output); + 'listener_filters_timeout': (_google_protobuf_Duration__Output | null); /** * Specifies the intended direction of the traffic relative to the local Envoy. */ @@ -458,7 +458,7 @@ export interface Listener__Output { * ` = "raw_udp_listener" for * creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". */ - 'udp_listener_config'?: (_envoy_config_listener_v3_UdpListenerConfig__Output); + 'udp_listener_config': (_envoy_config_listener_v3_UdpListenerConfig__Output | null); /** * Used to represent an API listener, which is used in non-proxy clients. The type of API * exposed to the non-proxy application depends on the type of API listener. @@ -477,13 +477,13 @@ export interface Listener__Output { * socket listener and the various types of API listener. That way, a given Listener message * can structurally only contain the fields of the relevant type.] */ - 'api_listener'?: (_envoy_config_listener_v3_ApiListener__Output); + 'api_listener': (_envoy_config_listener_v3_ApiListener__Output | null); /** * The listener's connection balancer configuration, currently only applicable to TCP listeners. * If no configuration is specified, Envoy will not attempt to balance active connections between * worker threads. */ - 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output); + 'connection_balance_config': (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output | null); /** * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and * create one socket for each worker thread. This makes inbound connections @@ -512,22 +512,22 @@ export interface Listener__Output { * If not present, treat it as "udp_default_writer". * [#not-implemented-hide:] */ - 'udp_writer_config'?: (_envoy_config_core_v3_TypedExtensionConfig__Output); + 'udp_writer_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); /** * The maximum length a tcp listener's pending connections queue can grow to. If no value is * provided net.core.somaxconn will be used on Linux and 128 otherwise. */ - 'tcp_backlog_size'?: (_google_protobuf_UInt32Value__Output); + 'tcp_backlog_size': (_google_protobuf_UInt32Value__Output | null); /** * The default filter chain if none of the filter chain matches. If no default filter chain is supplied, * the connection will be closed. The filter chain match is ignored in this field. */ - 'default_filter_chain'?: (_envoy_config_listener_v3_FilterChain__Output); + 'default_filter_chain': (_envoy_config_listener_v3_FilterChain__Output | null); /** * Whether the listener should bind to the port. A listener that doesn't * bind can only receive connections redirected from other listeners that set * :ref:`use_original_dst ` * to true. Default is true. */ - 'bind_to_port'?: (_google_protobuf_BoolValue__Output); + 'bind_to_port': (_google_protobuf_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts index e8a827618..beba60dce 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts @@ -9,13 +9,13 @@ export interface ListenerFilter { * :ref:`supported filter `. */ 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. * See :ref:`ListenerFilterChainMatchPredicate ` * for further examples. */ - 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate); + 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate | null); /** * Filter specific configuration which depends on the filter being instantiated. * See the supported filters for further documentation. @@ -29,13 +29,13 @@ export interface ListenerFilter__Output { * :ref:`supported filter `. */ 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. * See :ref:`ListenerFilterChainMatchPredicate ` * for further examples. */ - 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output); + 'filter_disabled': (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output | null); /** * Filter specific configuration which depends on the filter being instantiated. * See the supported filters for further documentation. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts index e0d9ccc3e..34f937fa1 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts @@ -57,16 +57,16 @@ export interface ListenerFilterChainMatchPredicate { * A set that describes a logical OR. If any member of the set matches, the match configuration * matches. */ - 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet); + 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet | null); /** * A set that describes a logical AND. If all members of the set match, the match configuration * matches. */ - 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet); + 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet | null); /** * A negation match. The match configuration will match if the negated match condition matches. */ - 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate); + 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate | null); /** * The match configuration will always match. */ @@ -75,7 +75,7 @@ export interface ListenerFilterChainMatchPredicate { * Match destination port. Particularly, the match evaluation must use the recovered local port if * the owning listener filter is after :ref:`an original_dst listener filter `. */ - 'destination_port_range'?: (_envoy_type_v3_Int32Range); + 'destination_port_range'?: (_envoy_type_v3_Int32Range | null); 'rule'?: "or_match"|"and_match"|"not_match"|"any_match"|"destination_port_range"; } @@ -113,16 +113,16 @@ export interface ListenerFilterChainMatchPredicate__Output { * A set that describes a logical OR. If any member of the set matches, the match configuration * matches. */ - 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output); + 'or_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output | null); /** * A set that describes a logical AND. If all members of the set match, the match configuration * matches. */ - 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output); + 'and_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate_MatchSet__Output | null); /** * A negation match. The match configuration will match if the negated match condition matches. */ - 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output); + 'not_match'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output | null); /** * The match configuration will always match. */ @@ -131,6 +131,6 @@ export interface ListenerFilterChainMatchPredicate__Output { * Match destination port. Particularly, the match evaluation must use the recovered local port if * the owning listener filter is after :ref:`an original_dst listener filter `. */ - 'destination_port_range'?: (_envoy_type_v3_Int32Range__Output); + 'destination_port_range'?: (_envoy_type_v3_Int32Range__Output | null); 'rule': "or_match"|"and_match"|"not_match"|"any_match"|"destination_port_range"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts index c7475c42b..2ac4463fb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts @@ -9,7 +9,7 @@ export interface UdpListenerConfig { * If not specified, treat as "raw_udp_listener". */ 'udp_listener_name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * Used to create a specific listener factory. To some factory, e.g. * "raw_udp_listener", config is not needed. @@ -24,7 +24,7 @@ export interface UdpListenerConfig__Output { * If not specified, treat as "raw_udp_listener". */ 'udp_listener_name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Used to create a specific listener factory. To some factory, e.g. * "raw_udp_listener", config is not needed. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts index c292a199a..03ec3218d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts @@ -27,7 +27,7 @@ export interface CorsPolicy { /** * Specifies whether the resource allows credentials. */ - 'allow_credentials'?: (_google_protobuf_BoolValue); + 'allow_credentials'?: (_google_protobuf_BoolValue | null); /** * Specifies the % of requests for which the CORS filter is enabled. * @@ -37,7 +37,7 @@ export interface CorsPolicy { * If :ref:`runtime_key ` is * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. */ - 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent); + 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); /** * Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not * enforced. @@ -49,7 +49,7 @@ export interface CorsPolicy { * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate * and track the request's *Origin* to determine if it's valid but will not enforce any policies. */ - 'shadow_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent); + 'shadow_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); /** * Specifies string patterns that match allowed origins. An origin is allowed if any of the * string matchers match. @@ -81,7 +81,7 @@ export interface CorsPolicy__Output { /** * Specifies whether the resource allows credentials. */ - 'allow_credentials'?: (_google_protobuf_BoolValue__Output); + 'allow_credentials': (_google_protobuf_BoolValue__Output | null); /** * Specifies the % of requests for which the CORS filter is enabled. * @@ -91,7 +91,7 @@ export interface CorsPolicy__Output { * If :ref:`runtime_key ` is * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. */ - 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); + 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); /** * Specifies the % of requests for which the CORS policies will be evaluated and tracked, but not * enforced. @@ -103,7 +103,7 @@ export interface CorsPolicy__Output { * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate * and track the request's *Origin* to determine if it's valid but will not enforce any policies. */ - 'shadow_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); + 'shadow_enabled': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); /** * Specifies string patterns that match allowed origins. An origin is allowed if any of the * string matchers match. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts index 00e37d98f..fa8bef1d8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Decorator.ts @@ -17,7 +17,7 @@ export interface Decorator { /** * Whether the decorated details should be propagated to the other party. The default is true. */ - 'propagate'?: (_google_protobuf_BoolValue); + 'propagate'?: (_google_protobuf_BoolValue | null); } export interface Decorator__Output { @@ -35,5 +35,5 @@ export interface Decorator__Output { /** * Whether the decorated details should be propagated to the other party. The default is true. */ - 'propagate'?: (_google_protobuf_BoolValue__Output); + 'propagate': (_google_protobuf_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts index 9088fe712..7e0c3a1b7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts @@ -17,7 +17,7 @@ export interface DirectResponseAction { * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or * :ref:`envoy_api_msg_config.route.v3.VirtualHost`. */ - 'body'?: (_envoy_config_core_v3_DataSource); + 'body'?: (_envoy_config_core_v3_DataSource | null); } export interface DirectResponseAction__Output { @@ -35,5 +35,5 @@ export interface DirectResponseAction__Output { * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or * :ref:`envoy_api_msg_config.route.v3.VirtualHost`. */ - 'body'?: (_envoy_config_core_v3_DataSource__Output); + 'body': (_envoy_config_core_v3_DataSource__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts index 78221b2c7..2765c0799 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterAction.ts @@ -6,12 +6,12 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ * A filter-defined action type. */ export interface FilterAction { - 'action'?: (_google_protobuf_Any); + 'action'?: (_google_protobuf_Any | null); } /** * A filter-defined action type. */ export interface FilterAction__Output { - 'action'?: (_google_protobuf_Any__Output); + 'action': (_google_protobuf_Any__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts index ff5976e0b..9a566c2bb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts @@ -15,7 +15,7 @@ export interface FilterConfig { /** * The filter config. */ - 'config'?: (_google_protobuf_Any); + 'config'?: (_google_protobuf_Any | null); /** * If true, the filter is optional, meaning that if the client does * not support the specified filter, it may ignore the map entry rather @@ -37,7 +37,7 @@ export interface FilterConfig__Output { /** * The filter config. */ - 'config'?: (_google_protobuf_Any__Output); + 'config': (_google_protobuf_Any__Output | null); /** * If true, the filter is optional, meaning that if the client does * not support the specified filter, it may ignore the map entry rather diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts index 8d20c5c63..ee03d1fe7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts @@ -53,7 +53,7 @@ export interface HeaderMatcher { * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, * "-1somestring" */ - 'range_match'?: (_envoy_type_v3_Int64Range); + 'range_match'?: (_envoy_type_v3_Int64Range | null); /** * If specified, header match will be performed based on whether the header is in the * request. @@ -91,7 +91,7 @@ export interface HeaderMatcher { * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. */ - 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher); + 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher | null); /** * If specified, header match will be performed based on whether the header value contains * the given value or not. @@ -157,7 +157,7 @@ export interface HeaderMatcher__Output { * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, * "-1somestring" */ - 'range_match'?: (_envoy_type_v3_Int64Range__Output); + 'range_match'?: (_envoy_type_v3_Int64Range__Output | null); /** * If specified, header match will be performed based on whether the header is in the * request. @@ -195,7 +195,7 @@ export interface HeaderMatcher__Output { * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. */ - 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher__Output); + 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); /** * If specified, header match will be performed based on whether the header value contains * the given value or not. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts index 344d825e7..413e93b27 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts @@ -13,14 +13,14 @@ export interface HedgePolicy { * Defaults to 1. * [#not-implemented-hide:] */ - 'initial_requests'?: (_google_protobuf_UInt32Value); + 'initial_requests'?: (_google_protobuf_UInt32Value | null); /** * Specifies a probability that an additional upstream request should be sent * on top of what is specified by initial_requests. * Defaults to 0. * [#not-implemented-hide:] */ - 'additional_request_chance'?: (_envoy_type_v3_FractionalPercent); + 'additional_request_chance'?: (_envoy_type_v3_FractionalPercent | null); /** * Indicates that a hedged request should be sent when the per-try timeout is hit. * This means that a retry will be issued without resetting the original request, leaving multiple upstream requests in flight. @@ -49,14 +49,14 @@ export interface HedgePolicy__Output { * Defaults to 1. * [#not-implemented-hide:] */ - 'initial_requests'?: (_google_protobuf_UInt32Value__Output); + 'initial_requests': (_google_protobuf_UInt32Value__Output | null); /** * Specifies a probability that an additional upstream request should be sent * on top of what is specified by initial_requests. * Defaults to 0. * [#not-implemented-hide:] */ - 'additional_request_chance'?: (_envoy_type_v3_FractionalPercent__Output); + 'additional_request_chance': (_envoy_type_v3_FractionalPercent__Output | null); /** * Indicates that a hedged request should be sent when the per-try timeout is hit. * This means that a retry will be issued without resetting the original request, leaving multiple upstream requests in flight. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts index 73a2bb32e..93a96c247 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts @@ -17,7 +17,7 @@ export interface InternalRedirectPolicy { * * If not specified, at most one redirect will be followed. */ - 'max_internal_redirects'?: (_google_protobuf_UInt32Value); + 'max_internal_redirects'?: (_google_protobuf_UInt32Value | null); /** * Defines what upstream response codes are allowed to trigger internal redirect. If unspecified, * only 302 will be treated as internal redirect. @@ -51,7 +51,7 @@ export interface InternalRedirectPolicy__Output { * * If not specified, at most one redirect will be followed. */ - 'max_internal_redirects'?: (_google_protobuf_UInt32Value__Output); + 'max_internal_redirects': (_google_protobuf_UInt32Value__Output | null); /** * Defines what upstream response codes are allowed to trigger internal redirect. If unspecified, * only 302 will be treated as internal redirect. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts index c88fa8ff7..d511259b0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts @@ -16,7 +16,7 @@ export interface QueryParameterMatcher { /** * Specifies whether a query parameter value should match against a string. */ - 'string_match'?: (_envoy_type_matcher_v3_StringMatcher); + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null); /** * Specifies whether a query parameter should be present. */ @@ -38,7 +38,7 @@ export interface QueryParameterMatcher__Output { /** * Specifies whether a query parameter value should match against a string. */ - 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output); + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null); /** * Specifies whether a query parameter should be present. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts index 33d93af7d..484044bdf 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts @@ -13,42 +13,42 @@ export interface _envoy_config_route_v3_RateLimit_Action { /** * Rate limit on source cluster. */ - 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster); + 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster | null); /** * Rate limit on destination cluster. */ - 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster); + 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster | null); /** * Rate limit on request headers. */ - 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders); + 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders | null); /** * Rate limit on remote address. */ - 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress); + 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress | null); /** * Rate limit on a generic key. */ - 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey); + 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey | null); /** * Rate limit on the existence of request headers. */ - 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch); + 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch | null); /** * Rate limit on dynamic metadata. * * .. attention:: * This field has been deprecated in favor of the :ref:`metadata ` field */ - 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData); + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData | null); /** * Rate limit on metadata. */ - 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData); + 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData | null); /** * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. */ - 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig); + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig | null); 'action_specifier'?: "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; } @@ -59,42 +59,42 @@ export interface _envoy_config_route_v3_RateLimit_Action__Output { /** * Rate limit on source cluster. */ - 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster__Output); + 'source_cluster'?: (_envoy_config_route_v3_RateLimit_Action_SourceCluster__Output | null); /** * Rate limit on destination cluster. */ - 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster__Output); + 'destination_cluster'?: (_envoy_config_route_v3_RateLimit_Action_DestinationCluster__Output | null); /** * Rate limit on request headers. */ - 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders__Output); + 'request_headers'?: (_envoy_config_route_v3_RateLimit_Action_RequestHeaders__Output | null); /** * Rate limit on remote address. */ - 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress__Output); + 'remote_address'?: (_envoy_config_route_v3_RateLimit_Action_RemoteAddress__Output | null); /** * Rate limit on a generic key. */ - 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey__Output); + 'generic_key'?: (_envoy_config_route_v3_RateLimit_Action_GenericKey__Output | null); /** * Rate limit on the existence of request headers. */ - 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Output); + 'header_value_match'?: (_envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Output | null); /** * Rate limit on dynamic metadata. * * .. attention:: * This field has been deprecated in favor of the :ref:`metadata ` field */ - 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output); + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output | null); /** * Rate limit on metadata. */ - 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData__Output); + 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData__Output | null); /** * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. */ - 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig__Output); + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); 'action_specifier': "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; } @@ -160,7 +160,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData { * Metadata struct that defines the key and path to retrieve the string value. A match will * only happen if the value in the dynamic metadata is of type string. */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); /** * An optional value to use if *metadata_key* is empty. If not set and * no value is present under the metadata_key then no descriptor is generated. @@ -188,7 +188,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output * Metadata struct that defines the key and path to retrieve the string value. A match will * only happen if the value in the dynamic metadata is of type string. */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); /** * An optional value to use if *metadata_key* is empty. If not set and * no value is present under the metadata_key then no descriptor is generated. @@ -206,7 +206,7 @@ export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata { * and a "unit" property with a value parseable to :ref:`RateLimitUnit * enum ` */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); } /** @@ -219,7 +219,7 @@ export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Outp * and a "unit" property with a value parseable to :ref:`RateLimitUnit * enum ` */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); } /** @@ -278,7 +278,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch { * descriptor entry when the request does not match the headers. The * default value is true. */ - 'expect_match'?: (_google_protobuf_BoolValue); + 'expect_match'?: (_google_protobuf_BoolValue | null); /** * Specifies a set of headers that the rate limit action should match * on. The action will check the request’s headers against all the @@ -307,7 +307,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Outpu * descriptor entry when the request does not match the headers. The * default value is true. */ - 'expect_match'?: (_google_protobuf_BoolValue__Output); + 'expect_match': (_google_protobuf_BoolValue__Output | null); /** * Specifies a set of headers that the rate limit action should match * on. The action will check the request’s headers against all the @@ -334,7 +334,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_MetaData { * Metadata struct that defines the key and path to retrieve the string value. A match will * only happen if the value in the metadata is of type string. */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); /** * An optional value to use if *metadata_key* is empty. If not set and * no value is present under the metadata_key then no descriptor is generated. @@ -362,7 +362,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_MetaData__Output { * Metadata struct that defines the key and path to retrieve the string value. A match will * only happen if the value in the metadata is of type string. */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); /** * An optional value to use if *metadata_key* is empty. If not set and * no value is present under the metadata_key then no descriptor is generated. @@ -378,7 +378,7 @@ export interface _envoy_config_route_v3_RateLimit_Override { /** * Limit override from dynamic metadata. */ - 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata); + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata | null); 'override_specifier'?: "dynamic_metadata"; } @@ -386,7 +386,7 @@ export interface _envoy_config_route_v3_RateLimit_Override__Output { /** * Limit override from dynamic metadata. */ - 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Output); + 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Output | null); 'override_specifier': "dynamic_metadata"; } @@ -517,7 +517,7 @@ export interface RateLimit { * * The filter supports a range of 0 - 10 inclusively for stage numbers. */ - 'stage'?: (_google_protobuf_UInt32Value); + 'stage'?: (_google_protobuf_UInt32Value | null); /** * The key to be set in runtime to disable this rate limit configuration. */ @@ -537,7 +537,7 @@ export interface RateLimit { * from metadata, no override is provided. See :ref:`rate limit override * ` for more information. */ - 'limit'?: (_envoy_config_route_v3_RateLimit_Override); + 'limit'?: (_envoy_config_route_v3_RateLimit_Override | null); } /** @@ -554,7 +554,7 @@ export interface RateLimit__Output { * * The filter supports a range of 0 - 10 inclusively for stage numbers. */ - 'stage'?: (_google_protobuf_UInt32Value__Output); + 'stage': (_google_protobuf_UInt32Value__Output | null); /** * The key to be set in runtime to disable this rate limit configuration. */ @@ -574,5 +574,5 @@ export interface RateLimit__Output { * from metadata, no override is provided. See :ref:`rate limit override * ` for more information. */ - 'limit'?: (_envoy_config_route_v3_RateLimit_Override__Output); + 'limit': (_envoy_config_route_v3_RateLimit_Override__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts index baea6bc4e..750946934 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts @@ -112,7 +112,7 @@ export interface RedirectAction { * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to * ``/aaa/yyy/bbb``. */ - 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); /** * When the scheme redirection take place, the following rules apply: * 1. If the source URI scheme is `http` and the port is explicitly @@ -209,7 +209,7 @@ export interface RedirectAction__Output { * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to * ``/aaa/yyy/bbb``. */ - 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); /** * When the scheme redirection take place, the following rules apply: * 1. If the source URI scheme is `http` and the port is explicitly diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts index 52f391dd9..d4712b2df 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts @@ -64,7 +64,7 @@ export interface _envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff { * header contains an interval longer than this then it will be discarded and * the next header will be tried. Defaults to 300 seconds. */ - 'max_interval'?: (_google_protobuf_Duration); + 'max_interval'?: (_google_protobuf_Duration | null); } /** @@ -125,7 +125,7 @@ export interface _envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Out * header contains an interval longer than this then it will be discarded and * the next header will be tried. Defaults to 300 seconds. */ - 'max_interval'?: (_google_protobuf_Duration__Output); + 'max_interval': (_google_protobuf_Duration__Output | null); } export interface _envoy_config_route_v3_RetryPolicy_ResetHeader { @@ -172,14 +172,14 @@ export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff { * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's * back-off algorithm. */ - 'base_interval'?: (_google_protobuf_Duration); + 'base_interval'?: (_google_protobuf_Duration | null); /** * Specifies the maximum interval between retries. This parameter is optional, but must be * greater than or equal to the `base_interval` if set. The default is 10 times the * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion * of Envoy's back-off algorithm. */ - 'max_interval'?: (_google_protobuf_Duration); + 'max_interval'?: (_google_protobuf_Duration | null); } export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff__Output { @@ -189,37 +189,37 @@ export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff__Output { * See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's * back-off algorithm. */ - 'base_interval'?: (_google_protobuf_Duration__Output); + 'base_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the maximum interval between retries. This parameter is optional, but must be * greater than or equal to the `base_interval` if set. The default is 10 times the * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion * of Envoy's back-off algorithm. */ - 'max_interval'?: (_google_protobuf_Duration__Output); + 'max_interval': (_google_protobuf_Duration__Output | null); } export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate { 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); 'config_type'?: "typed_config"; } export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate__Output { 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); 'config_type': "typed_config"; } export interface _envoy_config_route_v3_RetryPolicy_RetryPriority { 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); 'config_type'?: "typed_config"; } export interface _envoy_config_route_v3_RetryPolicy_RetryPriority__Output { 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); 'config_type': "typed_config"; } @@ -239,7 +239,7 @@ export interface RetryPolicy { * defaults to 1. These are the same conditions documented for * :ref:`config_http_filters_router_x-envoy-max-retries`. */ - 'num_retries'?: (_google_protobuf_UInt32Value); + 'num_retries'?: (_google_protobuf_UInt32Value | null); /** * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The * same conditions documented for @@ -253,13 +253,13 @@ export interface RetryPolicy { * retry policy, a request that times out will not be retried as the total timeout budget * would have been exhausted. */ - 'per_try_timeout'?: (_google_protobuf_Duration); + 'per_try_timeout'?: (_google_protobuf_Duration | null); /** * Specifies an implementation of a RetryPriority which is used to determine the * distribution of load across priorities used for retries. Refer to * :ref:`retry plugin configuration ` for more details. */ - 'retry_priority'?: (_envoy_config_route_v3_RetryPolicy_RetryPriority); + 'retry_priority'?: (_envoy_config_route_v3_RetryPolicy_RetryPriority | null); /** * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host * for retries. If any of the predicates reject the host, host selection will be reattempted. @@ -284,7 +284,7 @@ export interface RetryPolicy { * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` * describes Envoy's back-off algorithm. */ - 'retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RetryBackOff); + 'retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RetryBackOff | null); /** * HTTP response headers that trigger a retry if present in the response. A retry will be * triggered if any of the header matches match the upstream response headers. @@ -304,7 +304,7 @@ export interface RetryPolicy { * default exponential back off strategy (configured using `retry_back_off`) * whenever a response includes the matching headers. */ - 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff); + 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff | null); } /** @@ -323,7 +323,7 @@ export interface RetryPolicy__Output { * defaults to 1. These are the same conditions documented for * :ref:`config_http_filters_router_x-envoy-max-retries`. */ - 'num_retries'?: (_google_protobuf_UInt32Value__Output); + 'num_retries': (_google_protobuf_UInt32Value__Output | null); /** * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The * same conditions documented for @@ -337,13 +337,13 @@ export interface RetryPolicy__Output { * retry policy, a request that times out will not be retried as the total timeout budget * would have been exhausted. */ - 'per_try_timeout'?: (_google_protobuf_Duration__Output); + 'per_try_timeout': (_google_protobuf_Duration__Output | null); /** * Specifies an implementation of a RetryPriority which is used to determine the * distribution of load across priorities used for retries. Refer to * :ref:`retry plugin configuration ` for more details. */ - 'retry_priority'?: (_envoy_config_route_v3_RetryPolicy_RetryPriority__Output); + 'retry_priority': (_envoy_config_route_v3_RetryPolicy_RetryPriority__Output | null); /** * Specifies a collection of RetryHostPredicates that will be consulted when selecting a host * for retries. If any of the predicates reject the host, host selection will be reattempted. @@ -368,7 +368,7 @@ export interface RetryPolicy__Output { * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` * describes Envoy's back-off algorithm. */ - 'retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RetryBackOff__Output); + 'retry_back_off': (_envoy_config_route_v3_RetryPolicy_RetryBackOff__Output | null); /** * HTTP response headers that trigger a retry if present in the response. A retry will be * triggered if any of the header matches match the upstream response headers. @@ -388,5 +388,5 @@ export interface RetryPolicy__Output { * default exponential back off strategy (configured using `retry_back_off`) * whenever a response includes the matching headers. */ - 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output); + 'rate_limited_retry_back_off': (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts index ea00564f8..7bc223f82 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts @@ -26,15 +26,15 @@ export interface Route { /** * Route matching parameters. */ - 'match'?: (_envoy_config_route_v3_RouteMatch); + 'match'?: (_envoy_config_route_v3_RouteMatch | null); /** * Route request to some upstream cluster. */ - 'route'?: (_envoy_config_route_v3_RouteAction); + 'route'?: (_envoy_config_route_v3_RouteAction | null); /** * Return a redirect. */ - 'redirect'?: (_envoy_config_route_v3_RedirectAction); + 'redirect'?: (_envoy_config_route_v3_RedirectAction | null); /** * The Metadata field can be used to provide additional information * about the route. It can be used for configuration, stats, and logging. @@ -42,15 +42,15 @@ export interface Route { * For instance, if the metadata is intended for the Router filter, * the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_config_core_v3_Metadata); + 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * Decorator for the matched route. */ - 'decorator'?: (_envoy_config_route_v3_Decorator); + 'decorator'?: (_envoy_config_route_v3_Decorator | null); /** * Return an arbitrary HTTP response directly, without proxying. */ - 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction); + 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction | null); /** * Specifies a set of headers that will be added to requests matching this * route. Headers specified at this level are applied before headers from the @@ -98,13 +98,13 @@ export interface Route { * Presence of the object defines whether the connection manager's tracing configuration * is overridden by this route specific instance. */ - 'tracing'?: (_envoy_config_route_v3_Tracing); + 'tracing'?: (_envoy_config_route_v3_Tracing | null); /** * The maximum bytes which will be buffered for retries and shadowing. * If set, the bytes actually buffered will be the minimum value of this and the * listener per_connection_buffer_limit_bytes. */ - 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value); + 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); /** * [#not-implemented-hide:] * If true, a filter will define the action (e.g., it could dynamically generate the @@ -112,7 +112,7 @@ export interface Route { * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when * implemented] */ - 'filter_action'?: (_envoy_config_route_v3_FilterAction); + 'filter_action'?: (_envoy_config_route_v3_FilterAction | null); 'action'?: "route"|"redirect"|"direct_response"|"filter_action"; } @@ -130,15 +130,15 @@ export interface Route__Output { /** * Route matching parameters. */ - 'match'?: (_envoy_config_route_v3_RouteMatch__Output); + 'match': (_envoy_config_route_v3_RouteMatch__Output | null); /** * Route request to some upstream cluster. */ - 'route'?: (_envoy_config_route_v3_RouteAction__Output); + 'route'?: (_envoy_config_route_v3_RouteAction__Output | null); /** * Return a redirect. */ - 'redirect'?: (_envoy_config_route_v3_RedirectAction__Output); + 'redirect'?: (_envoy_config_route_v3_RedirectAction__Output | null); /** * The Metadata field can be used to provide additional information * about the route. It can be used for configuration, stats, and logging. @@ -146,15 +146,15 @@ export interface Route__Output { * For instance, if the metadata is intended for the Router filter, * the filter name should be specified as *envoy.filters.http.router*. */ - 'metadata'?: (_envoy_config_core_v3_Metadata__Output); + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * Decorator for the matched route. */ - 'decorator'?: (_envoy_config_route_v3_Decorator__Output); + 'decorator': (_envoy_config_route_v3_Decorator__Output | null); /** * Return an arbitrary HTTP response directly, without proxying. */ - 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction__Output); + 'direct_response'?: (_envoy_config_route_v3_DirectResponseAction__Output | null); /** * Specifies a set of headers that will be added to requests matching this * route. Headers specified at this level are applied before headers from the @@ -193,7 +193,7 @@ export interface Route__Output { * :ref:`FilterConfig` * message to specify additional options.] */ - 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any__Output}); + 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); /** * Name for the route. */ @@ -202,13 +202,13 @@ export interface Route__Output { * Presence of the object defines whether the connection manager's tracing configuration * is overridden by this route specific instance. */ - 'tracing'?: (_envoy_config_route_v3_Tracing__Output); + 'tracing': (_envoy_config_route_v3_Tracing__Output | null); /** * The maximum bytes which will be buffered for retries and shadowing. * If set, the bytes actually buffered will be the minimum value of this and the * listener per_connection_buffer_limit_bytes. */ - 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value__Output); + 'per_request_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); /** * [#not-implemented-hide:] * If true, a filter will define the action (e.g., it could dynamically generate the @@ -216,6 +216,6 @@ export interface Route__Output { * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when * implemented] */ - 'filter_action'?: (_envoy_config_route_v3_FilterAction__Output); + 'filter_action'?: (_envoy_config_route_v3_FilterAction__Output | null); 'action': "route"|"redirect"|"direct_response"|"filter_action"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts index 797d6eda6..234aa25e0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts @@ -37,7 +37,7 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig /** * If present, the proxy protocol header will be prepended to the CONNECT payload sent upstream. */ - 'proxy_protocol_config'?: (_envoy_config_core_v3_ProxyProtocolConfig); + 'proxy_protocol_config'?: (_envoy_config_core_v3_ProxyProtocolConfig | null); /** * If set, the route will also allow forwarding POST payload as raw TCP. */ @@ -52,7 +52,7 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig_ /** * If present, the proxy protocol header will be prepended to the CONNECT payload sent upstream. */ - 'proxy_protocol_config'?: (_envoy_config_core_v3_ProxyProtocolConfig__Output); + 'proxy_protocol_config': (_envoy_config_core_v3_ProxyProtocolConfig__Output | null); /** * If set, the route will also allow forwarding POST payload as raw TCP. */ @@ -101,7 +101,7 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_Cookie { * not present. If the TTL is present and zero, the generated cookie will * be a session cookie. */ - 'ttl'?: (_google_protobuf_Duration); + 'ttl'?: (_google_protobuf_Duration | null); /** * The name of the path for the cookie. If no path is specified here, no path * will be set for the cookie. @@ -137,7 +137,7 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output { * not present. If the TTL is present and zero, the generated cookie will * be a session cookie. */ - 'ttl'?: (_google_protobuf_Duration__Output); + 'ttl': (_google_protobuf_Duration__Output | null); /** * The name of the path for the cookie. If no path is specified here, no path * will be set for the cookie. @@ -172,23 +172,23 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy { /** * Header hash policy. */ - 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header); + 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header | null); /** * Cookie hash policy. */ - 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie); + 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie | null); /** * Connection properties hash policy. */ - 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties); + 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties | null); /** * Query parameter hash policy. */ - 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter); + 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter | null); /** * Filter state hash policy. */ - 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState); + 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState | null); /** * The flag that short-circuits the hash computing. This field provides a * 'fallback' style of configuration: "if a terminal policy doesn't work, @@ -223,23 +223,23 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy__Output { /** * Header hash policy. */ - 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header__Output); + 'header'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Header__Output | null); /** * Cookie hash policy. */ - 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output); + 'cookie'?: (_envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output | null); /** * Connection properties hash policy. */ - 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties__Output); + 'connection_properties'?: (_envoy_config_route_v3_RouteAction_HashPolicy_ConnectionProperties__Output | null); /** * Query parameter hash policy. */ - 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter__Output); + 'query_parameter'?: (_envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter__Output | null); /** * Filter state hash policy. */ - 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState__Output); + 'filter_state'?: (_envoy_config_route_v3_RouteAction_HashPolicy_FilterState__Output | null); /** * The flag that short-circuits the hash computing. This field provides a * 'fallback' style of configuration: "if a terminal policy doesn't work, @@ -275,7 +275,7 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header { * If specified, the request header value will be rewritten and used * to produce the hash key. */ - 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); } export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header__Output { @@ -288,7 +288,7 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header__Output { * If specified, the request header value will be rewritten and used * to produce the hash key. */ - 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); + 'regex_rewrite': (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); } // Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto @@ -313,14 +313,14 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration { * HttpConnectionManager max_stream_duration timeout will be disabled for * this route. */ - 'max_stream_duration'?: (_google_protobuf_Duration); + 'max_stream_duration'?: (_google_protobuf_Duration | null); /** * If present, and the request contains a `grpc-timeout header * `_, use that value as the * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. * If set to 0, the `grpc-timeout` header is used without modification. */ - 'grpc_timeout_header_max'?: (_google_protobuf_Duration); + 'grpc_timeout_header_max'?: (_google_protobuf_Duration | null); /** * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by * subtracting the provided duration from the header. This is useful for allowing Envoy to set @@ -329,7 +329,7 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration { * by the client. If, after applying the offset, the resulting timeout is zero or negative, * the stream will timeout immediately. */ - 'grpc_timeout_header_offset'?: (_google_protobuf_Duration); + 'grpc_timeout_header_offset'?: (_google_protobuf_Duration | null); } export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration__Output { @@ -343,14 +343,14 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration__Output { * HttpConnectionManager max_stream_duration timeout will be disabled for * this route. */ - 'max_stream_duration'?: (_google_protobuf_Duration__Output); + 'max_stream_duration': (_google_protobuf_Duration__Output | null); /** * If present, and the request contains a `grpc-timeout header * `_, use that value as the * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. * If set to 0, the `grpc-timeout` header is used without modification. */ - 'grpc_timeout_header_max'?: (_google_protobuf_Duration__Output); + 'grpc_timeout_header_max': (_google_protobuf_Duration__Output | null); /** * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by * subtracting the provided duration from the header. This is useful for allowing Envoy to set @@ -359,7 +359,7 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration__Output { * by the client. If, after applying the offset, the resulting timeout is zero or negative, * the stream will timeout immediately. */ - 'grpc_timeout_header_offset'?: (_google_protobuf_Duration__Output); + 'grpc_timeout_header_offset': (_google_protobuf_Duration__Output | null); } export interface _envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter { @@ -409,11 +409,11 @@ export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy { * number is <= the value of the numerator N, or if the key is not present, the default * value, the request will be mirrored. */ - 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent); + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); /** * Determines if the trace span should be sampled. Defaults to true. */ - 'trace_sampled'?: (_google_protobuf_BoolValue); + 'trace_sampled'?: (_google_protobuf_BoolValue | null); } /** @@ -445,11 +445,11 @@ export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output * number is <= the value of the numerator N, or if the key is not present, the default * value, the request will be mirrored. */ - 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); + 'runtime_fraction': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); /** * Determines if the trace span should be sampled. Defaults to true. */ - 'trace_sampled'?: (_google_protobuf_BoolValue__Output); + 'trace_sampled': (_google_protobuf_BoolValue__Output | null); } /** @@ -470,14 +470,14 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig { /** * Determines if upgrades are available on this route. Defaults to true. */ - 'enabled'?: (_google_protobuf_BoolValue); + 'enabled'?: (_google_protobuf_BoolValue | null); /** * Configuration for sending data upstream as a raw data payload. This is used for * CONNECT requests, when forwarding CONNECT payload as raw TCP. * Note that CONNECT support is currently considered alpha in Envoy. * [#comment:TODO(htuch): Replace the above comment with an alpha tag. */ - 'connect_config'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig); + 'connect_config'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig | null); } /** @@ -498,14 +498,14 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig__Output { /** * Determines if upgrades are available on this route. Defaults to true. */ - 'enabled'?: (_google_protobuf_BoolValue__Output); + 'enabled': (_google_protobuf_BoolValue__Output | null); /** * Configuration for sending data upstream as a raw data payload. This is used for * CONNECT requests, when forwarding CONNECT payload as raw TCP. * Note that CONNECT support is currently considered alpha in Envoy. * [#comment:TODO(htuch): Replace the above comment with an alpha tag. */ - 'connect_config'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig__Output); + 'connect_config': (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig__Output | null); } /** @@ -540,7 +540,7 @@ export interface RouteAction { * :ref:`traffic splitting ` * for additional documentation. */ - 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster); + 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster | null); /** * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints * in the upstream cluster with metadata matching what's set in this field will be considered @@ -548,7 +548,7 @@ export interface RouteAction { * `, metadata will be merged, with values * provided there taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_config_core_v3_Metadata); + 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); /** * Indicates that during forwarding, the matched prefix (or path) should be * swapped with this value. This option allows application URLs to be rooted @@ -595,7 +595,7 @@ export interface RouteAction { * type *strict_dns* or *logical_dns*. Setting this to true with other cluster * types has no effect. */ - 'auto_host_rewrite'?: (_google_protobuf_BoolValue); + 'auto_host_rewrite'?: (_google_protobuf_BoolValue | null); /** * Specifies the upstream timeout for the route. If not specified, the default is 15s. This * spans between the point at which the entire downstream request (i.e. end-of-stream) has been @@ -609,13 +609,13 @@ export interface RouteAction { * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the * :ref:`retry overview `. */ - 'timeout'?: (_google_protobuf_Duration); + 'timeout'?: (_google_protobuf_Duration | null); /** * Indicates that the route has a retry policy. Note that if this is set, * it'll take precedence over the virtual host level retry policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy); + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy | null); /** * Optionally specifies the :ref:`routing priority `. */ @@ -633,7 +633,7 @@ export interface RouteAction { * * This field is deprecated. Please use :ref:`vh_rate_limits ` */ - 'include_vh_rate_limits'?: (_google_protobuf_BoolValue); + 'include_vh_rate_limits'?: (_google_protobuf_BoolValue | null); /** * Specifies a list of hash policies to use for ring hash load balancing. Each * hash policy is evaluated individually and the combined result is used to @@ -652,7 +652,7 @@ export interface RouteAction { /** * Indicates that the route has a CORS policy. */ - 'cors'?: (_envoy_config_route_v3_CorsPolicy); + 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); /** * The HTTP status code to use when configured cluster is not found. * The default response code is 503 Service Unavailable. @@ -680,7 +680,7 @@ export interface RouteAction { * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the * :ref:`retry overview `. */ - 'max_grpc_timeout'?: (_google_protobuf_Duration); + 'max_grpc_timeout'?: (_google_protobuf_Duration | null); /** * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, * although the connection manager wide :ref:`stream_idle_timeout @@ -705,7 +705,7 @@ export interface RouteAction { * is configured, this timeout is scaled according to the value for * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. */ - 'idle_timeout'?: (_google_protobuf_Duration); + 'idle_timeout'?: (_google_protobuf_Duration | null); 'upgrade_configs'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig)[]; 'internal_redirect_action'?: (_envoy_config_route_v3_RouteAction_InternalRedirectAction | keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); /** @@ -713,7 +713,7 @@ export interface RouteAction { * it'll take precedence over the virtual host level hedge policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy); + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy | null); /** * Deprecated by :ref:`grpc_timeout_header_offset `. * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting @@ -724,7 +724,7 @@ export interface RouteAction { * ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning * infinity). */ - 'grpc_timeout_offset'?: (_google_protobuf_Duration); + 'grpc_timeout_offset'?: (_google_protobuf_Duration | null); /** * Indicates that during forwarding, the host header will be swapped with the content of given * downstream or :ref:`custom ` header. @@ -760,7 +760,7 @@ export interface RouteAction { * * If not specified, at most one redirect will be followed. */ - 'max_internal_redirects'?: (_google_protobuf_UInt32Value); + 'max_internal_redirects'?: (_google_protobuf_UInt32Value | null); /** * Indicates that during forwarding, portions of the path that match the * pattern should be rewritten, even allowing the substitution of capture @@ -791,7 +791,7 @@ export interface RouteAction { * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to * ``/aaa/yyy/bbb``. */ - 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); + 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that if this is set, it'll take @@ -799,14 +799,14 @@ export interface RouteAction { * most internal one becomes the enforced policy). :ref:`Retry policy ` * should not be set if this field is used. */ - 'retry_policy_typed_config'?: (_google_protobuf_Any); + 'retry_policy_typed_config'?: (_google_protobuf_Any | null); /** * If present, Envoy will try to follow an upstream redirect response instead of proxying the * response back to the downstream. An upstream redirect response is defined * by :ref:`redirect_response_codes * `. */ - 'internal_redirect_policy'?: (_envoy_config_route_v3_InternalRedirectPolicy); + 'internal_redirect_policy'?: (_envoy_config_route_v3_InternalRedirectPolicy | null); /** * Indicates that during forwarding, the host header will be swapped with * the result of the regex substitution executed on path value with query and fragment removed. @@ -824,11 +824,11 @@ export interface RouteAction { * * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. */ - 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute); + 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); /** * Specifies the maximum stream duration for this route. */ - 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration); + 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration | null); 'cluster_specifier'?: "cluster"|"cluster_header"|"weighted_clusters"; 'host_rewrite_specifier'?: "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } @@ -865,7 +865,7 @@ export interface RouteAction__Output { * :ref:`traffic splitting ` * for additional documentation. */ - 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster__Output); + 'weighted_clusters'?: (_envoy_config_route_v3_WeightedCluster__Output | null); /** * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints * in the upstream cluster with metadata matching what's set in this field will be considered @@ -873,7 +873,7 @@ export interface RouteAction__Output { * `, metadata will be merged, with values * provided there taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_config_core_v3_Metadata__Output); + 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); /** * Indicates that during forwarding, the matched prefix (or path) should be * swapped with this value. This option allows application URLs to be rooted @@ -920,7 +920,7 @@ export interface RouteAction__Output { * type *strict_dns* or *logical_dns*. Setting this to true with other cluster * types has no effect. */ - 'auto_host_rewrite'?: (_google_protobuf_BoolValue__Output); + 'auto_host_rewrite'?: (_google_protobuf_BoolValue__Output | null); /** * Specifies the upstream timeout for the route. If not specified, the default is 15s. This * spans between the point at which the entire downstream request (i.e. end-of-stream) has been @@ -934,13 +934,13 @@ export interface RouteAction__Output { * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the * :ref:`retry overview `. */ - 'timeout'?: (_google_protobuf_Duration__Output); + 'timeout': (_google_protobuf_Duration__Output | null); /** * Indicates that the route has a retry policy. Note that if this is set, * it'll take precedence over the virtual host level retry policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy__Output); + 'retry_policy': (_envoy_config_route_v3_RetryPolicy__Output | null); /** * Optionally specifies the :ref:`routing priority `. */ @@ -958,7 +958,7 @@ export interface RouteAction__Output { * * This field is deprecated. Please use :ref:`vh_rate_limits ` */ - 'include_vh_rate_limits'?: (_google_protobuf_BoolValue__Output); + 'include_vh_rate_limits': (_google_protobuf_BoolValue__Output | null); /** * Specifies a list of hash policies to use for ring hash load balancing. Each * hash policy is evaluated individually and the combined result is used to @@ -977,7 +977,7 @@ export interface RouteAction__Output { /** * Indicates that the route has a CORS policy. */ - 'cors'?: (_envoy_config_route_v3_CorsPolicy__Output); + 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); /** * The HTTP status code to use when configured cluster is not found. * The default response code is 503 Service Unavailable. @@ -1005,7 +1005,7 @@ export interface RouteAction__Output { * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the * :ref:`retry overview `. */ - 'max_grpc_timeout'?: (_google_protobuf_Duration__Output); + 'max_grpc_timeout': (_google_protobuf_Duration__Output | null); /** * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, * although the connection manager wide :ref:`stream_idle_timeout @@ -1030,7 +1030,7 @@ export interface RouteAction__Output { * is configured, this timeout is scaled according to the value for * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. */ - 'idle_timeout'?: (_google_protobuf_Duration__Output); + 'idle_timeout': (_google_protobuf_Duration__Output | null); 'upgrade_configs': (_envoy_config_route_v3_RouteAction_UpgradeConfig__Output)[]; 'internal_redirect_action': (keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); /** @@ -1038,7 +1038,7 @@ export interface RouteAction__Output { * it'll take precedence over the virtual host level hedge policy entirely * (e.g.: policies are not merged, most internal one becomes the enforced policy). */ - 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy__Output); + 'hedge_policy': (_envoy_config_route_v3_HedgePolicy__Output | null); /** * Deprecated by :ref:`grpc_timeout_header_offset `. * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting @@ -1049,7 +1049,7 @@ export interface RouteAction__Output { * ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning * infinity). */ - 'grpc_timeout_offset'?: (_google_protobuf_Duration__Output); + 'grpc_timeout_offset': (_google_protobuf_Duration__Output | null); /** * Indicates that during forwarding, the host header will be swapped with the content of given * downstream or :ref:`custom ` header. @@ -1085,7 +1085,7 @@ export interface RouteAction__Output { * * If not specified, at most one redirect will be followed. */ - 'max_internal_redirects'?: (_google_protobuf_UInt32Value__Output); + 'max_internal_redirects': (_google_protobuf_UInt32Value__Output | null); /** * Indicates that during forwarding, portions of the path that match the * pattern should be rewritten, even allowing the substitution of capture @@ -1116,7 +1116,7 @@ export interface RouteAction__Output { * would do a case-insensitive match and transform path ``/aaa/XxX/bbb`` to * ``/aaa/yyy/bbb``. */ - 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); + 'regex_rewrite': (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that if this is set, it'll take @@ -1124,14 +1124,14 @@ export interface RouteAction__Output { * most internal one becomes the enforced policy). :ref:`Retry policy ` * should not be set if this field is used. */ - 'retry_policy_typed_config'?: (_google_protobuf_Any__Output); + 'retry_policy_typed_config': (_google_protobuf_Any__Output | null); /** * If present, Envoy will try to follow an upstream redirect response instead of proxying the * response back to the downstream. An upstream redirect response is defined * by :ref:`redirect_response_codes * `. */ - 'internal_redirect_policy'?: (_envoy_config_route_v3_InternalRedirectPolicy__Output); + 'internal_redirect_policy': (_envoy_config_route_v3_InternalRedirectPolicy__Output | null); /** * Indicates that during forwarding, the host header will be swapped with * the result of the regex substitution executed on path value with query and fragment removed. @@ -1149,11 +1149,11 @@ export interface RouteAction__Output { * * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. */ - 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output); + 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); /** * Specifies the maximum stream duration for this route. */ - 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration__Output); + 'max_stream_duration': (_envoy_config_route_v3_RouteAction_MaxStreamDuration__Output | null); 'cluster_specifier': "cluster"|"cluster_header"|"weighted_clusters"; 'host_rewrite_specifier': "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts index e9ad30257..ebb3c3419 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts @@ -66,7 +66,7 @@ export interface RouteConfiguration { * option. Users may wish to override the default behavior in certain cases (for example when * using CDS with a static route table). */ - 'validate_clusters'?: (_google_protobuf_BoolValue); + 'validate_clusters'?: (_google_protobuf_BoolValue | null); /** * Specifies a list of HTTP headers that should be removed from each request * routed by the HTTP connection manager. @@ -80,7 +80,7 @@ export interface RouteConfiguration { * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration * taking precedence. */ - 'vhds'?: (_envoy_config_route_v3_Vhds); + 'vhds'?: (_envoy_config_route_v3_Vhds | null); /** * By default, headers that should be added/removed are evaluated from most to least specific: * @@ -106,7 +106,7 @@ export interface RouteConfiguration { * this to be larger than the default 4KB, since the allocated memory for direct response body * is not subject to data plane buffering controls. */ - 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value); + 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value | null); } /** @@ -169,7 +169,7 @@ export interface RouteConfiguration__Output { * option. Users may wish to override the default behavior in certain cases (for example when * using CDS with a static route table). */ - 'validate_clusters'?: (_google_protobuf_BoolValue__Output); + 'validate_clusters': (_google_protobuf_BoolValue__Output | null); /** * Specifies a list of HTTP headers that should be removed from each request * routed by the HTTP connection manager. @@ -183,7 +183,7 @@ export interface RouteConfiguration__Output { * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration * taking precedence. */ - 'vhds'?: (_envoy_config_route_v3_Vhds__Output); + 'vhds': (_envoy_config_route_v3_Vhds__Output | null); /** * By default, headers that should be added/removed are evaluated from most to least specific: * @@ -209,5 +209,5 @@ export interface RouteConfiguration__Output { * this to be larger than the default 4KB, since the allocated memory for direct response body * is not subject to data plane buffering controls. */ - 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value__Output); + 'max_direct_response_body_size_bytes': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts index d941bcfd1..ddf44da2f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts @@ -29,12 +29,12 @@ export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions { * If specified, the route will match against whether or not a certificate is presented. * If not specified, certificate presentation status (true or false) will not be considered when route matching. */ - 'presented'?: (_google_protobuf_BoolValue); + 'presented'?: (_google_protobuf_BoolValue | null); /** * If specified, the route will match against whether or not a certificate is validated. * If not specified, certificate validation status (true or false) will not be considered when route matching. */ - 'validated'?: (_google_protobuf_BoolValue); + 'validated'?: (_google_protobuf_BoolValue | null); } export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Output { @@ -42,12 +42,12 @@ export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Outpu * If specified, the route will match against whether or not a certificate is presented. * If not specified, certificate presentation status (true or false) will not be considered when route matching. */ - 'presented'?: (_google_protobuf_BoolValue__Output); + 'presented': (_google_protobuf_BoolValue__Output | null); /** * If specified, the route will match against whether or not a certificate is validated. * If not specified, certificate validation status (true or false) will not be considered when route matching. */ - 'validated'?: (_google_protobuf_BoolValue__Output); + 'validated': (_google_protobuf_BoolValue__Output | null); } /** @@ -68,7 +68,7 @@ export interface RouteMatch { * Indicates that prefix/path matching should be case sensitive. The default * is true. */ - 'case_sensitive'?: (_google_protobuf_BoolValue); + 'case_sensitive'?: (_google_protobuf_BoolValue | null); /** * Specifies a set of headers that the route should match on. The router will * check the request’s headers against all the specified headers in the route @@ -90,7 +90,7 @@ export interface RouteMatch { * that the content-type header has a application/grpc or one of the various * application/grpc+ values. */ - 'grpc'?: (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions); + 'grpc'?: (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions | null); /** * Indicates that the route should additionally match on a runtime key. Every time the route * is considered for a match, it must also fall under the percentage of matches indicated by @@ -109,7 +109,7 @@ export interface RouteMatch { * instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent * whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. */ - 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent); + 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); /** * If specified, the route is a regular expression rule meaning that the * regex must match the *:path* header once the query string is removed. The entire path @@ -124,14 +124,14 @@ export interface RouteMatch { * on :path, etc. The issue with that is it is unclear how to generically deal with query string * stripping. This needs more thought.] */ - 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher | null); /** * If specified, the client tls context will be matched against the defined * match options. * * [#next-major-version: unify with RBAC] */ - 'tls_context'?: (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions); + 'tls_context'?: (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions | null); /** * If this is used as the matcher, the matcher will only match CONNECT requests. * Note that this will not match HTTP/2 upgrade-style CONNECT requests @@ -143,7 +143,7 @@ export interface RouteMatch { * Note that CONNECT support is currently considered alpha in Envoy. * [#comment:TODO(htuch): Replace the above comment with an alpha tag. */ - 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher); + 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher | null); 'path_specifier'?: "prefix"|"path"|"safe_regex"|"connect_matcher"; } @@ -165,7 +165,7 @@ export interface RouteMatch__Output { * Indicates that prefix/path matching should be case sensitive. The default * is true. */ - 'case_sensitive'?: (_google_protobuf_BoolValue__Output); + 'case_sensitive': (_google_protobuf_BoolValue__Output | null); /** * Specifies a set of headers that the route should match on. The router will * check the request’s headers against all the specified headers in the route @@ -187,7 +187,7 @@ export interface RouteMatch__Output { * that the content-type header has a application/grpc or one of the various * application/grpc+ values. */ - 'grpc'?: (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions__Output); + 'grpc': (_envoy_config_route_v3_RouteMatch_GrpcRouteMatchOptions__Output | null); /** * Indicates that the route should additionally match on a runtime key. Every time the route * is considered for a match, it must also fall under the percentage of matches indicated by @@ -206,7 +206,7 @@ export interface RouteMatch__Output { * instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent * whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. */ - 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output); + 'runtime_fraction': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); /** * If specified, the route is a regular expression rule meaning that the * regex must match the *:path* header once the query string is removed. The entire path @@ -221,14 +221,14 @@ export interface RouteMatch__Output { * on :path, etc. The issue with that is it is unclear how to generically deal with query string * stripping. This needs more thought.] */ - 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); /** * If specified, the client tls context will be matched against the defined * match options. * * [#next-major-version: unify with RBAC] */ - 'tls_context'?: (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Output); + 'tls_context': (_envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Output | null); /** * If this is used as the matcher, the matcher will only match CONNECT requests. * Note that this will not match HTTP/2 upgrade-style CONNECT requests @@ -240,6 +240,6 @@ export interface RouteMatch__Output { * Note that CONNECT support is currently considered alpha in Envoy. * [#comment:TODO(htuch): Replace the above comment with an alpha tag. */ - 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher__Output); + 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher__Output | null); 'path_specifier': "prefix"|"path"|"safe_regex"|"connect_matcher"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts index afbb9886b..74b5fe43d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts @@ -123,7 +123,7 @@ export interface ScopedRouteConfiguration { /** * The key to match against. */ - 'key'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key); + 'key'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key | null); /** * Whether the RouteConfiguration should be loaded on demand. */ @@ -204,7 +204,7 @@ export interface ScopedRouteConfiguration__Output { /** * The key to match against. */ - 'key'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key__Output); + 'key': (_envoy_config_route_v3_ScopedRouteConfiguration_Key__Output | null); /** * Whether the RouteConfiguration should be loaded on demand. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts index 5be619a5f..e1a9220d7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts @@ -12,7 +12,7 @@ export interface Tracing { * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_v3_FractionalPercent); + 'client_sampling'?: (_envoy_type_v3_FractionalPercent | null); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -20,7 +20,7 @@ export interface Tracing { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_v3_FractionalPercent); + 'random_sampling'?: (_envoy_type_v3_FractionalPercent | null); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -31,7 +31,7 @@ export interface Tracing { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_v3_FractionalPercent); + 'overall_sampling'?: (_envoy_type_v3_FractionalPercent | null); /** * A list of custom tags with unique tag name to create tags for the active span. * It will take effect after merging with the :ref:`corresponding configuration @@ -52,7 +52,7 @@ export interface Tracing__Output { * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_v3_FractionalPercent__Output); + 'client_sampling': (_envoy_type_v3_FractionalPercent__Output | null); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -60,7 +60,7 @@ export interface Tracing__Output { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_v3_FractionalPercent__Output); + 'random_sampling': (_envoy_type_v3_FractionalPercent__Output | null); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -71,7 +71,7 @@ export interface Tracing__Output { * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_v3_FractionalPercent__Output); + 'overall_sampling': (_envoy_type_v3_FractionalPercent__Output | null); /** * A list of custom tags with unique tag name to create tags for the active span. * It will take effect after merging with the :ref:`corresponding configuration diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts index 2868685b2..b8a37be65 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Vhds.ts @@ -6,12 +6,12 @@ export interface Vhds { /** * Configuration source specifier for VHDS. */ - 'config_source'?: (_envoy_config_core_v3_ConfigSource); + 'config_source'?: (_envoy_config_core_v3_ConfigSource | null); } export interface Vhds__Output { /** * Configuration source specifier for VHDS. */ - 'config_source'?: (_envoy_config_core_v3_ConfigSource__Output); + 'config_source': (_envoy_config_core_v3_ConfigSource__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts index 86088ded6..1d3fb2309 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts @@ -96,7 +96,7 @@ export interface VirtualHost { /** * Indicates that the virtual host has a CORS policy. */ - 'cors'?: (_envoy_config_route_v3_CorsPolicy); + 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); /** * Specifies a list of HTTP headers that should be added to each response * handled by this virtual host. Headers specified at this level are applied @@ -145,19 +145,19 @@ export interface VirtualHost { * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy); + 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy | null); /** * Indicates the hedge policy for all routes in this virtual host. Note that setting a * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy); + 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy | null); /** * The maximum bytes which will be buffered for retries and shadowing. * If set and a route-specific limit is not set, the bytes actually buffered will be the minimum * value of this and the listener per_connection_buffer_limit_bytes. */ - 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value); + 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); /** * Decides whether the :ref:`x-envoy-attempt-count * ` header should be included @@ -176,7 +176,7 @@ export interface VirtualHost { * inherited). :ref:`Retry policy ` should not be * set if this field is used. */ - 'retry_policy_typed_config'?: (_google_protobuf_Any); + 'retry_policy_typed_config'?: (_google_protobuf_Any | null); } /** @@ -246,7 +246,7 @@ export interface VirtualHost__Output { /** * Indicates that the virtual host has a CORS policy. */ - 'cors'?: (_envoy_config_route_v3_CorsPolicy__Output); + 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); /** * Specifies a list of HTTP headers that should be added to each response * handled by this virtual host. Headers specified at this level are applied @@ -289,25 +289,25 @@ export interface VirtualHost__Output { * :ref:`FilterConfig` * message to specify additional options.] */ - 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any__Output}); + 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); /** * Indicates the retry policy for all routes in this virtual host. Note that setting a * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'retry_policy'?: (_envoy_config_route_v3_RetryPolicy__Output); + 'retry_policy': (_envoy_config_route_v3_RetryPolicy__Output | null); /** * Indicates the hedge policy for all routes in this virtual host. Note that setting a * route level entry will take precedence over this config and it'll be treated * independently (e.g.: values are not inherited). */ - 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy__Output); + 'hedge_policy': (_envoy_config_route_v3_HedgePolicy__Output | null); /** * The maximum bytes which will be buffered for retries and shadowing. * If set and a route-specific limit is not set, the bytes actually buffered will be the minimum * value of this and the listener per_connection_buffer_limit_bytes. */ - 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value__Output); + 'per_request_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); /** * Decides whether the :ref:`x-envoy-attempt-count * ` header should be included @@ -326,5 +326,5 @@ export interface VirtualHost__Output { * inherited). :ref:`Retry policy ` should not be * set if this field is used. */ - 'retry_policy_typed_config'?: (_google_protobuf_Any__Output); + 'retry_policy_typed_config': (_google_protobuf_Any__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts index 7974508ea..02edc0243 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts @@ -20,7 +20,7 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { * the choice of an upstream cluster is determined by its weight. The sum of weights across all * entries in the clusters array must add up to the total_weight, which defaults to 100. */ - 'weight'?: (_google_protobuf_UInt32Value); + 'weight'?: (_google_protobuf_UInt32Value | null); /** * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in * the upstream cluster with metadata matching what is set in this field will be considered for @@ -28,7 +28,7 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { * :ref:`RouteAction.metadata_match `, with * values here taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_config_core_v3_Metadata); + 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); /** * Specifies a list of headers to be added to requests when this cluster is selected * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. @@ -87,7 +87,7 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { * the choice of an upstream cluster is determined by its weight. The sum of weights across all * entries in the clusters array must add up to the total_weight, which defaults to 100. */ - 'weight'?: (_google_protobuf_UInt32Value__Output); + 'weight': (_google_protobuf_UInt32Value__Output | null); /** * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in * the upstream cluster with metadata matching what is set in this field will be considered for @@ -95,7 +95,7 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { * :ref:`RouteAction.metadata_match `, with * values here taking precedence. The filter name should be specified as *envoy.lb*. */ - 'metadata_match'?: (_envoy_config_core_v3_Metadata__Output); + 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); /** * Specifies a list of headers to be added to requests when this cluster is selected * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. @@ -136,7 +136,7 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { * :ref:`FilterConfig` * message to specify additional options.] */ - 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any__Output}); + 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); } /** @@ -167,7 +167,7 @@ export interface WeightedCluster { * Specifies the total weight across all clusters. The sum of all cluster weights must equal this * value, which must be greater than 0. Defaults to 100. */ - 'total_weight'?: (_google_protobuf_UInt32Value); + 'total_weight'?: (_google_protobuf_UInt32Value | null); } /** @@ -198,5 +198,5 @@ export interface WeightedCluster__Output { * Specifies the total weight across all clusters. The sum of all cluster weights must equal this * value, which must be greater than 0. Defaults to 100. */ - 'total_weight'?: (_google_protobuf_UInt32Value__Output); + 'total_weight': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts index 36dfade51..95b161939 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts @@ -23,7 +23,7 @@ export interface _envoy_config_trace_v3_Tracing_Http { * - *envoy.tracers.xray* */ 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * Trace driver specific configuration which depends on the driver being instantiated. * See the trace drivers for examples: @@ -59,7 +59,7 @@ export interface _envoy_config_trace_v3_Tracing_Http__Output { * - *envoy.tracers.xray* */ 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Trace driver specific configuration which depends on the driver being instantiated. * See the trace drivers for examples: @@ -89,7 +89,7 @@ export interface Tracing { /** * Provides configuration for the HTTP tracer. */ - 'http'?: (_envoy_config_trace_v3_Tracing_Http); + 'http'?: (_envoy_config_trace_v3_Tracing_Http | null); } /** @@ -107,5 +107,5 @@ export interface Tracing__Output { /** * Provides configuration for the HTTP tracer. */ - 'http'?: (_envoy_config_trace_v3_Tracing_Http__Output); + 'http': (_envoy_config_trace_v3_Tracing_Http__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts index b68e812ef..4a8912599 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts @@ -132,7 +132,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht /** * Whether to forward the subject of the client cert. Defaults to false. */ - 'subject'?: (_google_protobuf_BoolValue); + 'subject'?: (_google_protobuf_BoolValue | null); /** * Whether to forward the entire client cert in URL encoded PEM format. This will appear in the * XFCC header comma separated from other values with the value Cert="PEM". @@ -165,7 +165,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht /** * Whether to forward the subject of the client cert. Defaults to false. */ - 'subject'?: (_google_protobuf_BoolValue__Output); + 'subject': (_google_protobuf_BoolValue__Output | null); /** * Whether to forward the entire client cert in URL encoded PEM format. This will appear in the * XFCC header comma separated from other values with the value Cert="PEM". @@ -203,7 +203,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_v3_Percent); + 'client_sampling'?: (_envoy_type_v3_Percent | null); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -211,7 +211,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_v3_Percent); + 'random_sampling'?: (_envoy_type_v3_Percent | null); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -222,7 +222,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_v3_Percent); + 'overall_sampling'?: (_envoy_type_v3_Percent | null); /** * Whether to annotate spans with additional data. If true, spans will include logs for stream * events. @@ -233,7 +233,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * truncate lengthy request paths to meet the needs of a tracing backend. * Default: 256 */ - 'max_path_tag_length'?: (_google_protobuf_UInt32Value); + 'max_path_tag_length'?: (_google_protobuf_UInt32Value | null); /** * A list of custom tags with unique tag name to create tags for the active span. */ @@ -250,7 +250,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * Such a constraint is inherent to OpenCensus itself. It cannot be overcome without changes * on OpenCensus side. */ - 'provider'?: (_envoy_config_trace_v3_Tracing_Http); + 'provider'?: (_envoy_config_trace_v3_Tracing_Http | null); } /** @@ -265,7 +265,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * `. * Default: 100% */ - 'client_sampling'?: (_envoy_type_v3_Percent__Output); + 'client_sampling': (_envoy_type_v3_Percent__Output | null); /** * Target percentage of requests managed by this HTTP connection manager that will be randomly * selected for trace generation, if not requested by the client or not forced. This field is @@ -273,7 +273,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'random_sampling'?: (_envoy_type_v3_Percent__Output); + 'random_sampling': (_envoy_type_v3_Percent__Output | null); /** * Target percentage of requests managed by this HTTP connection manager that will be traced * after all other sampling checks have been applied (client-directed, force tracing, random @@ -284,7 +284,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * :ref:`HTTP Connection Manager `. * Default: 100% */ - 'overall_sampling'?: (_envoy_type_v3_Percent__Output); + 'overall_sampling': (_envoy_type_v3_Percent__Output | null); /** * Whether to annotate spans with additional data. If true, spans will include logs for stream * events. @@ -295,7 +295,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * truncate lengthy request paths to meet the needs of a tracing backend. * Default: 256 */ - 'max_path_tag_length'?: (_google_protobuf_UInt32Value__Output); + 'max_path_tag_length': (_google_protobuf_UInt32Value__Output | null); /** * A list of custom tags with unique tag name to create tags for the active span. */ @@ -312,7 +312,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * Such a constraint is inherent to OpenCensus itself. It cannot be overcome without changes * on OpenCensus side. */ - 'provider'?: (_envoy_config_trace_v3_Tracing_Http__Output); + 'provider': (_envoy_config_trace_v3_Tracing_Http__Output | null); } /** @@ -349,7 +349,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * ` as documented in the * :ref:`upgrade documentation `. */ - 'enabled'?: (_google_protobuf_BoolValue); + 'enabled'?: (_google_protobuf_BoolValue | null); } /** @@ -386,7 +386,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * ` as documented in the * :ref:`upgrade documentation `. */ - 'enabled'?: (_google_protobuf_BoolValue__Output); + 'enabled': (_google_protobuf_BoolValue__Output | null); } /** @@ -406,11 +406,11 @@ export interface HttpConnectionManager { /** * The connection manager’s route table will be dynamically loaded via the RDS API. */ - 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds); + 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds | null); /** * The route table for the connection manager is static and is specified in this property. */ - 'route_config'?: (_envoy_config_route_v3_RouteConfiguration); + 'route_config'?: (_envoy_config_route_v3_RouteConfiguration | null); /** * A list of individual HTTP filters that make up the filter chain for * requests made to the connection manager. :ref:`Order matters ` @@ -422,21 +422,21 @@ export interface HttpConnectionManager { * and :ref:`config_http_conn_man_headers_downstream-service-cluster` headers. See the linked * documentation for more information. Defaults to false. */ - 'add_user_agent'?: (_google_protobuf_BoolValue); + 'add_user_agent'?: (_google_protobuf_BoolValue | null); /** * Presence of the object defines whether the connection manager * emits :ref:`tracing ` data to the :ref:`configured tracing provider * `. */ - 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing); + 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing | null); /** * Additional HTTP/1 settings that are passed to the HTTP/1 codec. */ - 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions); + 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions | null); /** * Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. */ - 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions); + 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); /** * An optional override that the connection manager will write to the server * header in responses. If not set, the default is *envoy*. @@ -453,7 +453,7 @@ export interface HttpConnectionManager { * draining. The default grace period is 5000 milliseconds (5 seconds) if this * option is not specified. */ - 'drain_timeout'?: (_google_protobuf_Duration); + 'drain_timeout'?: (_google_protobuf_Duration | null); /** * Configuration for :ref:`HTTP access logs ` * emitted by the connection manager. @@ -468,14 +468,14 @@ export interface HttpConnectionManager { * :ref:`config_http_conn_man_headers_x-envoy-internal`, and * :ref:`config_http_conn_man_headers_x-envoy-external-address` for more information. */ - 'use_remote_address'?: (_google_protobuf_BoolValue); + 'use_remote_address'?: (_google_protobuf_BoolValue | null); /** * Whether the connection manager will generate the :ref:`x-request-id * ` header if it does not exist. This defaults to * true. Generating a random UUID4 is expensive so in high throughput scenarios where this feature * is not desired it can be disabled. */ - 'generate_request_id'?: (_google_protobuf_BoolValue); + 'generate_request_id'?: (_google_protobuf_BoolValue | null); /** * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. @@ -490,7 +490,7 @@ export interface HttpConnectionManager { * *By* is always set when the client certificate presents the URI type Subject Alternative Name * value. */ - 'set_current_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails); + 'set_current_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails | null); /** * If proxy_100_continue is true, Envoy will proxy incoming "Expect: * 100-continue" headers upstream, and forward "100 Continue" responses @@ -580,14 +580,14 @@ export interface HttpConnectionManager { * A value of 0 will completely disable the connection manager stream idle * timeout, although per-route idle timeout overrides will continue to apply. */ - 'stream_idle_timeout'?: (_google_protobuf_Duration); + 'stream_idle_timeout'?: (_google_protobuf_Duration | null); /** * Configures what network addresses are considered internal for stats and header sanitation * purposes. If unspecified, only RFC1918 IP addresses will be considered internal. * See the documentation for :ref:`config_http_conn_man_headers_x-envoy-internal` for more * information about internal/external addresses. */ - 'internal_address_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig); + 'internal_address_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig | null); /** * The delayed close timeout is for downstream connections managed by the HTTP connection manager. * It is defined as a grace period after connection close processing has been locally initiated @@ -620,14 +620,14 @@ export interface HttpConnectionManager { * connection's socket will be closed immediately after the write flush is completed or will * never close if the write flush does not complete. */ - 'delayed_close_timeout'?: (_google_protobuf_Duration); + 'delayed_close_timeout'?: (_google_protobuf_Duration | null); /** * The amount of time that Envoy will wait for the entire request to be received. * The timer is activated when the request is initiated, and is disarmed when the last byte of the * request is sent upstream (i.e. all decoding filters have processed the request), OR when the * response is initiated. If not specified or set to 0, this timeout is disabled. */ - 'request_timeout'?: (_google_protobuf_Duration); + 'request_timeout'?: (_google_protobuf_Duration | null); /** * The maximum request headers size for incoming connections. * If unconfigured, the default max request headers allowed is 60 KiB. @@ -635,7 +635,7 @@ export interface HttpConnectionManager { * The max configurable limit is 96 KiB, based on current implementation * constraints. */ - 'max_request_headers_kb'?: (_google_protobuf_UInt32Value); + 'max_request_headers_kb'?: (_google_protobuf_UInt32Value | null); /** * Should paths be normalized according to RFC 3986 before any processing of * requests by HTTP filters or routing? This affects the upstream *:path* header @@ -649,13 +649,13 @@ export interface HttpConnectionManager { * Note that Envoy does not perform * `case normalization `_ */ - 'normalize_path'?: (_google_protobuf_BoolValue); + 'normalize_path'?: (_google_protobuf_BoolValue | null); /** * A route table will be dynamically assigned to each request based on request attributes * (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are * specified in this message. */ - 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes); + 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes | null); /** * Whether the connection manager will keep the :ref:`x-request-id * ` header if passed for a request that is edge @@ -681,7 +681,7 @@ export interface HttpConnectionManager { * Additional settings for HTTP requests handled by the connection manager. These will be * applicable to both HTTP1 and HTTP2 requests. */ - 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions); + 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions | null); /** * The configuration of the request ID extension. This includes operations such as * generation, validation, and associated tracing operations. @@ -694,7 +694,7 @@ export interface HttpConnectionManager { * * 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. */ - 'request_id_extension'?: (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension); + 'request_id_extension'?: (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension | null); /** * If set, Envoy will always set :ref:`x-request-id ` header in response. * If this is false or not set, the request ID is returned in responses only if tracing is forced using @@ -706,7 +706,7 @@ export interface HttpConnectionManager { * body text and response content type. If not specified, status code and text body are hard * coded in Envoy, the response content type is plain text. */ - 'local_reply_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig); + 'local_reply_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig | null); /** * Determines if the port part should be removed from host/authority header before any processing * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` @@ -735,13 +735,13 @@ export interface HttpConnectionManager { * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging * ` */ - 'stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue); + 'stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); /** * The amount of time that Envoy will wait for the request headers to be received. The timer is * activated when the first byte of the headers is received, and is disarmed when the last byte of * the headers has been received. If not specified or set to 0, this timeout is disabled. */ - 'request_headers_timeout'?: (_google_protobuf_Duration); + 'request_headers_timeout'?: (_google_protobuf_Duration | null); /** * Determines if the port part should be removed from host/authority header before any processing * of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. @@ -773,11 +773,11 @@ export interface HttpConnectionManager__Output { /** * The connection manager’s route table will be dynamically loaded via the RDS API. */ - 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds__Output); + 'rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_Rds__Output | null); /** * The route table for the connection manager is static and is specified in this property. */ - 'route_config'?: (_envoy_config_route_v3_RouteConfiguration__Output); + 'route_config'?: (_envoy_config_route_v3_RouteConfiguration__Output | null); /** * A list of individual HTTP filters that make up the filter chain for * requests made to the connection manager. :ref:`Order matters ` @@ -789,21 +789,21 @@ export interface HttpConnectionManager__Output { * and :ref:`config_http_conn_man_headers_downstream-service-cluster` headers. See the linked * documentation for more information. Defaults to false. */ - 'add_user_agent'?: (_google_protobuf_BoolValue__Output); + 'add_user_agent': (_google_protobuf_BoolValue__Output | null); /** * Presence of the object defines whether the connection manager * emits :ref:`tracing ` data to the :ref:`configured tracing provider * `. */ - 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output); + 'tracing': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output | null); /** * Additional HTTP/1 settings that are passed to the HTTP/1 codec. */ - 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions__Output); + 'http_protocol_options': (_envoy_config_core_v3_Http1ProtocolOptions__Output | null); /** * Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. */ - 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions__Output); + 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); /** * An optional override that the connection manager will write to the server * header in responses. If not set, the default is *envoy*. @@ -820,7 +820,7 @@ export interface HttpConnectionManager__Output { * draining. The default grace period is 5000 milliseconds (5 seconds) if this * option is not specified. */ - 'drain_timeout'?: (_google_protobuf_Duration__Output); + 'drain_timeout': (_google_protobuf_Duration__Output | null); /** * Configuration for :ref:`HTTP access logs ` * emitted by the connection manager. @@ -835,14 +835,14 @@ export interface HttpConnectionManager__Output { * :ref:`config_http_conn_man_headers_x-envoy-internal`, and * :ref:`config_http_conn_man_headers_x-envoy-external-address` for more information. */ - 'use_remote_address'?: (_google_protobuf_BoolValue__Output); + 'use_remote_address': (_google_protobuf_BoolValue__Output | null); /** * Whether the connection manager will generate the :ref:`x-request-id * ` header if it does not exist. This defaults to * true. Generating a random UUID4 is expensive so in high throughput scenarios where this feature * is not desired it can be disabled. */ - 'generate_request_id'?: (_google_protobuf_BoolValue__Output); + 'generate_request_id': (_google_protobuf_BoolValue__Output | null); /** * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. @@ -857,7 +857,7 @@ export interface HttpConnectionManager__Output { * *By* is always set when the client certificate presents the URI type Subject Alternative Name * value. */ - 'set_current_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails__Output); + 'set_current_client_cert_details': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails__Output | null); /** * If proxy_100_continue is true, Envoy will proxy incoming "Expect: * 100-continue" headers upstream, and forward "100 Continue" responses @@ -947,14 +947,14 @@ export interface HttpConnectionManager__Output { * A value of 0 will completely disable the connection manager stream idle * timeout, although per-route idle timeout overrides will continue to apply. */ - 'stream_idle_timeout'?: (_google_protobuf_Duration__Output); + 'stream_idle_timeout': (_google_protobuf_Duration__Output | null); /** * Configures what network addresses are considered internal for stats and header sanitation * purposes. If unspecified, only RFC1918 IP addresses will be considered internal. * See the documentation for :ref:`config_http_conn_man_headers_x-envoy-internal` for more * information about internal/external addresses. */ - 'internal_address_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig__Output); + 'internal_address_config': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig__Output | null); /** * The delayed close timeout is for downstream connections managed by the HTTP connection manager. * It is defined as a grace period after connection close processing has been locally initiated @@ -987,14 +987,14 @@ export interface HttpConnectionManager__Output { * connection's socket will be closed immediately after the write flush is completed or will * never close if the write flush does not complete. */ - 'delayed_close_timeout'?: (_google_protobuf_Duration__Output); + 'delayed_close_timeout': (_google_protobuf_Duration__Output | null); /** * The amount of time that Envoy will wait for the entire request to be received. * The timer is activated when the request is initiated, and is disarmed when the last byte of the * request is sent upstream (i.e. all decoding filters have processed the request), OR when the * response is initiated. If not specified or set to 0, this timeout is disabled. */ - 'request_timeout'?: (_google_protobuf_Duration__Output); + 'request_timeout': (_google_protobuf_Duration__Output | null); /** * The maximum request headers size for incoming connections. * If unconfigured, the default max request headers allowed is 60 KiB. @@ -1002,7 +1002,7 @@ export interface HttpConnectionManager__Output { * The max configurable limit is 96 KiB, based on current implementation * constraints. */ - 'max_request_headers_kb'?: (_google_protobuf_UInt32Value__Output); + 'max_request_headers_kb': (_google_protobuf_UInt32Value__Output | null); /** * Should paths be normalized according to RFC 3986 before any processing of * requests by HTTP filters or routing? This affects the upstream *:path* header @@ -1016,13 +1016,13 @@ export interface HttpConnectionManager__Output { * Note that Envoy does not perform * `case normalization `_ */ - 'normalize_path'?: (_google_protobuf_BoolValue__Output); + 'normalize_path': (_google_protobuf_BoolValue__Output | null); /** * A route table will be dynamically assigned to each request based on request attributes * (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are * specified in this message. */ - 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes__Output); + 'scoped_routes'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes__Output | null); /** * Whether the connection manager will keep the :ref:`x-request-id * ` header if passed for a request that is edge @@ -1048,7 +1048,7 @@ export interface HttpConnectionManager__Output { * Additional settings for HTTP requests handled by the connection manager. These will be * applicable to both HTTP1 and HTTP2 requests. */ - 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions__Output); + 'common_http_protocol_options': (_envoy_config_core_v3_HttpProtocolOptions__Output | null); /** * The configuration of the request ID extension. This includes operations such as * generation, validation, and associated tracing operations. @@ -1061,7 +1061,7 @@ export interface HttpConnectionManager__Output { * * 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. */ - 'request_id_extension'?: (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output); + 'request_id_extension': (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output | null); /** * If set, Envoy will always set :ref:`x-request-id ` header in response. * If this is false or not set, the request ID is returned in responses only if tracing is forced using @@ -1073,7 +1073,7 @@ export interface HttpConnectionManager__Output { * body text and response content type. If not specified, status code and text body are hard * coded in Envoy, the response content type is plain text. */ - 'local_reply_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output); + 'local_reply_config': (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output | null); /** * Determines if the port part should be removed from host/authority header before any processing * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` @@ -1102,13 +1102,13 @@ export interface HttpConnectionManager__Output { * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging * ` */ - 'stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue__Output); + 'stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); /** * The amount of time that Envoy will wait for the request headers to be received. The timer is * activated when the first byte of the headers is received, and is disarmed when the last byte of * the headers has been received. If not specified or set to 0, this timeout is disabled. */ - 'request_headers_timeout'?: (_google_protobuf_Duration__Output); + 'request_headers_timeout': (_google_protobuf_Duration__Output | null); /** * Determines if the port part should be removed from host/authority header before any processing * of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts index 881e2c714..42e4215d0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts @@ -17,13 +17,13 @@ export interface HttpFilter { * Filter specific configuration which depends on the filter being instantiated. See the supported * filters for further documentation. */ - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); /** * Configuration source specifier for an extension configuration discovery service. * In case of a failure and without the default configuration, the HTTP listener responds with code 500. * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). */ - 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource); + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource | null); /** * If true, clients that do not support this filter may ignore the * filter but otherwise accept the config. @@ -48,13 +48,13 @@ export interface HttpFilter__Output { * Filter specific configuration which depends on the filter being instantiated. See the supported * filters for further documentation. */ - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Configuration source specifier for an extension configuration discovery service. * In case of a failure and without the default configuration, the HTTP listener responds with code 500. * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). */ - 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output); + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output | null); /** * If true, clients that do not support this filter may ignore the * filter but otherwise accept the config. diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts index 10bb6e700..04de11fcb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig.ts @@ -51,7 +51,7 @@ export interface LocalReplyConfig { * "path": "/foo" * } */ - 'body_format'?: (_envoy_config_core_v3_SubstitutionFormatString); + 'body_format'?: (_envoy_config_core_v3_SubstitutionFormatString | null); } /** @@ -102,5 +102,5 @@ export interface LocalReplyConfig__Output { * "path": "/foo" * } */ - 'body_format'?: (_envoy_config_core_v3_SubstitutionFormatString__Output); + 'body_format': (_envoy_config_core_v3_SubstitutionFormatString__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts index 8e06a6d67..b99e2f1bd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/Rds.ts @@ -6,7 +6,7 @@ export interface Rds { /** * Configuration source specifier for RDS. */ - 'config_source'?: (_envoy_config_core_v3_ConfigSource); + 'config_source'?: (_envoy_config_core_v3_ConfigSource | null); /** * 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 @@ -20,7 +20,7 @@ export interface Rds__Output { /** * Configuration source specifier for RDS. */ - 'config_source'?: (_envoy_config_core_v3_ConfigSource__Output); + 'config_source': (_envoy_config_core_v3_ConfigSource__Output | null); /** * 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 diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts index 24e05ccc3..ba1789a80 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension.ts @@ -6,12 +6,12 @@ export interface RequestIDExtension { /** * Request ID extension specific configuration. */ - 'typed_config'?: (_google_protobuf_Any); + 'typed_config'?: (_google_protobuf_Any | null); } export interface RequestIDExtension__Output { /** * Request ID extension specific configuration. */ - 'typed_config'?: (_google_protobuf_Any__Output); + 'typed_config': (_google_protobuf_Any__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts index 67af3aec4..26d14a07d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts @@ -14,21 +14,21 @@ export interface ResponseMapper { /** * Filter to determine if this mapper should apply. */ - 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter); + 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter | null); /** * The new response status code if specified. */ - 'status_code'?: (_google_protobuf_UInt32Value); + 'status_code'?: (_google_protobuf_UInt32Value | null); /** * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` * command operator in the `body_format`. */ - 'body'?: (_envoy_config_core_v3_DataSource); + 'body'?: (_envoy_config_core_v3_DataSource | null); /** * A per mapper `body_format` to override the :ref:`body_format `. * It will be used when this mapper is matched. */ - 'body_format_override'?: (_envoy_config_core_v3_SubstitutionFormatString); + 'body_format_override'?: (_envoy_config_core_v3_SubstitutionFormatString | null); /** * HTTP headers to add to a local reply. This allows the response mapper to append, to add * or to override headers of any local reply before it is sent to a downstream client. @@ -44,21 +44,21 @@ export interface ResponseMapper__Output { /** * Filter to determine if this mapper should apply. */ - 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter__Output); + 'filter': (_envoy_config_accesslog_v3_AccessLogFilter__Output | null); /** * The new response status code if specified. */ - 'status_code'?: (_google_protobuf_UInt32Value__Output); + 'status_code': (_google_protobuf_UInt32Value__Output | null); /** * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` * command operator in the `body_format`. */ - 'body'?: (_envoy_config_core_v3_DataSource__Output); + 'body': (_envoy_config_core_v3_DataSource__Output | null); /** * A per mapper `body_format` to override the :ref:`body_format `. * It will be used when this mapper is matched. */ - 'body_format_override'?: (_envoy_config_core_v3_SubstitutionFormatString__Output); + 'body_format_override': (_envoy_config_core_v3_SubstitutionFormatString__Output | null); /** * HTTP headers to add to a local reply. This allows the response mapper to append, to add * or to override headers of any local reply before it is sent to a downstream client. diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts index a9995b455..e4565ce80 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts @@ -6,12 +6,12 @@ export interface ScopedRds { /** * Configuration source specifier for scoped RDS. */ - 'scoped_rds_config_source'?: (_envoy_config_core_v3_ConfigSource); + 'scoped_rds_config_source'?: (_envoy_config_core_v3_ConfigSource | null); } export interface ScopedRds__Output { /** * Configuration source specifier for scoped RDS. */ - 'scoped_rds_config_source'?: (_envoy_config_core_v3_ConfigSource__Output); + 'scoped_rds_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts index a19e59b2a..8aecd3883 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts @@ -11,7 +11,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Sc /** * Specifies how a header field's value should be extracted. */ - 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor); + 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor | null); 'type'?: "header_value_extractor"; } @@ -22,7 +22,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Sc /** * Specifies how a header field's value should be extracted. */ - 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output); + 'header_value_extractor'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor__Output | null); 'type': "header_value_extractor"; } @@ -70,7 +70,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Sc /** * Specifies the key value pair to extract the value from. */ - 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement); + 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement | null); 'extract_type'?: "index"|"element"; } @@ -118,7 +118,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Sc /** * Specifies the key value pair to extract the value from. */ - 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement__Output); + 'element'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder_HeaderValueExtractor_KvElement__Output | null); 'extract_type': "index"|"element"; } @@ -209,13 +209,13 @@ export interface ScopedRoutes { /** * The algorithm to use for constructing a scope key for each request. */ - 'scope_key_builder'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder); + 'scope_key_builder'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder | null); /** * Configuration source specifier for RDS. * This config source is used to subscribe to RouteConfiguration resources specified in * ScopedRouteConfiguration messages. */ - 'rds_config_source'?: (_envoy_config_core_v3_ConfigSource); + 'rds_config_source'?: (_envoy_config_core_v3_ConfigSource | null); /** * 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 @@ -223,7 +223,7 @@ export interface ScopedRoutes { * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList); + 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList | null); /** * 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 @@ -231,7 +231,7 @@ export interface ScopedRoutes { * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds); + 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds | null); 'config_specifier'?: "scoped_route_configurations_list"|"scoped_rds"; } @@ -246,13 +246,13 @@ export interface ScopedRoutes__Output { /** * The algorithm to use for constructing a scope key for each request. */ - 'scope_key_builder'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder__Output); + 'scope_key_builder': (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder__Output | null); /** * Configuration source specifier for RDS. * This config source is used to subscribe to RouteConfiguration resources specified in * ScopedRouteConfiguration messages. */ - 'rds_config_source'?: (_envoy_config_core_v3_ConfigSource__Output); + 'rds_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); /** * 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 @@ -260,7 +260,7 @@ export interface ScopedRoutes__Output { * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList__Output); + 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList__Output | null); /** * 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 @@ -268,6 +268,6 @@ export interface ScopedRoutes__Output { * :ref:`ScopeKeyBuilder` * in this message. */ - 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds__Output); + 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds__Output | null); 'config_specifier': "scoped_route_configurations_list"|"scoped_rds"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts index ec7641dca..6044118bc 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts @@ -1,6 +1,7 @@ // Original file: deps/envoy-api/envoy/service/discovery/v2/ads.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { DeltaDiscoveryRequest as _envoy_api_v2_DeltaDiscoveryRequest, DeltaDiscoveryRequest__Output as _envoy_api_v2_DeltaDiscoveryRequest__Output } from '../../../../envoy/api/v2/DeltaDiscoveryRequest'; import type { DeltaDiscoveryResponse as _envoy_api_v2_DeltaDiscoveryResponse, DeltaDiscoveryResponse__Output as _envoy_api_v2_DeltaDiscoveryResponse__Output } from '../../../../envoy/api/v2/DeltaDiscoveryResponse'; import type { DiscoveryRequest as _envoy_api_v2_DiscoveryRequest, DiscoveryRequest__Output as _envoy_api_v2_DiscoveryRequest__Output } from '../../../../envoy/api/v2/DiscoveryRequest'; @@ -50,3 +51,8 @@ export interface AggregatedDiscoveryServiceHandlers extends grpc.UntypedServiceI StreamAggregatedResources: grpc.handleBidiStreamingCall<_envoy_api_v2_DiscoveryRequest__Output, _envoy_api_v2_DiscoveryResponse>; } + +export interface AggregatedDiscoveryServiceDefinition extends grpc.ServiceDefinition { + DeltaAggregatedResources: MethodDefinition<_envoy_api_v2_DeltaDiscoveryRequest, _envoy_api_v2_DeltaDiscoveryResponse, _envoy_api_v2_DeltaDiscoveryRequest__Output, _envoy_api_v2_DeltaDiscoveryResponse__Output> + StreamAggregatedResources: MethodDefinition<_envoy_api_v2_DiscoveryRequest, _envoy_api_v2_DiscoveryResponse, _envoy_api_v2_DiscoveryRequest__Output, _envoy_api_v2_DiscoveryResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts index 445057d44..e6498177b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts @@ -1,6 +1,7 @@ // Original file: deps/envoy-api/envoy/service/discovery/v3/ads.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { DeltaDiscoveryRequest as _envoy_service_discovery_v3_DeltaDiscoveryRequest, DeltaDiscoveryRequest__Output as _envoy_service_discovery_v3_DeltaDiscoveryRequest__Output } from '../../../../envoy/service/discovery/v3/DeltaDiscoveryRequest'; import type { DeltaDiscoveryResponse as _envoy_service_discovery_v3_DeltaDiscoveryResponse, DeltaDiscoveryResponse__Output as _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output } from '../../../../envoy/service/discovery/v3/DeltaDiscoveryResponse'; import type { DiscoveryRequest as _envoy_service_discovery_v3_DiscoveryRequest, DiscoveryRequest__Output as _envoy_service_discovery_v3_DiscoveryRequest__Output } from '../../../../envoy/service/discovery/v3/DiscoveryRequest'; @@ -50,3 +51,8 @@ export interface AggregatedDiscoveryServiceHandlers extends grpc.UntypedServiceI StreamAggregatedResources: grpc.handleBidiStreamingCall<_envoy_service_discovery_v3_DiscoveryRequest__Output, _envoy_service_discovery_v3_DiscoveryResponse>; } + +export interface AggregatedDiscoveryServiceDefinition extends grpc.ServiceDefinition { + DeltaAggregatedResources: MethodDefinition<_envoy_service_discovery_v3_DeltaDiscoveryRequest, _envoy_service_discovery_v3_DeltaDiscoveryResponse, _envoy_service_discovery_v3_DeltaDiscoveryRequest__Output, _envoy_service_discovery_v3_DeltaDiscoveryResponse__Output> + StreamAggregatedResources: MethodDefinition<_envoy_service_discovery_v3_DiscoveryRequest, _envoy_service_discovery_v3_DiscoveryResponse, _envoy_service_discovery_v3_DiscoveryRequest__Output, _envoy_service_discovery_v3_DiscoveryResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts index bcf2de4e2..b42470516 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts @@ -42,7 +42,7 @@ export interface DeltaDiscoveryRequest { /** * The node making the request. */ - 'node'?: (_envoy_config_core_v3_Node); + 'node'?: (_envoy_config_core_v3_Node | null); /** * Type of the resource that is being requested, e.g. * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if @@ -101,7 +101,7 @@ export interface DeltaDiscoveryRequest { * failed to update configuration. The *message* field in *error_details* * provides the Envoy internal exception related to the failure. */ - 'error_detail'?: (_google_rpc_Status); + 'error_detail'?: (_google_rpc_Status | null); } /** @@ -143,7 +143,7 @@ export interface DeltaDiscoveryRequest__Output { /** * The node making the request. */ - 'node'?: (_envoy_config_core_v3_Node__Output); + 'node': (_envoy_config_core_v3_Node__Output | null); /** * Type of the resource that is being requested, e.g. * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if @@ -202,5 +202,5 @@ export interface DeltaDiscoveryRequest__Output { * failed to update configuration. The *message* field in *error_details* * provides the Envoy internal exception related to the failure. */ - 'error_detail'?: (_google_rpc_Status__Output); + 'error_detail': (_google_rpc_Status__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts index c22690cd9..efbbed85c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts @@ -35,7 +35,7 @@ export interface DeltaDiscoveryResponse { * [#not-implemented-hide:] * The control plane instance that sent the response. */ - 'control_plane'?: (_envoy_config_core_v3_ControlPlane); + 'control_plane'?: (_envoy_config_core_v3_ControlPlane | null); } /** @@ -70,5 +70,5 @@ export interface DeltaDiscoveryResponse__Output { * [#not-implemented-hide:] * The control plane instance that sent the response. */ - 'control_plane'?: (_envoy_config_core_v3_ControlPlane__Output); + 'control_plane': (_envoy_config_core_v3_ControlPlane__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts index 2b23ee869..b30930b6f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts @@ -22,7 +22,7 @@ export interface DiscoveryRequest { /** * The node making the request. */ - 'node'?: (_envoy_config_core_v3_Node); + 'node'?: (_envoy_config_core_v3_Node | null); /** * List of resources to subscribe to, e.g. list of cluster names or a route * configuration name. If this is empty, all resources for the API are @@ -53,7 +53,7 @@ export interface DiscoveryRequest { * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. */ - 'error_detail'?: (_google_rpc_Status); + 'error_detail'?: (_google_rpc_Status | null); } /** @@ -75,7 +75,7 @@ export interface DiscoveryRequest__Output { /** * The node making the request. */ - 'node'?: (_envoy_config_core_v3_Node__Output); + 'node': (_envoy_config_core_v3_Node__Output | null); /** * List of resources to subscribe to, e.g. list of cluster names or a route * configuration name. If this is empty, all resources for the API are @@ -106,5 +106,5 @@ export interface DiscoveryRequest__Output { * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. */ - 'error_detail'?: (_google_rpc_Status__Output); + 'error_detail': (_google_rpc_Status__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts index f69944b80..874168317 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryResponse.ts @@ -51,7 +51,7 @@ export interface DiscoveryResponse { /** * The control plane instance that sent the response. */ - 'control_plane'?: (_envoy_config_core_v3_ControlPlane); + 'control_plane'?: (_envoy_config_core_v3_ControlPlane | null); } /** @@ -102,5 +102,5 @@ export interface DiscoveryResponse__Output { /** * The control plane instance that sent the response. */ - 'control_plane'?: (_envoy_config_core_v3_ControlPlane__Output); + 'control_plane': (_envoy_config_core_v3_ControlPlane__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts index 27a70c97c..0e5897ab4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts @@ -41,7 +41,7 @@ export interface Resource { /** * The resource being tracked. */ - 'resource'?: (_google_protobuf_Any); + 'resource'?: (_google_protobuf_Any | null); /** * The resource's name, to distinguish it from others of the same type of resource. */ @@ -65,12 +65,12 @@ export interface Resource { * testing where the fault injection should be terminated in the event that Envoy loses contact * with the management server. */ - 'ttl'?: (_google_protobuf_Duration); + 'ttl'?: (_google_protobuf_Duration | null); /** * Cache control properties for the resource. * [#not-implemented-hide:] */ - 'cache_control'?: (_envoy_service_discovery_v3_Resource_CacheControl); + 'cache_control'?: (_envoy_service_discovery_v3_Resource_CacheControl | null); } /** @@ -85,7 +85,7 @@ export interface Resource__Output { /** * The resource being tracked. */ - 'resource'?: (_google_protobuf_Any__Output); + 'resource': (_google_protobuf_Any__Output | null); /** * The resource's name, to distinguish it from others of the same type of resource. */ @@ -109,10 +109,10 @@ export interface Resource__Output { * testing where the fault injection should be terminated in the event that Envoy loses contact * with the management server. */ - 'ttl'?: (_google_protobuf_Duration__Output); + 'ttl': (_google_protobuf_Duration__Output | null); /** * Cache control properties for the resource. * [#not-implemented-hide:] */ - 'cache_control'?: (_envoy_service_discovery_v3_Resource_CacheControl__Output); + 'cache_control': (_envoy_service_discovery_v3_Resource_CacheControl__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts index 41e73f0e9..2a95752de 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts @@ -1,6 +1,7 @@ // Original file: deps/envoy-api/envoy/service/load_stats/v2/lrs.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { LoadStatsRequest as _envoy_service_load_stats_v2_LoadStatsRequest, LoadStatsRequest__Output as _envoy_service_load_stats_v2_LoadStatsRequest__Output } from '../../../../envoy/service/load_stats/v2/LoadStatsRequest'; import type { LoadStatsResponse as _envoy_service_load_stats_v2_LoadStatsResponse, LoadStatsResponse__Output as _envoy_service_load_stats_v2_LoadStatsResponse__Output } from '../../../../envoy/service/load_stats/v2/LoadStatsResponse'; @@ -106,3 +107,7 @@ export interface LoadReportingServiceHandlers extends grpc.UntypedServiceImpleme StreamLoadStats: grpc.handleBidiStreamingCall<_envoy_service_load_stats_v2_LoadStatsRequest__Output, _envoy_service_load_stats_v2_LoadStatsResponse>; } + +export interface LoadReportingServiceDefinition extends grpc.ServiceDefinition { + StreamLoadStats: MethodDefinition<_envoy_service_load_stats_v2_LoadStatsRequest, _envoy_service_load_stats_v2_LoadStatsResponse, _envoy_service_load_stats_v2_LoadStatsRequest__Output, _envoy_service_load_stats_v2_LoadStatsResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts index a7ac6b8e1..3cd5ebc2f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts @@ -11,7 +11,7 @@ export interface LoadStatsRequest { /** * Node identifier for Envoy instance. */ - 'node'?: (_envoy_api_v2_core_Node); + 'node'?: (_envoy_api_v2_core_Node | null); /** * A list of load stats to report. */ @@ -26,7 +26,7 @@ export interface LoadStatsRequest__Output { /** * Node identifier for Envoy instance. */ - 'node'?: (_envoy_api_v2_core_Node__Output); + 'node': (_envoy_api_v2_core_Node__Output | null); /** * A list of load stats to report. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts index afec8f180..1065fe22f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts @@ -22,7 +22,7 @@ export interface LoadStatsResponse { * of inobservability that might otherwise exists between the messages. New clusters are not * subject to this consideration. */ - 'load_reporting_interval'?: (_google_protobuf_Duration); + 'load_reporting_interval'?: (_google_protobuf_Duration | null); /** * Set to *true* if the management server supports endpoint granularity * report. @@ -56,7 +56,7 @@ export interface LoadStatsResponse__Output { * of inobservability that might otherwise exists between the messages. New clusters are not * subject to this consideration. */ - 'load_reporting_interval'?: (_google_protobuf_Duration__Output); + 'load_reporting_interval': (_google_protobuf_Duration__Output | null); /** * Set to *true* if the management server supports endpoint granularity * report. diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts index 24a9de55e..32aa4f96e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadReportingService.ts @@ -1,6 +1,7 @@ // Original file: deps/envoy-api/envoy/service/load_stats/v3/lrs.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { LoadStatsRequest as _envoy_service_load_stats_v3_LoadStatsRequest, LoadStatsRequest__Output as _envoy_service_load_stats_v3_LoadStatsRequest__Output } from '../../../../envoy/service/load_stats/v3/LoadStatsRequest'; import type { LoadStatsResponse as _envoy_service_load_stats_v3_LoadStatsResponse, LoadStatsResponse__Output as _envoy_service_load_stats_v3_LoadStatsResponse__Output } from '../../../../envoy/service/load_stats/v3/LoadStatsResponse'; @@ -106,3 +107,7 @@ export interface LoadReportingServiceHandlers extends grpc.UntypedServiceImpleme StreamLoadStats: grpc.handleBidiStreamingCall<_envoy_service_load_stats_v3_LoadStatsRequest__Output, _envoy_service_load_stats_v3_LoadStatsResponse>; } + +export interface LoadReportingServiceDefinition extends grpc.ServiceDefinition { + StreamLoadStats: MethodDefinition<_envoy_service_load_stats_v3_LoadStatsRequest, _envoy_service_load_stats_v3_LoadStatsResponse, _envoy_service_load_stats_v3_LoadStatsRequest__Output, _envoy_service_load_stats_v3_LoadStatsResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts index 6f0e19525..e430eb270 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsRequest.ts @@ -10,7 +10,7 @@ export interface LoadStatsRequest { /** * Node identifier for Envoy instance. */ - 'node'?: (_envoy_config_core_v3_Node); + 'node'?: (_envoy_config_core_v3_Node | null); /** * A list of load stats to report. */ @@ -24,7 +24,7 @@ export interface LoadStatsRequest__Output { /** * Node identifier for Envoy instance. */ - 'node'?: (_envoy_config_core_v3_Node__Output); + 'node': (_envoy_config_core_v3_Node__Output | null); /** * A list of load stats to report. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts index 51f017474..c39658cde 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts @@ -22,7 +22,7 @@ export interface LoadStatsResponse { * of inobservability that might otherwise exists between the messages. New clusters are not * subject to this consideration. */ - 'load_reporting_interval'?: (_google_protobuf_Duration); + 'load_reporting_interval'?: (_google_protobuf_Duration | null); /** * Set to *true* if the management server supports endpoint granularity * report. @@ -56,7 +56,7 @@ export interface LoadStatsResponse__Output { * of inobservability that might otherwise exists between the messages. New clusters are not * subject to this consideration. */ - 'load_reporting_interval'?: (_google_protobuf_Duration__Output); + 'load_reporting_interval': (_google_protobuf_Duration__Output | null); /** * Set to *true* if the management server supports endpoint granularity * report. diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts index e91a4898e..0bf3bca79 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/DoubleMatcher.ts @@ -10,7 +10,7 @@ export interface DoubleMatcher { * If specified, the input double value must be in the range specified here. * Note: The range is using half-open interval semantics [start, end). */ - 'range'?: (_envoy_type_v3_DoubleRange); + 'range'?: (_envoy_type_v3_DoubleRange | null); /** * If specified, the input double value must be equal to the value specified here. */ @@ -26,7 +26,7 @@ export interface DoubleMatcher__Output { * If specified, the input double value must be in the range specified here. * Note: The range is using half-open interval semantics [start, end). */ - 'range'?: (_envoy_type_v3_DoubleRange__Output); + 'range'?: (_envoy_type_v3_DoubleRange__Output | null); /** * If specified, the input double value must be equal to the value specified here. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts index c32b72a39..10bf5567c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ListMatcher.ts @@ -9,7 +9,7 @@ export interface ListMatcher { /** * If specified, at least one of the values in the list must match the value specified. */ - 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher); + 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher | null); 'match_pattern'?: "one_of"; } @@ -20,6 +20,6 @@ export interface ListMatcher__Output { /** * If specified, at least one of the values in the list must match the value specified. */ - 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher__Output); + 'one_of'?: (_envoy_type_matcher_v3_ValueMatcher__Output | null); 'match_pattern': "one_of"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts index 8d80408a3..58117faab 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts @@ -43,7 +43,7 @@ export interface MetadataMatcher { /** * The MetadataMatcher is matched if the value retrieved by path is matched to this value. */ - 'value'?: (_envoy_type_matcher_v3_ValueMatcher); + 'value'?: (_envoy_type_matcher_v3_ValueMatcher | null); } /** @@ -61,5 +61,5 @@ export interface MetadataMatcher__Output { /** * The MetadataMatcher is matched if the value retrieved by path is matched to this value. */ - 'value'?: (_envoy_type_matcher_v3_ValueMatcher__Output); + 'value': (_envoy_type_matcher_v3_ValueMatcher__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts index 4abddc655..3796765a7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatchAndSubstitute.ts @@ -18,7 +18,7 @@ export interface RegexMatchAndSubstitute { * used in the pattern to extract portions of the subject string, and then * referenced in the substitution string. */ - 'pattern'?: (_envoy_type_matcher_v3_RegexMatcher); + 'pattern'?: (_envoy_type_matcher_v3_RegexMatcher | null); /** * The string that should be substituted into matching portions of the * subject string during a substitution operation to produce a new string. @@ -49,7 +49,7 @@ export interface RegexMatchAndSubstitute__Output { * used in the pattern to extract portions of the subject string, and then * referenced in the substitution string. */ - 'pattern'?: (_envoy_type_matcher_v3_RegexMatcher__Output); + 'pattern': (_envoy_type_matcher_v3_RegexMatcher__Output | null); /** * The string that should be substituted into matching portions of the * subject string during a substitution operation to produce a new string. diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts index 0e8b37f08..1addb1730 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts @@ -27,7 +27,7 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2 { * This field is deprecated; regexp validation should be performed on the management server * instead of being done by each individual client. */ - 'max_program_size'?: (_google_protobuf_UInt32Value); + 'max_program_size'?: (_google_protobuf_UInt32Value | null); } /** @@ -55,7 +55,7 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output { * This field is deprecated; regexp validation should be performed on the management server * instead of being done by each individual client. */ - 'max_program_size'?: (_google_protobuf_UInt32Value__Output); + 'max_program_size': (_google_protobuf_UInt32Value__Output | null); } /** @@ -65,7 +65,7 @@ export interface RegexMatcher { /** * Google's RE2 regex engine. */ - 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2); + 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2 | null); /** * The regex match string. The string must be supported by the configured engine. */ @@ -80,7 +80,7 @@ export interface RegexMatcher__Output { /** * Google's RE2 regex engine. */ - 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output); + 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output | null); /** * The regex match string. The string must be supported by the configured engine. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts index 58616dde8..c89190510 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts @@ -36,7 +36,7 @@ export interface StringMatcher { /** * The input string must match the regular expression specified here. */ - 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher | null); /** * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no * effect for the safe_regex match. @@ -89,7 +89,7 @@ export interface StringMatcher__Output { /** * The input string must match the regular expression specified here. */ - 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output); + 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); /** * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no * effect for the safe_regex match. diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts index 6c271162a..d01060446 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/ValueMatcher.ts @@ -25,17 +25,17 @@ export interface ValueMatcher { /** * If specified, a match occurs if and only if the target value is a NullValue. */ - 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch); + 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch | null); /** * If specified, a match occurs if and only if the target value is a double value and is * matched to this field. */ - 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher); + 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher | null); /** * If specified, a match occurs if and only if the target value is a string value and is * matched to this field. */ - 'string_match'?: (_envoy_type_matcher_v3_StringMatcher); + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null); /** * If specified, a match occurs if and only if the target value is a bool value and is equal * to this field. @@ -51,7 +51,7 @@ export interface ValueMatcher { * If specified, a match occurs if and only if the target value is a list value and * is matched to this field. */ - 'list_match'?: (_envoy_type_matcher_v3_ListMatcher); + 'list_match'?: (_envoy_type_matcher_v3_ListMatcher | null); /** * Specifies how to match a value. */ @@ -67,17 +67,17 @@ export interface ValueMatcher__Output { /** * If specified, a match occurs if and only if the target value is a NullValue. */ - 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch__Output); + 'null_match'?: (_envoy_type_matcher_v3_ValueMatcher_NullMatch__Output | null); /** * If specified, a match occurs if and only if the target value is a double value and is * matched to this field. */ - 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher__Output); + 'double_match'?: (_envoy_type_matcher_v3_DoubleMatcher__Output | null); /** * If specified, a match occurs if and only if the target value is a string value and is * matched to this field. */ - 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output); + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null); /** * If specified, a match occurs if and only if the target value is a bool value and is equal * to this field. @@ -93,7 +93,7 @@ export interface ValueMatcher__Output { * If specified, a match occurs if and only if the target value is a list value and * is matched to this field. */ - 'list_match'?: (_envoy_type_matcher_v3_ListMatcher__Output); + 'list_match'?: (_envoy_type_matcher_v3_ListMatcher__Output | null); /** * Specifies how to match a value. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts index c231ecf98..2457b3f91 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts @@ -58,19 +58,19 @@ export interface MetadataKind { /** * Request kind of metadata. */ - 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request); + 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request | null); /** * Route kind of metadata. */ - 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route); + 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route | null); /** * Cluster kind of metadata. */ - 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster); + 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster | null); /** * Host kind of metadata. */ - 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host); + 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host | null); 'kind'?: "request"|"route"|"cluster"|"host"; } @@ -81,18 +81,18 @@ export interface MetadataKind__Output { /** * Request kind of metadata. */ - 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request__Output); + 'request'?: (_envoy_type_metadata_v3_MetadataKind_Request__Output | null); /** * Route kind of metadata. */ - 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route__Output); + 'route'?: (_envoy_type_metadata_v3_MetadataKind_Route__Output | null); /** * Cluster kind of metadata. */ - 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster__Output); + 'cluster'?: (_envoy_type_metadata_v3_MetadataKind_Cluster__Output | null); /** * Host kind of metadata. */ - 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host__Output); + 'host'?: (_envoy_type_metadata_v3_MetadataKind_Host__Output | null); 'kind': "request"|"route"|"cluster"|"host"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts index ef29c3420..d5fe26ed5 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts @@ -98,11 +98,11 @@ export interface _envoy_type_tracing_v3_CustomTag_Metadata { /** * Specify what kind of metadata to obtain tag value from. */ - 'kind'?: (_envoy_type_metadata_v3_MetadataKind); + 'kind'?: (_envoy_type_metadata_v3_MetadataKind | null); /** * Metadata key to define the path to retrieve the tag value. */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey); + 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); /** * When no valid metadata is found, * the tag value would be populated with this default value if specified, @@ -122,11 +122,11 @@ export interface _envoy_type_tracing_v3_CustomTag_Metadata__Output { /** * Specify what kind of metadata to obtain tag value from. */ - 'kind'?: (_envoy_type_metadata_v3_MetadataKind__Output); + 'kind': (_envoy_type_metadata_v3_MetadataKind__Output | null); /** * Metadata key to define the path to retrieve the tag value. */ - 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey__Output); + 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); /** * When no valid metadata is found, * the tag value would be populated with this default value if specified, @@ -147,19 +147,19 @@ export interface CustomTag { /** * A literal custom tag. */ - 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal); + 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal | null); /** * An environment custom tag. */ - 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment); + 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment | null); /** * A request header custom tag. */ - 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header); + 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header | null); /** * A custom tag to obtain tag value from the metadata. */ - 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata); + 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata | null); /** * Used to specify what kind of custom tag. */ @@ -178,19 +178,19 @@ export interface CustomTag__Output { /** * A literal custom tag. */ - 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal__Output); + 'literal'?: (_envoy_type_tracing_v3_CustomTag_Literal__Output | null); /** * An environment custom tag. */ - 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment__Output); + 'environment'?: (_envoy_type_tracing_v3_CustomTag_Environment__Output | null); /** * A request header custom tag. */ - 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header__Output); + 'request_header'?: (_envoy_type_tracing_v3_CustomTag_Header__Output | null); /** * A custom tag to obtain tag value from the metadata. */ - 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata__Output); + 'metadata'?: (_envoy_type_tracing_v3_CustomTag_Metadata__Output | null); /** * Used to specify what kind of custom tag. */ diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/DescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/DescriptorProto.ts index 2f6f9f0cc..f729437f4 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/DescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/DescriptorProto.ts @@ -33,7 +33,7 @@ export interface DescriptorProto { 'enumType'?: (_google_protobuf_EnumDescriptorProto)[]; 'extensionRange'?: (_google_protobuf_DescriptorProto_ExtensionRange)[]; 'extension'?: (_google_protobuf_FieldDescriptorProto)[]; - 'options'?: (_google_protobuf_MessageOptions); + 'options'?: (_google_protobuf_MessageOptions | null); 'oneofDecl'?: (_google_protobuf_OneofDescriptorProto)[]; 'reservedRange'?: (_google_protobuf_DescriptorProto_ReservedRange)[]; 'reservedName'?: (string)[]; @@ -46,7 +46,7 @@ export interface DescriptorProto__Output { 'enumType': (_google_protobuf_EnumDescriptorProto__Output)[]; 'extensionRange': (_google_protobuf_DescriptorProto_ExtensionRange__Output)[]; 'extension': (_google_protobuf_FieldDescriptorProto__Output)[]; - 'options'?: (_google_protobuf_MessageOptions__Output); + 'options': (_google_protobuf_MessageOptions__Output | null); 'oneofDecl': (_google_protobuf_OneofDescriptorProto__Output)[]; 'reservedRange': (_google_protobuf_DescriptorProto_ReservedRange__Output)[]; 'reservedName': (string)[]; diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/EnumDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumDescriptorProto.ts index 7aa40ce4d..dc4c9673e 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/EnumDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumDescriptorProto.ts @@ -6,11 +6,11 @@ import type { EnumOptions as _google_protobuf_EnumOptions, EnumOptions__Output a export interface EnumDescriptorProto { 'name'?: (string); 'value'?: (_google_protobuf_EnumValueDescriptorProto)[]; - 'options'?: (_google_protobuf_EnumOptions); + 'options'?: (_google_protobuf_EnumOptions | null); } export interface EnumDescriptorProto__Output { 'name': (string); 'value': (_google_protobuf_EnumValueDescriptorProto__Output)[]; - 'options'?: (_google_protobuf_EnumOptions__Output); + 'options': (_google_protobuf_EnumOptions__Output | null); } 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 b92ade4f9..777901a54 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumOptions.ts @@ -1,15 +1,18 @@ // 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/EnumValueDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueDescriptorProto.ts index 238e7fd01..7f8e57ea5 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueDescriptorProto.ts @@ -5,11 +5,11 @@ import type { EnumValueOptions as _google_protobuf_EnumValueOptions, EnumValueOp export interface EnumValueDescriptorProto { 'name'?: (string); 'number'?: (number); - 'options'?: (_google_protobuf_EnumValueOptions); + 'options'?: (_google_protobuf_EnumValueOptions | null); } export interface EnumValueDescriptorProto__Output { 'name': (string); 'number': (number); - 'options'?: (_google_protobuf_EnumValueOptions__Output); + 'options': (_google_protobuf_EnumValueOptions__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 e60ee6f4c..48fea77be 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts @@ -1,13 +1,18 @@ // 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); } 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); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts index b59518c4b..c511e2eff 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts @@ -41,7 +41,7 @@ export interface FieldDescriptorProto { 'type'?: (_google_protobuf_FieldDescriptorProto_Type | keyof typeof _google_protobuf_FieldDescriptorProto_Type); 'typeName'?: (string); 'defaultValue'?: (string); - 'options'?: (_google_protobuf_FieldOptions); + 'options'?: (_google_protobuf_FieldOptions | null); 'oneofIndex'?: (number); 'jsonName'?: (string); } @@ -54,7 +54,7 @@ export interface FieldDescriptorProto__Output { 'type': (keyof typeof _google_protobuf_FieldDescriptorProto_Type); 'typeName': (string); 'defaultValue': (string); - 'options'?: (_google_protobuf_FieldOptions__Output); + 'options': (_google_protobuf_FieldOptions__Output | null); 'oneofIndex': (number); 'jsonName': (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 530022afe..028e14b74 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -2,6 +2,7 @@ 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'; // Original file: null @@ -27,7 +28,10 @@ export interface FieldOptions { 'jstype'?: (_google_protobuf_FieldOptions_JSType | keyof typeof _google_protobuf_FieldOptions_JSType); 'weak'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; - '.validate.rules'?: (_validate_FieldRules); + '.validate.rules'?: (_validate_FieldRules | null); + '.udpa.annotations.sensitive'?: (boolean); + '.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation | null); + '.envoy.annotations.disallowed_by_default'?: (boolean); } export interface FieldOptions__Output { @@ -38,5 +42,8 @@ export interface FieldOptions__Output { 'jstype': (keyof typeof _google_protobuf_FieldOptions_JSType); 'weak': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; - '.validate.rules'?: (_validate_FieldRules__Output); + '.validate.rules': (_validate_FieldRules__Output | null); + '.udpa.annotations.sensitive': (boolean); + '.udpa.annotations.field_migrate': (_udpa_annotations_FieldMigrateAnnotation__Output | null); + '.envoy.annotations.disallowed_by_default': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorProto.ts index 2954e4208..b723da7c0 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileDescriptorProto.ts @@ -15,8 +15,8 @@ export interface FileDescriptorProto { 'enumType'?: (_google_protobuf_EnumDescriptorProto)[]; 'service'?: (_google_protobuf_ServiceDescriptorProto)[]; 'extension'?: (_google_protobuf_FieldDescriptorProto)[]; - 'options'?: (_google_protobuf_FileOptions); - 'sourceCodeInfo'?: (_google_protobuf_SourceCodeInfo); + 'options'?: (_google_protobuf_FileOptions | null); + 'sourceCodeInfo'?: (_google_protobuf_SourceCodeInfo | null); 'publicDependency'?: (number)[]; 'weakDependency'?: (number)[]; 'syntax'?: (string); @@ -30,8 +30,8 @@ export interface FileDescriptorProto__Output { 'enumType': (_google_protobuf_EnumDescriptorProto__Output)[]; 'service': (_google_protobuf_ServiceDescriptorProto__Output)[]; 'extension': (_google_protobuf_FieldDescriptorProto__Output)[]; - 'options'?: (_google_protobuf_FileOptions__Output); - 'sourceCodeInfo'?: (_google_protobuf_SourceCodeInfo__Output); + 'options': (_google_protobuf_FileOptions__Output | null); + 'sourceCodeInfo': (_google_protobuf_SourceCodeInfo__Output | null); 'publicDependency': (number)[]; 'weakDependency': (number)[]; 'syntax': (string); 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 573e847c0..b046c3ad3 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts @@ -1,6 +1,8 @@ // 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'; // Original file: null @@ -26,6 +28,8 @@ 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); } export interface FileOptions__Output { @@ -44,4 +48,6 @@ 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); } 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 d3b5a54b8..9dc521214 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts @@ -1,6 +1,8 @@ // 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'; export interface MessageOptions { 'messageSetWireFormat'?: (boolean); @@ -9,6 +11,8 @@ export interface MessageOptions { 'mapEntry'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; '.validate.disabled'?: (boolean); + '.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation | null); + '.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation | null); } export interface MessageOptions__Output { @@ -18,4 +22,6 @@ export interface MessageOptions__Output { '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); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/MethodDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/MethodDescriptorProto.ts index bc2f0afb5..c76c0ea23 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MethodDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MethodDescriptorProto.ts @@ -6,7 +6,7 @@ export interface MethodDescriptorProto { 'name'?: (string); 'inputType'?: (string); 'outputType'?: (string); - 'options'?: (_google_protobuf_MethodOptions); + 'options'?: (_google_protobuf_MethodOptions | null); 'clientStreaming'?: (boolean); 'serverStreaming'?: (boolean); } @@ -15,7 +15,7 @@ export interface MethodDescriptorProto__Output { 'name': (string); 'inputType': (string); 'outputType': (string); - 'options'?: (_google_protobuf_MethodOptions__Output); + 'options': (_google_protobuf_MethodOptions__Output | null); 'clientStreaming': (boolean); 'serverStreaming': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/OneofDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/OneofDescriptorProto.ts index c10ccecd3..636f13ed4 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/OneofDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/OneofDescriptorProto.ts @@ -4,10 +4,10 @@ import type { OneofOptions as _google_protobuf_OneofOptions, OneofOptions__Outpu export interface OneofDescriptorProto { 'name'?: (string); - 'options'?: (_google_protobuf_OneofOptions); + 'options'?: (_google_protobuf_OneofOptions | null); } export interface OneofDescriptorProto__Output { 'name': (string); - 'options'?: (_google_protobuf_OneofOptions__Output); + 'options': (_google_protobuf_OneofOptions__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/ServiceDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/ServiceDescriptorProto.ts index 695a8775c..40c9263ea 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/ServiceDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/ServiceDescriptorProto.ts @@ -6,11 +6,11 @@ import type { ServiceOptions as _google_protobuf_ServiceOptions, ServiceOptions_ export interface ServiceDescriptorProto { 'name'?: (string); 'method'?: (_google_protobuf_MethodDescriptorProto)[]; - 'options'?: (_google_protobuf_ServiceOptions); + 'options'?: (_google_protobuf_ServiceOptions | null); } export interface ServiceDescriptorProto__Output { 'name': (string); 'method': (_google_protobuf_MethodDescriptorProto__Output)[]; - 'options'?: (_google_protobuf_ServiceOptions__Output); + 'options': (_google_protobuf_ServiceOptions__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Struct.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Struct.ts index 9919350e4..41b79eab3 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/Struct.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Struct.ts @@ -7,5 +7,5 @@ export interface Struct { } export interface Struct__Output { - 'fields'?: ({[key: string]: _google_protobuf_Value__Output}); + 'fields': ({[key: string]: _google_protobuf_Value__Output}); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts index 0860535dc..b1a942a56 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts @@ -9,8 +9,8 @@ export interface Value { 'numberValue'?: (number | string); 'stringValue'?: (string); 'boolValue'?: (boolean); - 'structValue'?: (_google_protobuf_Struct); - 'listValue'?: (_google_protobuf_ListValue); + 'structValue'?: (_google_protobuf_Struct | null); + 'listValue'?: (_google_protobuf_ListValue | null); 'kind'?: "nullValue"|"numberValue"|"stringValue"|"boolValue"|"structValue"|"listValue"; } @@ -19,7 +19,7 @@ export interface Value__Output { 'numberValue'?: (number); 'stringValue'?: (string); 'boolValue'?: (boolean); - 'structValue'?: (_google_protobuf_Struct__Output); - 'listValue'?: (_google_protobuf_ListValue__Output); + 'structValue'?: (_google_protobuf_Struct__Output | null); + 'listValue'?: (_google_protobuf_ListValue__Output | null); 'kind': "nullValue"|"numberValue"|"stringValue"|"boolValue"|"structValue"|"listValue"; } diff --git a/packages/grpc-js-xds/src/generated/http_connection_manager.ts b/packages/grpc-js-xds/src/generated/http_connection_manager.ts index 9cb81bb0e..bb4e80360 100644 --- a/packages/grpc-js-xds/src/generated/http_connection_manager.ts +++ b/packages/grpc-js-xds/src/generated/http_connection_manager.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; type SubtypeConstructor any, Subtype> = { diff --git a/packages/grpc-js-xds/src/generated/listener.ts b/packages/grpc-js-xds/src/generated/listener.ts index 69454efdc..cf7297a5b 100644 --- a/packages/grpc-js-xds/src/generated/listener.ts +++ b/packages/grpc-js-xds/src/generated/listener.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; type SubtypeConstructor any, Subtype> = { diff --git a/packages/grpc-js-xds/src/generated/lrs.ts b/packages/grpc-js-xds/src/generated/lrs.ts index 9a2864fcf..929fd6914 100644 --- a/packages/grpc-js-xds/src/generated/lrs.ts +++ b/packages/grpc-js-xds/src/generated/lrs.ts @@ -1,8 +1,8 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; -import type { LoadReportingServiceClient as _envoy_service_load_stats_v2_LoadReportingServiceClient } from './envoy/service/load_stats/v2/LoadReportingService'; -import type { LoadReportingServiceClient as _envoy_service_load_stats_v3_LoadReportingServiceClient } from './envoy/service/load_stats/v3/LoadReportingService'; +import type { LoadReportingServiceClient as _envoy_service_load_stats_v2_LoadReportingServiceClient, LoadReportingServiceDefinition as _envoy_service_load_stats_v2_LoadReportingServiceDefinition } from './envoy/service/load_stats/v2/LoadReportingService'; +import type { LoadReportingServiceClient as _envoy_service_load_stats_v3_LoadReportingServiceClient, LoadReportingServiceDefinition as _envoy_service_load_stats_v3_LoadReportingServiceDefinition } from './envoy/service/load_stats/v3/LoadReportingService'; type SubtypeConstructor any, Subtype> = { new(...args: ConstructorParameters): Subtype; @@ -102,12 +102,12 @@ export interface ProtoGrpcType { service: { load_stats: { v2: { - LoadReportingService: SubtypeConstructor & { service: ServiceDefinition } + LoadReportingService: SubtypeConstructor & { service: _envoy_service_load_stats_v2_LoadReportingServiceDefinition } LoadStatsRequest: MessageTypeDefinition LoadStatsResponse: MessageTypeDefinition } v3: { - LoadReportingService: SubtypeConstructor & { service: ServiceDefinition } + LoadReportingService: SubtypeConstructor & { service: _envoy_service_load_stats_v3_LoadReportingServiceDefinition } LoadStatsRequest: MessageTypeDefinition LoadStatsResponse: MessageTypeDefinition } diff --git a/packages/grpc-js-xds/src/generated/route.ts b/packages/grpc-js-xds/src/generated/route.ts index a51060779..702395d19 100644 --- a/packages/grpc-js-xds/src/generated/route.ts +++ b/packages/grpc-js-xds/src/generated/route.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; type SubtypeConstructor any, Subtype> = { diff --git a/packages/grpc-js-xds/src/generated/typed_struct.ts b/packages/grpc-js-xds/src/generated/typed_struct.ts index 78d781a29..47abe063c 100644 --- a/packages/grpc-js-xds/src/generated/typed_struct.ts +++ b/packages/grpc-js-xds/src/generated/typed_struct.ts @@ -1,5 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; type SubtypeConstructor any, Subtype> = { diff --git a/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts index f9d842982..435872808 100644 --- a/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts +++ b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts @@ -36,7 +36,7 @@ export interface TypedStruct { /** * A JSON representation of the above specified type. */ - 'value'?: (_google_protobuf_Struct); + 'value'?: (_google_protobuf_Struct | null); } /** @@ -73,5 +73,5 @@ export interface TypedStruct__Output { /** * A JSON representation of the above specified type. */ - 'value'?: (_google_protobuf_Struct__Output); + 'value': (_google_protobuf_Struct__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/validate/DurationRules.ts b/packages/grpc-js-xds/src/generated/validate/DurationRules.ts index 86b80a34d..879928e20 100644 --- a/packages/grpc-js-xds/src/generated/validate/DurationRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/DurationRules.ts @@ -14,27 +14,27 @@ export interface DurationRules { /** * Const specifies that this field must be exactly the specified value */ - 'const'?: (_google_protobuf_Duration); + 'const'?: (_google_protobuf_Duration | null); /** * Lt specifies that this field must be less than the specified value, * exclusive */ - 'lt'?: (_google_protobuf_Duration); + 'lt'?: (_google_protobuf_Duration | null); /** * Lt specifies that this field must be less than the specified value, * inclusive */ - 'lte'?: (_google_protobuf_Duration); + 'lte'?: (_google_protobuf_Duration | null); /** * Gt specifies that this field must be greater than the specified value, * exclusive */ - 'gt'?: (_google_protobuf_Duration); + 'gt'?: (_google_protobuf_Duration | null); /** * Gte specifies that this field must be greater than the specified value, * inclusive */ - 'gte'?: (_google_protobuf_Duration); + 'gte'?: (_google_protobuf_Duration | null); /** * In specifies that this field must be equal to one of the specified * values @@ -59,27 +59,27 @@ export interface DurationRules__Output { /** * Const specifies that this field must be exactly the specified value */ - 'const'?: (_google_protobuf_Duration__Output); + 'const': (_google_protobuf_Duration__Output | null); /** * Lt specifies that this field must be less than the specified value, * exclusive */ - 'lt'?: (_google_protobuf_Duration__Output); + 'lt': (_google_protobuf_Duration__Output | null); /** * Lt specifies that this field must be less than the specified value, * inclusive */ - 'lte'?: (_google_protobuf_Duration__Output); + 'lte': (_google_protobuf_Duration__Output | null); /** * Gt specifies that this field must be greater than the specified value, * exclusive */ - 'gt'?: (_google_protobuf_Duration__Output); + 'gt': (_google_protobuf_Duration__Output | null); /** * Gte specifies that this field must be greater than the specified value, * inclusive */ - 'gte'?: (_google_protobuf_Duration__Output); + 'gte': (_google_protobuf_Duration__Output | null); /** * In specifies that this field must be equal to one of the specified * values diff --git a/packages/grpc-js-xds/src/generated/validate/FieldRules.ts b/packages/grpc-js-xds/src/generated/validate/FieldRules.ts index dae124bc1..067125775 100644 --- a/packages/grpc-js-xds/src/generated/validate/FieldRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/FieldRules.ts @@ -32,34 +32,34 @@ export interface FieldRules { /** * Scalar Field Types */ - 'float'?: (_validate_FloatRules); - 'double'?: (_validate_DoubleRules); - 'int32'?: (_validate_Int32Rules); - 'int64'?: (_validate_Int64Rules); - 'uint32'?: (_validate_UInt32Rules); - 'uint64'?: (_validate_UInt64Rules); - 'sint32'?: (_validate_SInt32Rules); - 'sint64'?: (_validate_SInt64Rules); - 'fixed32'?: (_validate_Fixed32Rules); - 'fixed64'?: (_validate_Fixed64Rules); - 'sfixed32'?: (_validate_SFixed32Rules); - 'sfixed64'?: (_validate_SFixed64Rules); - 'bool'?: (_validate_BoolRules); - 'string'?: (_validate_StringRules); - 'bytes'?: (_validate_BytesRules); + 'float'?: (_validate_FloatRules | null); + 'double'?: (_validate_DoubleRules | null); + 'int32'?: (_validate_Int32Rules | null); + 'int64'?: (_validate_Int64Rules | null); + 'uint32'?: (_validate_UInt32Rules | null); + 'uint64'?: (_validate_UInt64Rules | null); + 'sint32'?: (_validate_SInt32Rules | null); + 'sint64'?: (_validate_SInt64Rules | null); + 'fixed32'?: (_validate_Fixed32Rules | null); + 'fixed64'?: (_validate_Fixed64Rules | null); + 'sfixed32'?: (_validate_SFixed32Rules | null); + 'sfixed64'?: (_validate_SFixed64Rules | null); + 'bool'?: (_validate_BoolRules | null); + 'string'?: (_validate_StringRules | null); + 'bytes'?: (_validate_BytesRules | null); /** * Complex Field Types */ - 'enum'?: (_validate_EnumRules); - 'message'?: (_validate_MessageRules); - 'repeated'?: (_validate_RepeatedRules); - 'map'?: (_validate_MapRules); + 'enum'?: (_validate_EnumRules | null); + 'message'?: (_validate_MessageRules | null); + 'repeated'?: (_validate_RepeatedRules | null); + 'map'?: (_validate_MapRules | null); /** * Well-Known Field Types */ - 'any'?: (_validate_AnyRules); - 'duration'?: (_validate_DurationRules); - 'timestamp'?: (_validate_TimestampRules); + 'any'?: (_validate_AnyRules | null); + 'duration'?: (_validate_DurationRules | null); + 'timestamp'?: (_validate_TimestampRules | null); 'type'?: "float"|"double"|"int32"|"int64"|"uint32"|"uint64"|"sint32"|"sint64"|"fixed32"|"fixed64"|"sfixed32"|"sfixed64"|"bool"|"string"|"bytes"|"enum"|"repeated"|"map"|"any"|"duration"|"timestamp"; } @@ -71,33 +71,33 @@ export interface FieldRules__Output { /** * Scalar Field Types */ - 'float'?: (_validate_FloatRules__Output); - 'double'?: (_validate_DoubleRules__Output); - 'int32'?: (_validate_Int32Rules__Output); - 'int64'?: (_validate_Int64Rules__Output); - 'uint32'?: (_validate_UInt32Rules__Output); - 'uint64'?: (_validate_UInt64Rules__Output); - 'sint32'?: (_validate_SInt32Rules__Output); - 'sint64'?: (_validate_SInt64Rules__Output); - 'fixed32'?: (_validate_Fixed32Rules__Output); - 'fixed64'?: (_validate_Fixed64Rules__Output); - 'sfixed32'?: (_validate_SFixed32Rules__Output); - 'sfixed64'?: (_validate_SFixed64Rules__Output); - 'bool'?: (_validate_BoolRules__Output); - 'string'?: (_validate_StringRules__Output); - 'bytes'?: (_validate_BytesRules__Output); + 'float'?: (_validate_FloatRules__Output | null); + 'double'?: (_validate_DoubleRules__Output | null); + 'int32'?: (_validate_Int32Rules__Output | null); + 'int64'?: (_validate_Int64Rules__Output | null); + 'uint32'?: (_validate_UInt32Rules__Output | null); + 'uint64'?: (_validate_UInt64Rules__Output | null); + 'sint32'?: (_validate_SInt32Rules__Output | null); + 'sint64'?: (_validate_SInt64Rules__Output | null); + 'fixed32'?: (_validate_Fixed32Rules__Output | null); + 'fixed64'?: (_validate_Fixed64Rules__Output | null); + 'sfixed32'?: (_validate_SFixed32Rules__Output | null); + 'sfixed64'?: (_validate_SFixed64Rules__Output | null); + 'bool'?: (_validate_BoolRules__Output | null); + 'string'?: (_validate_StringRules__Output | null); + 'bytes'?: (_validate_BytesRules__Output | null); /** * Complex Field Types */ - 'enum'?: (_validate_EnumRules__Output); - 'message'?: (_validate_MessageRules__Output); - 'repeated'?: (_validate_RepeatedRules__Output); - 'map'?: (_validate_MapRules__Output); + 'enum'?: (_validate_EnumRules__Output | null); + 'message': (_validate_MessageRules__Output | null); + 'repeated'?: (_validate_RepeatedRules__Output | null); + 'map'?: (_validate_MapRules__Output | null); /** * Well-Known Field Types */ - 'any'?: (_validate_AnyRules__Output); - 'duration'?: (_validate_DurationRules__Output); - 'timestamp'?: (_validate_TimestampRules__Output); + 'any'?: (_validate_AnyRules__Output | null); + 'duration'?: (_validate_DurationRules__Output | null); + 'timestamp'?: (_validate_TimestampRules__Output | null); 'type': "float"|"double"|"int32"|"int64"|"uint32"|"uint64"|"sint32"|"sint64"|"fixed32"|"fixed64"|"sfixed32"|"sfixed64"|"bool"|"string"|"bytes"|"enum"|"repeated"|"map"|"any"|"duration"|"timestamp"; } diff --git a/packages/grpc-js-xds/src/generated/validate/MapRules.ts b/packages/grpc-js-xds/src/generated/validate/MapRules.ts index 0c89bf2b3..7efe5b390 100644 --- a/packages/grpc-js-xds/src/generated/validate/MapRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/MapRules.ts @@ -25,13 +25,13 @@ export interface MapRules { /** * Keys specifies the constraints to be applied to each key in the field. */ - 'keys'?: (_validate_FieldRules); + 'keys'?: (_validate_FieldRules | null); /** * Values specifies the constraints to be applied to the value of each key * in the field. Message values will still have their validations evaluated * unless skip is specified here. */ - 'values'?: (_validate_FieldRules); + 'values'?: (_validate_FieldRules | null); } /** @@ -56,11 +56,11 @@ export interface MapRules__Output { /** * Keys specifies the constraints to be applied to each key in the field. */ - 'keys'?: (_validate_FieldRules__Output); + 'keys': (_validate_FieldRules__Output | null); /** * Values specifies the constraints to be applied to the value of each key * in the field. Message values will still have their validations evaluated * unless skip is specified here. */ - 'values'?: (_validate_FieldRules__Output); + 'values': (_validate_FieldRules__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/validate/RepeatedRules.ts b/packages/grpc-js-xds/src/generated/validate/RepeatedRules.ts index 1f6d4f0ff..d347b6497 100644 --- a/packages/grpc-js-xds/src/generated/validate/RepeatedRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/RepeatedRules.ts @@ -28,7 +28,7 @@ export interface RepeatedRules { * Repeated message fields will still execute validation against each item * unless skip is specified here. */ - 'items'?: (_validate_FieldRules); + 'items'?: (_validate_FieldRules | null); } /** @@ -56,5 +56,5 @@ export interface RepeatedRules__Output { * Repeated message fields will still execute validation against each item * unless skip is specified here. */ - 'items'?: (_validate_FieldRules__Output); + 'items': (_validate_FieldRules__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/validate/TimestampRules.ts b/packages/grpc-js-xds/src/generated/validate/TimestampRules.ts index 9436cc8ee..69d548126 100644 --- a/packages/grpc-js-xds/src/generated/validate/TimestampRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/TimestampRules.ts @@ -15,27 +15,27 @@ export interface TimestampRules { /** * Const specifies that this field must be exactly the specified value */ - 'const'?: (_google_protobuf_Timestamp); + 'const'?: (_google_protobuf_Timestamp | null); /** * Lt specifies that this field must be less than the specified value, * exclusive */ - 'lt'?: (_google_protobuf_Timestamp); + 'lt'?: (_google_protobuf_Timestamp | null); /** * Lte specifies that this field must be less than the specified value, * inclusive */ - 'lte'?: (_google_protobuf_Timestamp); + 'lte'?: (_google_protobuf_Timestamp | null); /** * Gt specifies that this field must be greater than the specified value, * exclusive */ - 'gt'?: (_google_protobuf_Timestamp); + 'gt'?: (_google_protobuf_Timestamp | null); /** * Gte specifies that this field must be greater than the specified value, * inclusive */ - 'gte'?: (_google_protobuf_Timestamp); + 'gte'?: (_google_protobuf_Timestamp | null); /** * LtNow specifies that this must be less than the current time. LtNow * can only be used with the Within rule. @@ -51,7 +51,7 @@ export interface TimestampRules { * current time. This constraint can be used alone or with the LtNow and * GtNow rules. */ - 'within'?: (_google_protobuf_Duration); + 'within'?: (_google_protobuf_Duration | null); } /** @@ -66,27 +66,27 @@ export interface TimestampRules__Output { /** * Const specifies that this field must be exactly the specified value */ - 'const'?: (_google_protobuf_Timestamp__Output); + 'const': (_google_protobuf_Timestamp__Output | null); /** * Lt specifies that this field must be less than the specified value, * exclusive */ - 'lt'?: (_google_protobuf_Timestamp__Output); + 'lt': (_google_protobuf_Timestamp__Output | null); /** * Lte specifies that this field must be less than the specified value, * inclusive */ - 'lte'?: (_google_protobuf_Timestamp__Output); + 'lte': (_google_protobuf_Timestamp__Output | null); /** * Gt specifies that this field must be greater than the specified value, * exclusive */ - 'gt'?: (_google_protobuf_Timestamp__Output); + 'gt': (_google_protobuf_Timestamp__Output | null); /** * Gte specifies that this field must be greater than the specified value, * inclusive */ - 'gte'?: (_google_protobuf_Timestamp__Output); + 'gte': (_google_protobuf_Timestamp__Output | null); /** * LtNow specifies that this must be less than the current time. LtNow * can only be used with the Within rule. @@ -102,5 +102,5 @@ export interface TimestampRules__Output { * current time. This constraint can be used alone or with the LtNow and * GtNow rules. */ - 'within'?: (_google_protobuf_Duration__Output); + 'within': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts index 4a9a45527..8d583d961 100644 --- a/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts @@ -22,7 +22,7 @@ export interface _xds_core_v3_CollectionEntry_InlineEntry { /** * The resource payload, including type URL. */ - 'resource'?: (_google_protobuf_Any); + 'resource'?: (_google_protobuf_Any | null); } /** @@ -44,7 +44,7 @@ export interface _xds_core_v3_CollectionEntry_InlineEntry__Output { /** * The resource payload, including type URL. */ - 'resource'?: (_google_protobuf_Any__Output); + 'resource': (_google_protobuf_Any__Output | null); } /** @@ -60,11 +60,11 @@ export interface CollectionEntry { /** * A resource locator describing how the member resource is to be located. */ - 'locator'?: (_xds_core_v3_ResourceLocator); + 'locator'?: (_xds_core_v3_ResourceLocator | null); /** * The resource is inlined in the list collection. */ - 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry); + 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry | null); 'resource_specifier'?: "locator"|"inline_entry"; } @@ -81,10 +81,10 @@ export interface CollectionEntry__Output { /** * A resource locator describing how the member resource is to be located. */ - 'locator'?: (_xds_core_v3_ResourceLocator__Output); + 'locator'?: (_xds_core_v3_ResourceLocator__Output | null); /** * The resource is inlined in the list collection. */ - 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry__Output); + 'inline_entry'?: (_xds_core_v3_CollectionEntry_InlineEntry__Output | null); 'resource_specifier': "locator"|"inline_entry"; } diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts index f28a31d20..2b314d8bb 100644 --- a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts @@ -37,7 +37,7 @@ export interface _xds_core_v3_ResourceLocator_Directive { * resource, it will fallback to `bar`. Alternative resources do not need * to have equivalent content, but they should be functional substitutes. */ - 'alt'?: (_xds_core_v3_ResourceLocator); + 'alt'?: (_xds_core_v3_ResourceLocator | null); /** * List collections support inlining of resources via the entry field in * Resource. These inlined Resource objects may have an optional name @@ -83,7 +83,7 @@ export interface _xds_core_v3_ResourceLocator_Directive__Output { * resource, it will fallback to `bar`. Alternative resources do not need * to have equivalent content, but they should be functional substitutes. */ - 'alt'?: (_xds_core_v3_ResourceLocator__Output); + 'alt'?: (_xds_core_v3_ResourceLocator__Output | null); /** * List collections support inlining of resources via the entry field in * Resource. These inlined Resource objects may have an optional name @@ -150,7 +150,7 @@ export interface ResourceLocator { * there must be no additional context parameters set on the matched * resource. */ - 'exact_context'?: (_xds_core_v3_ContextParams); + 'exact_context'?: (_xds_core_v3_ContextParams | null); /** * A list of directives that appear in the xDS resource locator #fragment. * @@ -208,7 +208,7 @@ export interface ResourceLocator__Output { * there must be no additional context parameters set on the matched * resource. */ - 'exact_context'?: (_xds_core_v3_ContextParams__Output); + 'exact_context'?: (_xds_core_v3_ContextParams__Output | null); /** * A list of directives that appear in the xDS resource locator #fragment. * From 8718d7d4761efa8c74948ac2a6e52aa735c6ce3e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 14:59:38 -0700 Subject: [PATCH 056/694] grpc-js-xds: Generate files for fault injection --- .../filters/common/fault/v3/FaultDelay.ts | 81 +++++++ .../filters/common/fault/v3/FaultRateLimit.ts | 78 +++++++ .../filters/http/fault/v3/FaultAbort.ts | 67 ++++++ .../filters/http/fault/v3/HTTPFault.ts | 213 +++++++++++++++++ packages/grpc-js-xds/src/generated/fault.ts | 220 ++++++++++++++++++ 5 files changed, 659 insertions(+) create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultRateLimit.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/FaultAbort.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts create mode 100644 packages/grpc-js-xds/src/generated/fault.ts diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts new file mode 100644 index 000000000..8e2739958 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts @@ -0,0 +1,81 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/common/fault/v3/fault.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../../../google/protobuf/Duration'; +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../../../envoy/type/v3/FractionalPercent'; + +// Original file: deps/envoy-api/envoy/extensions/filters/common/fault/v3/fault.proto + +export enum _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType { + /** + * Unused and deprecated. + */ + FIXED = 0, +} + +/** + * Fault delays are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay { +} + +/** + * Fault delays are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay__Output { +} + +/** + * Delay specification is used to inject latency into the + * HTTP/gRPC/Mongo/Redis operation or delay proxying of TCP connections. + * [#next-free-field: 6] + */ +export interface FaultDelay { + /** + * Add a fixed delay before forwarding the operation upstream. See + * https://developers.google.com/protocol-buffers/docs/proto3#json for + * the JSON/YAML Duration mapping. For HTTP/Mongo/Redis, the specified + * delay will be injected before a new request/operation. For TCP + * connections, the proxying of the connection upstream will be delayed + * for the specified period. This is required if type is FIXED. + */ + 'fixed_delay'?: (_google_protobuf_Duration | null); + /** + * The percentage of operations/connections/requests on which the delay will be injected. + */ + 'percentage'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Fault delays are controlled via an HTTP header (if applicable). + */ + 'header_delay'?: (_envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay | null); + 'fault_delay_secifier'?: "fixed_delay"|"header_delay"; +} + +/** + * Delay specification is used to inject latency into the + * HTTP/gRPC/Mongo/Redis operation or delay proxying of TCP connections. + * [#next-free-field: 6] + */ +export interface FaultDelay__Output { + /** + * Add a fixed delay before forwarding the operation upstream. See + * https://developers.google.com/protocol-buffers/docs/proto3#json for + * the JSON/YAML Duration mapping. For HTTP/Mongo/Redis, the specified + * delay will be injected before a new request/operation. For TCP + * connections, the proxying of the connection upstream will be delayed + * for the specified period. This is required if type is FIXED. + */ + 'fixed_delay'?: (_google_protobuf_Duration__Output | null); + /** + * The percentage of operations/connections/requests on which the delay will be injected. + */ + 'percentage': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Fault delays are controlled via an HTTP header (if applicable). + */ + 'header_delay'?: (_envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDelay__Output | null); + 'fault_delay_secifier': "fixed_delay"|"header_delay"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultRateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultRateLimit.ts new file mode 100644 index 000000000..4df7395bb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultRateLimit.ts @@ -0,0 +1,78 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/common/fault/v3/fault.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../../../envoy/type/v3/FractionalPercent'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Describes a fixed/constant rate limit. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit { + /** + * The limit supplied in KiB/s. + */ + 'limit_kbps'?: (number | string | Long); +} + +/** + * Describes a fixed/constant rate limit. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit__Output { + /** + * The limit supplied in KiB/s. + */ + 'limit_kbps': (string); +} + +/** + * Rate limits are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit { +} + +/** + * Rate limits are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit__Output { +} + +/** + * Describes a rate limit to be applied. + */ +export interface FaultRateLimit { + /** + * A fixed rate limit. + */ + 'fixed_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit | null); + /** + * The percentage of operations/connections/requests on which the rate limit will be injected. + */ + 'percentage'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Rate limits are controlled via an HTTP header (if applicable). + */ + 'header_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit | null); + 'limit_type'?: "fixed_limit"|"header_limit"; +} + +/** + * Describes a rate limit to be applied. + */ +export interface FaultRateLimit__Output { + /** + * A fixed rate limit. + */ + 'fixed_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_FixedLimit__Output | null); + /** + * The percentage of operations/connections/requests on which the rate limit will be injected. + */ + 'percentage': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Rate limits are controlled via an HTTP header (if applicable). + */ + 'header_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit_HeaderLimit__Output | null); + 'limit_type': "fixed_limit"|"header_limit"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/FaultAbort.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/FaultAbort.ts new file mode 100644 index 000000000..823706cb7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/FaultAbort.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/http/fault/v3/fault.proto + +import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalPercent__Output as _envoy_type_v3_FractionalPercent__Output } from '../../../../../../envoy/type/v3/FractionalPercent'; + +/** + * Fault aborts are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort { +} + +/** + * Fault aborts are controlled via an HTTP header (if applicable). See the + * :ref:`HTTP fault filter ` documentation for + * more information. + */ +export interface _envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort__Output { +} + +/** + * [#next-free-field: 6] + */ +export interface FaultAbort { + /** + * HTTP status code to use to abort the HTTP request. + */ + 'http_status'?: (number); + /** + * The percentage of requests/operations/connections that will be aborted with the error code + * provided. + */ + 'percentage'?: (_envoy_type_v3_FractionalPercent | null); + /** + * Fault aborts are controlled via an HTTP header (if applicable). + */ + 'header_abort'?: (_envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort | null); + /** + * gRPC status code to use to abort the gRPC request. + */ + 'grpc_status'?: (number); + 'error_type'?: "http_status"|"grpc_status"|"header_abort"; +} + +/** + * [#next-free-field: 6] + */ +export interface FaultAbort__Output { + /** + * HTTP status code to use to abort the HTTP request. + */ + 'http_status'?: (number); + /** + * The percentage of requests/operations/connections that will be aborted with the error code + * provided. + */ + 'percentage': (_envoy_type_v3_FractionalPercent__Output | null); + /** + * Fault aborts are controlled via an HTTP header (if applicable). + */ + 'header_abort'?: (_envoy_extensions_filters_http_fault_v3_FaultAbort_HeaderAbort__Output | null); + /** + * gRPC status code to use to abort the gRPC request. + */ + 'grpc_status'?: (number); + 'error_type': "http_status"|"grpc_status"|"header_abort"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts new file mode 100644 index 000000000..20c543508 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts @@ -0,0 +1,213 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/http/fault/v3/fault.proto + +import type { FaultDelay as _envoy_extensions_filters_common_fault_v3_FaultDelay, FaultDelay__Output as _envoy_extensions_filters_common_fault_v3_FaultDelay__Output } from '../../../../../../envoy/extensions/filters/common/fault/v3/FaultDelay'; +import type { FaultAbort as _envoy_extensions_filters_http_fault_v3_FaultAbort, FaultAbort__Output as _envoy_extensions_filters_http_fault_v3_FaultAbort__Output } from '../../../../../../envoy/extensions/filters/http/fault/v3/FaultAbort'; +import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../../../envoy/config/route/v3/HeaderMatcher'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../../google/protobuf/UInt32Value'; +import type { FaultRateLimit as _envoy_extensions_filters_common_fault_v3_FaultRateLimit, FaultRateLimit__Output as _envoy_extensions_filters_common_fault_v3_FaultRateLimit__Output } from '../../../../../../envoy/extensions/filters/common/fault/v3/FaultRateLimit'; + +/** + * [#next-free-field: 15] + */ +export interface HTTPFault { + /** + * If specified, the filter will inject delays based on the values in the + * object. + */ + 'delay'?: (_envoy_extensions_filters_common_fault_v3_FaultDelay | null); + /** + * If specified, the filter will abort requests based on the values in + * the object. At least *abort* or *delay* must be specified. + */ + 'abort'?: (_envoy_extensions_filters_http_fault_v3_FaultAbort | null); + /** + * Specifies the name of the (destination) upstream cluster that the + * filter should match on. Fault injection will be restricted to requests + * bound to the specific upstream cluster. + */ + 'upstream_cluster'?: (string); + /** + * Specifies a set of headers that the filter should match on. The fault + * injection filter can be applied selectively to requests that match a set of + * headers specified in the fault filter config. The chances of actual fault + * injection further depend on the value of the :ref:`percentage + * ` field. + * The filter will check the request's headers against all the specified + * headers in the filter config. A match will happen if all the headers in the + * config are present in the request with the same values (or based on + * presence if the *value* field is not in the config). + */ + 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; + /** + * Faults are injected for the specified list of downstream hosts. If this + * setting is not set, faults are injected for all downstream nodes. + * Downstream node name is taken from :ref:`the HTTP + * x-envoy-downstream-service-node + * ` header and compared + * against downstream_nodes list. + */ + 'downstream_nodes'?: (string)[]; + /** + * The maximum number of faults that can be active at a single time via the configured fault + * filter. Note that because this setting can be overridden at the route level, it's possible + * for the number of active faults to be greater than this value (if injected via a different + * route). If not specified, defaults to unlimited. This setting can be overridden via + * `runtime ` and any faults that are not injected + * due to overflow will be indicated via the `faults_overflow + * ` stat. + * + * .. attention:: + * Like other :ref:`circuit breakers ` in Envoy, this is a fuzzy + * limit. It's possible for the number of active faults to rise slightly above the configured + * amount due to the implementation details. + */ + 'max_active_faults'?: (_google_protobuf_UInt32Value | null); + /** + * The response rate limit to be applied to the response body of the stream. When configured, + * the percentage can be overridden by the :ref:`fault.http.rate_limit.response_percent + * ` runtime key. + * + * .. attention:: + * This is a per-stream limit versus a connection level limit. This means that concurrent streams + * will each get an independent limit. + */ + 'response_rate_limit'?: (_envoy_extensions_filters_common_fault_v3_FaultRateLimit | null); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_delay_percent + */ + 'delay_percent_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.abort_percent + */ + 'abort_percent_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_duration_ms + */ + 'delay_duration_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.http_status + */ + 'abort_http_status_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.max_active_faults + */ + 'max_active_faults_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.rate_limit.response_percent + */ + 'response_rate_limit_percent_runtime'?: (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.grpc_status + */ + 'abort_grpc_status_runtime'?: (string); +} + +/** + * [#next-free-field: 15] + */ +export interface HTTPFault__Output { + /** + * If specified, the filter will inject delays based on the values in the + * object. + */ + 'delay': (_envoy_extensions_filters_common_fault_v3_FaultDelay__Output | null); + /** + * If specified, the filter will abort requests based on the values in + * the object. At least *abort* or *delay* must be specified. + */ + 'abort': (_envoy_extensions_filters_http_fault_v3_FaultAbort__Output | null); + /** + * Specifies the name of the (destination) upstream cluster that the + * filter should match on. Fault injection will be restricted to requests + * bound to the specific upstream cluster. + */ + 'upstream_cluster': (string); + /** + * Specifies a set of headers that the filter should match on. The fault + * injection filter can be applied selectively to requests that match a set of + * headers specified in the fault filter config. The chances of actual fault + * injection further depend on the value of the :ref:`percentage + * ` field. + * The filter will check the request's headers against all the specified + * headers in the filter config. A match will happen if all the headers in the + * config are present in the request with the same values (or based on + * presence if the *value* field is not in the config). + */ + 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; + /** + * Faults are injected for the specified list of downstream hosts. If this + * setting is not set, faults are injected for all downstream nodes. + * Downstream node name is taken from :ref:`the HTTP + * x-envoy-downstream-service-node + * ` header and compared + * against downstream_nodes list. + */ + 'downstream_nodes': (string)[]; + /** + * The maximum number of faults that can be active at a single time via the configured fault + * filter. Note that because this setting can be overridden at the route level, it's possible + * for the number of active faults to be greater than this value (if injected via a different + * route). If not specified, defaults to unlimited. This setting can be overridden via + * `runtime ` and any faults that are not injected + * due to overflow will be indicated via the `faults_overflow + * ` stat. + * + * .. attention:: + * Like other :ref:`circuit breakers ` in Envoy, this is a fuzzy + * limit. It's possible for the number of active faults to rise slightly above the configured + * amount due to the implementation details. + */ + 'max_active_faults': (_google_protobuf_UInt32Value__Output | null); + /** + * The response rate limit to be applied to the response body of the stream. When configured, + * the percentage can be overridden by the :ref:`fault.http.rate_limit.response_percent + * ` runtime key. + * + * .. attention:: + * This is a per-stream limit versus a connection level limit. This means that concurrent streams + * will each get an independent limit. + */ + 'response_rate_limit': (_envoy_extensions_filters_common_fault_v3_FaultRateLimit__Output | null); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_delay_percent + */ + 'delay_percent_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.abort_percent + */ + 'abort_percent_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.delay.fixed_duration_ms + */ + 'delay_duration_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.http_status + */ + 'abort_http_status_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.max_active_faults + */ + 'max_active_faults_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.rate_limit.response_percent + */ + 'response_rate_limit_percent_runtime': (string); + /** + * The runtime key to override the :ref:`default ` + * runtime. The default is: fault.http.abort.grpc_status + */ + 'abort_grpc_status_runtime': (string); +} diff --git a/packages/grpc-js-xds/src/generated/fault.ts b/packages/grpc-js-xds/src/generated/fault.ts new file mode 100644 index 000000000..6eefcdfbd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/fault.ts @@ -0,0 +1,220 @@ +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: { + annotations: { + } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + route: { + v3: { + CorsPolicy: MessageTypeDefinition + Decorator: MessageTypeDefinition + DirectResponseAction: MessageTypeDefinition + FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition + HeaderMatcher: MessageTypeDefinition + HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition + QueryParameterMatcher: MessageTypeDefinition + RateLimit: MessageTypeDefinition + RedirectAction: MessageTypeDefinition + RetryPolicy: MessageTypeDefinition + Route: MessageTypeDefinition + RouteAction: MessageTypeDefinition + RouteMatch: MessageTypeDefinition + Tracing: MessageTypeDefinition + VirtualCluster: MessageTypeDefinition + VirtualHost: MessageTypeDefinition + WeightedCluster: MessageTypeDefinition + } + } + } + extensions: { + filters: { + common: { + fault: { + v3: { + FaultDelay: MessageTypeDefinition + FaultRateLimit: MessageTypeDefinition + } + } + } + http: { + fault: { + v3: { + FaultAbort: MessageTypeDefinition + HTTPFault: MessageTypeDefinition + } + } + } + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } + } + metadata: { + v3: { + MetadataKey: MessageTypeDefinition + MetadataKind: MessageTypeDefinition + } + } + tracing: { + v3: { + CustomTag: MessageTypeDefinition + } + } + v3: { + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: 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 + VersioningAnnotation: 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 + } + xds: { + core: { + v3: { + Authority: MessageTypeDefinition + } + } + } +} + From 365b37919317b8db1dc576b4957ebf96049a34b4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 14:59:59 -0700 Subject: [PATCH 057/694] grpc-js-xds: Factor Fraction out into a separate file --- packages/grpc-js-xds/src/fraction.ts | 39 ++++++++++++++++++++++++ packages/grpc-js-xds/src/matcher.ts | 10 +----- packages/grpc-js-xds/src/resolver-xds.ts | 14 ++------- 3 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 packages/grpc-js-xds/src/fraction.ts diff --git a/packages/grpc-js-xds/src/fraction.ts b/packages/grpc-js-xds/src/fraction.ts new file mode 100644 index 000000000..709af72b2 --- /dev/null +++ b/packages/grpc-js-xds/src/fraction.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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 { FractionalPercent__Output } from "./generated/envoy/type/v3/FractionalPercent"; + +export interface Fraction { + numerator: number; + denominator: number; +} + +export function fractionToString(fraction: Fraction): string { + return `${fraction.numerator}/${fraction.denominator}`; +} + +const RUNTIME_FRACTION_DENOMINATOR_VALUES = { + HUNDRED: 100, + TEN_THOUSAND: 10_000, + MILLION: 1_000_000 +} + +export function envoyFractionToFraction(envoyFraction: FractionalPercent__Output): Fraction { + return { + numerator: envoyFraction.numerator, + denominator: RUNTIME_FRACTION_DENOMINATOR_VALUES[envoyFraction.denominator] + }; +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/matcher.ts b/packages/grpc-js-xds/src/matcher.ts index 14cb7f672..b173c30e0 100644 --- a/packages/grpc-js-xds/src/matcher.ts +++ b/packages/grpc-js-xds/src/matcher.ts @@ -16,6 +16,7 @@ import { Metadata } from "@grpc/grpc-js"; import { RE2 } from "re2-wasm"; +import { Fraction, fractionToString } from "./fraction"; /** * An object representing a predicate that determines whether a given @@ -210,15 +211,6 @@ export class PathSafeRegexValueMatcher { } } -export interface Fraction { - numerator: number; - denominator: number; -} - -function fractionToString(fraction: Fraction): string { - return `${fraction.numerator}/${fraction.denominator}`; -} - export class FullMatcher implements Matcher { constructor(private pathMatcher: ValueMatcher, private headerMatchers: Matcher[], private fraction: Fraction | null) {} diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 3eaabb111..3712ad647 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -37,7 +37,8 @@ import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderM import ConfigSelector = experimental.ConfigSelector; import LoadBalancingConfig = experimental.LoadBalancingConfig; import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluster-manager'; -import { ExactValueMatcher, Fraction, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; +import { ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; +import { envoyFractionToFraction, Fraction } from "./fraction"; import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from './resources'; import Duration = experimental.Duration; @@ -158,12 +159,6 @@ function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Match return new HeaderMatcher(headerMatch.name, valueChecker, headerMatch.invert_match); } -const RUNTIME_FRACTION_DENOMINATOR_VALUES = { - HUNDRED: 100, - TEN_THOUSAND: 10_000, - MILLION: 1_000_000 -} - function getPredicateForMatcher(routeMatch: RouteMatch__Output): Matcher { let pathMatcher: ValueMatcher; const caseInsensitive = routeMatch.case_sensitive?.value === false; @@ -185,10 +180,7 @@ function getPredicateForMatcher(routeMatch: RouteMatch__Output): Matcher { if (!routeMatch.runtime_fraction?.default_value) { runtimeFraction = null; } else { - runtimeFraction = { - numerator: routeMatch.runtime_fraction.default_value.numerator, - denominator: RUNTIME_FRACTION_DENOMINATOR_VALUES[routeMatch.runtime_fraction.default_value.denominator] - }; + runtimeFraction = envoyFractionToFraction(routeMatch.runtime_fraction.default_value) } return new FullMatcher(pathMatcher, headerMatchers, runtimeFraction); } From f216ecef00f925d46a3796e7ca3c91d9ce547385 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 15:06:47 -0700 Subject: [PATCH 058/694] grpc-js-xds: Add null checks to handle generated code changes --- packages/grpc-js-xds/src/resolver-xds.ts | 2 +- packages/grpc-js-xds/src/xds-stream-state/rds-state.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 3712ad647..f6287ea67 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -228,7 +228,7 @@ class XdsResolver implements Resolver { onValidUpdate: (update: Listener__Output) => { const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, update.api_listener!.api_listener!.value); const defaultTimeout = httpConnectionManager.common_http_protocol_options?.idle_timeout; - if (defaultTimeout === undefined) { + if (defaultTimeout === null || defaultTimeout === undefined) { this.latestDefaultTimeout = undefined; } else { this.latestDefaultTimeout = protoDurationToDuration(defaultTimeout); diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index f04692859..58f11f0b4 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -137,7 +137,7 @@ export class RdsState implements XdsStreamState { if (route.action !== 'route') { return false; } - if ((route.route === undefined) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { + if ((route.route === undefined) || (route.route === null) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { return false; } if (EXPERIMENTAL_FAULT_INJECTION) { From e1b0d62e9bcf5f0aba88ac2207459afba43bbec7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 15:07:42 -0700 Subject: [PATCH 059/694] grpc-js-xds: Add fault injection file to type generator script --- packages/grpc-js-xds/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index eb8e3e03e..6506112c4 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/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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 envoy/extensions/filters/http/fault/v3/fault.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" }, "repository": { From b5fd5b033ee6e876e4bc0304ff8c31a8bfd0b8a4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 15:08:10 -0700 Subject: [PATCH 060/694] grpc-js-xds: Add fault injection HTTP filter --- .../src/http-filter/fault-injection-filter.ts | 333 ++++++++++++++++++ packages/grpc-js-xds/src/index.ts | 2 + 2 files changed, 335 insertions(+) create mode 100644 packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts diff --git a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts new file mode 100644 index 000000000..825b7c058 --- /dev/null +++ b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts @@ -0,0 +1,333 @@ +/* + * Copyright 2021 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. + */ + +// This is a non-public, unstable API, but it's very convenient +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { experimental, Metadata, status } from '@grpc/grpc-js'; +import { Any__Output } from '../generated/google/protobuf/Any'; +import Filter = experimental.Filter; +import FilterFactory = experimental.FilterFactory; +import BaseFilter = experimental.BaseFilter; +import CallStream = experimental.CallStream; +import { HttpFilterConfig, registerHttpFilter } from '../http-filter'; +import { HTTPFault__Output } from '../generated/envoy/extensions/filters/http/fault/v3/HTTPFault'; +import { envoyFractionToFraction, Fraction } from '../fraction'; +import { Duration__Output } from '../generated/google/protobuf/Duration'; + +const resourceRoot = loadProtosWithOptionsSync([ + 'envoy/extendsion/filtesr/http/fault/v3/fault.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/udpa/', + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/protoc-gen-validate/' + ], + } +); + +interface FixedDelayConfig { + kind: 'fixed'; + durationMs: number; + percentage: Fraction; +} + +interface HeaderDelayConfig { + kind: 'header'; + percentage: Fraction; +} + +interface GrpcAbortConfig { + kind: 'grpc'; + code: status; + percentage: Fraction; +} + +interface HeaderAbortConfig { + kind: 'header'; + percentage: Fraction; +} + +interface FaultInjectionConfig { + delay: FixedDelayConfig | HeaderDelayConfig | null; + abort: GrpcAbortConfig | HeaderAbortConfig | null; + maxActiveFaults: number; +} + +interface FaultInjectionFilterConfig extends HttpFilterConfig { + typeUrl: 'type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault'; + config: FaultInjectionConfig; +} + +const FAULT_INJECTION_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault'; + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +function parseAnyMessage(message: Any__Output): MessageType | null { + const messageType = resourceRoot.lookup(message.type_url); + if (messageType) { + const decodedMessage = (messageType as any).decode(message.value); + return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType; + } else { + return null; + } +} + +function durationToMs(duration: Duration__Output): number { + return Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000; +} + +function httpCodeToGrpcStatus(code: number): status { + switch (code) { + case 400: return status.INTERNAL; + case 401: return status.UNAUTHENTICATED; + case 403: return status.PERMISSION_DENIED; + case 404: return status.UNIMPLEMENTED; + case 429: return status.UNAVAILABLE; + case 502: return status.UNAVAILABLE; + case 503: return status.UNAVAILABLE; + case 504: return status.UNAVAILABLE; + default: return status.UNKNOWN; + } +} + +function parseHTTPFaultConfig(encodedConfig: Any__Output): FaultInjectionFilterConfig | null { + if (encodedConfig.type_url !== FAULT_INJECTION_FILTER_URL) { + return null; + } + const parsedMessage = parseAnyMessage(encodedConfig); + if (parsedMessage === null) { + return null; + } + const result: FaultInjectionConfig = { + delay: null, + abort: null, + maxActiveFaults: Infinity + }; + // Parse delay field + if (parsedMessage.delay !== null) { + if (parsedMessage.delay.percentage === null) { + return null; + } + const percentage = envoyFractionToFraction(parsedMessage.delay.percentage); + switch (parsedMessage.delay.fault_delay_secifier /* sic */) { + case 'fixed_delay': + result.delay = { + kind: 'fixed', + durationMs: durationToMs(parsedMessage.delay.fixed_delay!), + percentage: percentage + }; + break; + case 'header_delay': + result.delay = { + kind: 'header', + percentage: percentage + }; + break; + default: + // Should not be possible + return null; + } + } + // Parse abort field + if (parsedMessage.abort !== null) { + if (parsedMessage.abort.percentage === null) { + return null; + } + const percentage = envoyFractionToFraction(parsedMessage.abort.percentage); + switch (parsedMessage.abort.error_type) { + case 'http_status': + result.abort = { + kind: 'grpc', + code: httpCodeToGrpcStatus(parsedMessage.abort.http_status!), + percentage: percentage + }; + break; + case 'grpc_status': + result.abort = { + kind: 'grpc', + code: parsedMessage.abort.grpc_status!, + percentage: percentage + } + break; + case 'header_abort': + result.abort = { + kind: 'header', + percentage: percentage + }; + break; + default: + // Should not be possible + return null; + } + } + // Parse max_active_faults field + if (parsedMessage.max_active_faults !== null) { + result.maxActiveFaults = parsedMessage.max_active_faults.value; + } + return { + typeUrl: FAULT_INJECTION_FILTER_URL, + config: result + }; +} + +function asyncTimeout(timeMs: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, timeMs); + }); +} + +/** + * Returns true with probability numerator/denominator. + * @param numerator + * @param denominator + */ +function rollRandomPercentage(numerator: number, denominator: number): boolean { + return Math.random() * denominator < numerator; +} + +const DELAY_DURATION_HEADER_KEY = 'x-envoy-fault-delay-request'; +const DELAY_PERCENTAGE_HEADER_KEY = 'x-envoy-fault-delay-request-percentage'; +const ABORT_GRPC_HEADER_KEY = 'x-envoy-fault-abort-grpc-request'; +const ABORT_HTTP_HEADER_KEY = 'x-envoy-fault-abort-request'; +const ABORT_PERCENTAGE_HEADER_KEY = 'x-envoy-fault-abort-request-percentage'; + +const NUMBER_REGEX = /\d+/; + +let totalActiveFaults = 0; + +class FaultInjectionFilter extends BaseFilter implements Filter { + constructor(private callStream: CallStream, private config: FaultInjectionConfig) { + super(); + } + + async sendMetadata(metadataPromise: Promise): Promise { + const metadata = await metadataPromise; + // Handle delay + if (totalActiveFaults < this.config.maxActiveFaults && this.config.delay) { + let duration = 0; + let numerator = this.config.delay.percentage.numerator; + const denominator = this.config.delay.percentage.denominator; + if (this.config.delay.kind === 'fixed') { + duration = this.config.delay.durationMs; + } else { + const durationHeader = metadata.get(DELAY_DURATION_HEADER_KEY); + for (const value of durationHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + duration = Number.parseInt(value); + break; + } + } + const percentageHeader = metadata.get(DELAY_PERCENTAGE_HEADER_KEY); + for (const value of percentageHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + numerator = Math.min(numerator, Number.parseInt(value)); + break; + } + } + } + if (rollRandomPercentage(numerator, denominator)) { + totalActiveFaults++; + await asyncTimeout(duration); + totalActiveFaults--; + } + } + // Handle abort + if (totalActiveFaults < this.config.maxActiveFaults && this.config.abort) { + let abortStatus: status | null = null; + let numerator = this.config.abort.percentage.numerator; + const denominator = this.config.abort.percentage.denominator; + if (this.config.abort.kind === 'grpc') { + abortStatus = this.config.abort.code; + } else { + const grpcStatusHeader = metadata.get(ABORT_GRPC_HEADER_KEY); + for (const value of grpcStatusHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + abortStatus = Number.parseInt(value); + break; + } + } + /* Fall back to looking for HTTP status header if the gRPC status + * header is not present. */ + if (abortStatus === null) { + const httpStatusHeader = metadata.get(ABORT_HTTP_HEADER_KEY); + for (const value of httpStatusHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + abortStatus = httpCodeToGrpcStatus(Number.parseInt(value)); + break; + } + } + } + const percentageHeader = metadata.get(ABORT_PERCENTAGE_HEADER_KEY); + for (const value of percentageHeader) { + if (typeof value !== 'string') { + continue; + } + if (NUMBER_REGEX.test(value)) { + numerator = Math.min(numerator, Number.parseInt(value)); + break; + } + } + } + if (abortStatus !== null && rollRandomPercentage(numerator, denominator)) { + this.callStream.cancelWithStatus(abortStatus, 'Fault injected'); + } + } + return metadata; + } +} + +class FaultInjectionFilterFactory implements FilterFactory { + private config: FaultInjectionConfig; + constructor(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig) { + if (overrideConfig?.typeUrl === FAULT_INJECTION_FILTER_URL) { + this.config = overrideConfig.config; + } else { + this.config = config.config; + } + } + + createFilter(callStream: experimental.CallStream): FaultInjectionFilter { + return new FaultInjectionFilter(callStream, this.config); + } +} + +export function setup() { + registerHttpFilter(FAULT_INJECTION_FILTER_URL, { + parseTopLevelFilterConfig: parseHTTPFaultConfig, + parseOverrideFilterConfig: parseHTTPFaultConfig, + httpFilterConstructor: FaultInjectionFilterFactory + }); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 2fee339d1..7d35bcd1e 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -23,6 +23,7 @@ import * as load_balancer_priority from './load-balancer-priority'; import * as load_balancer_weighted_target from './load-balancer-weighted-target'; import * as load_balancer_xds_cluster_manager from './load-balancer-xds-cluster-manager'; import * as router_filter from './http-filter/router-filter'; +import * as fault_injection_filter from './http-filter/fault-injection-filter'; /** * Register the "xds:" name scheme with the @grpc/grpc-js library. @@ -36,4 +37,5 @@ export function register() { load_balancer_weighted_target.setup(); load_balancer_xds_cluster_manager.setup(); router_filter.setup(); + fault_injection_filter.setup(); } \ No newline at end of file From 36c6add3a799cb37a11d9da9cfb8ea017c58cf63 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 15:11:52 -0700 Subject: [PATCH 061/694] grpc-js-xds: Enable fault_injection xDS interop test --- packages/grpc-js-xds/scripts/xds.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 66fe4d568..f845abfb0 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -51,8 +51,9 @@ grpc/tools/run_tests/helper_scripts/prep_xds.sh GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver \ GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ + GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ - --test_case="all,timeout,circuit_breaking" \ + --test_case="all,timeout,circuit_breaking,fault_injection" \ --project_id=grpc-testing \ --source_image=projects/grpc-testing/global/images/xds-test-server-4 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ From a0baf7c99ab37ed651a576921776a98b91284656 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 4 Aug 2021 11:54:21 -0700 Subject: [PATCH 062/694] Fix bugs and add tracing --- packages/grpc-js-xds/scripts/xds.sh | 2 +- packages/grpc-js-xds/src/http-filter.ts | 30 ++++++++++++++++--- .../src/http-filter/fault-injection-filter.ts | 28 ++++++++++++----- .../src/xds-stream-state/lds-state.ts | 11 +++++-- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index f845abfb0..8d94ae195 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -48,7 +48,7 @@ git clone -b master --single-branch --depth=1 https://github.com/grpc/grpc.git grpc/tools/run_tests/helper_scripts/prep_xds.sh -GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver \ +GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter \ GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION=1 \ diff --git a/packages/grpc-js-xds/src/http-filter.ts b/packages/grpc-js-xds/src/http-filter.ts index e7ea32957..a00cde5db 100644 --- a/packages/grpc-js-xds/src/http-filter.ts +++ b/packages/grpc-js-xds/src/http-filter.ts @@ -16,7 +16,7 @@ // This is a non-public, unstable API, but it's very convenient import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; -import { experimental } from '@grpc/grpc-js'; +import { experimental, logVerbosity } from '@grpc/grpc-js'; import { Any__Output } from './generated/google/protobuf/Any'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; @@ -24,6 +24,12 @@ import { TypedStruct__Output } from './generated/udpa/type/v1/TypedStruct'; import { FilterConfig__Output } from './generated/envoy/config/route/v3/FilterConfig'; import { HttpFilter__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter'; +const TRACER_NAME = 'http_filter'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + const TYPED_STRUCT_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct'; const TYPED_STRUCT_NAME = 'udpa.type.v1.TypedStruct'; @@ -37,7 +43,8 @@ const resourceRoot = loadProtosWithOptionsSync([ includeDirs: [ // Paths are relative to src/build __dirname + '/../../deps/udpa/', - __dirname + '/../../deps/envoy-api/' + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/protoc-gen-validate/' ], } ); @@ -60,6 +67,7 @@ export interface HttpFilterRegistryEntry { const FILTER_REGISTRY = new Map(); export function registerHttpFilter(typeName: string, entry: HttpFilterRegistryEntry) { + trace('Registered filter with type URL ' + typeName); FILTER_REGISTRY.set(typeName, entry); } @@ -71,7 +79,8 @@ const toObjectOptions = { } function parseAnyMessage(message: Any__Output): MessageType | null { - const messageType = resourceRoot.lookup(message.type_url); + const typeName = message.type_url.substring(message.type_url.lastIndexOf('/') + 1); + const messageType = resourceRoot.lookup(typeName); if (messageType) { const decodedMessage = (messageType as any).decode(message.value); return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType; @@ -80,7 +89,7 @@ function parseAnyMessage(message: Any__Output): MessageType | null } } -function getTopLevelFilterUrl(encodedConfig: Any__Output): string { +export function getTopLevelFilterUrl(encodedConfig: Any__Output): string { let typeUrl: string; if (encodedConfig.type_url === TYPED_STRUCT_URL) { const typedStruct = parseAnyMessage(encodedConfig) @@ -96,6 +105,7 @@ function getTopLevelFilterUrl(encodedConfig: Any__Output): string { export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean { if (!httpFilter.typed_config) { + trace(httpFilter.name + ' validation failed: typed_config unset'); return false; } const encodedConfig = httpFilter.typed_config; @@ -103,16 +113,21 @@ export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean try { typeUrl = getTopLevelFilterUrl(encodedConfig); } catch (e) { + trace(httpFilter.name + ' validation failed with error ' + e.message); return false; } const registryEntry = FILTER_REGISTRY.get(typeUrl); if (registryEntry) { const parsedConfig = registryEntry.parseTopLevelFilterConfig(encodedConfig); + if (parsedConfig === null) { + trace(httpFilter.name + ' validation failed: config parsing failed'); + } return parsedConfig !== null; } else { if (httpFilter.is_optional) { return true; } else { + trace(httpFilter.name + ' validation failed: filter is not optional and registry does not contain type URL ' + typeUrl); return false; } } @@ -129,9 +144,11 @@ export function validateOverrideFilter(encodedConfig: Any__Output): boolean { if (filterConfig.config) { realConfig = filterConfig.config; } else { + trace('Override filter validation failed: FilterConfig config field is empty'); return false; } } else { + trace('Override filter validation failed: failed to parse FilterConfig message'); return false; } } else { @@ -142,6 +159,7 @@ export function validateOverrideFilter(encodedConfig: Any__Output): boolean { if (typedStruct) { typeUrl = typedStruct.type_url; } else { + trace('Override filter validation failed: failed to parse TypedStruct message'); return false; } } else { @@ -150,11 +168,15 @@ export function validateOverrideFilter(encodedConfig: Any__Output): boolean { const registryEntry = FILTER_REGISTRY.get(typeUrl); if (registryEntry) { const parsedConfig = registryEntry.parseOverrideFilterConfig(encodedConfig); + if (parsedConfig === null) { + trace('Override filter validation failed: config parsing failed. Type URL: ' + typeUrl); + } return parsedConfig !== null; } else { if (isOptional) { return true; } else { + trace('Override filter validation failed: filter is not optional and registry does not contain type URL ' + typeUrl); return false; } } diff --git a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts index 825b7c058..e0ee658c7 100644 --- a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts +++ b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts @@ -16,7 +16,7 @@ // This is a non-public, unstable API, but it's very convenient import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; -import { experimental, Metadata, status } from '@grpc/grpc-js'; +import { experimental, logVerbosity, Metadata, status } from '@grpc/grpc-js'; import { Any__Output } from '../generated/google/protobuf/Any'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; @@ -27,14 +27,20 @@ import { HTTPFault__Output } from '../generated/envoy/extensions/filters/http/fa import { envoyFractionToFraction, Fraction } from '../fraction'; import { Duration__Output } from '../generated/google/protobuf/Duration'; +const TRACER_NAME = 'fault_injection'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + const resourceRoot = loadProtosWithOptionsSync([ - 'envoy/extendsion/filtesr/http/fault/v3/fault.proto'], { + 'envoy/extensions/filters/http/fault/v3/fault.proto'], { keepCase: true, includeDirs: [ - // Paths are relative to src/build - __dirname + '/../../deps/udpa/', - __dirname + '/../../deps/envoy-api/', - __dirname + '/../../deps/protoc-gen-validate/' + // Paths are relative to src/build/http-filter + __dirname + '/../../../deps/udpa/', + __dirname + '/../../../deps/envoy-api/', + __dirname + '/../../../deps/protoc-gen-validate/' ], } ); @@ -82,7 +88,8 @@ const toObjectOptions = { } function parseAnyMessage(message: Any__Output): MessageType | null { - const messageType = resourceRoot.lookup(message.type_url); + const typeName = message.type_url.substring(message.type_url.lastIndexOf('/') + 1); + const messageType = resourceRoot.lookup(typeName); if (messageType) { const decodedMessage = (messageType as any).decode(message.value); return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType; @@ -111,12 +118,15 @@ function httpCodeToGrpcStatus(code: number): status { function parseHTTPFaultConfig(encodedConfig: Any__Output): FaultInjectionFilterConfig | null { if (encodedConfig.type_url !== FAULT_INJECTION_FILTER_URL) { + trace('Config parsing failed: unexpected type URL: ' + encodedConfig.type_url); return null; } const parsedMessage = parseAnyMessage(encodedConfig); if (parsedMessage === null) { + trace('Config parsing failed: failed to parse HTTPFault message'); return null; } + trace('Parsing HTTPFault message ' + JSON.stringify(parsedMessage, undefined, 2)); const result: FaultInjectionConfig = { delay: null, abort: null, @@ -125,6 +135,7 @@ function parseHTTPFaultConfig(encodedConfig: Any__Output): FaultInjectionFilterC // Parse delay field if (parsedMessage.delay !== null) { if (parsedMessage.delay.percentage === null) { + trace('Config parsing failed: delay.percentage unset'); return null; } const percentage = envoyFractionToFraction(parsedMessage.delay.percentage); @@ -143,6 +154,7 @@ function parseHTTPFaultConfig(encodedConfig: Any__Output): FaultInjectionFilterC }; break; default: + trace('Config parsing failed: delay.fault_delay_secifier has unexpected value ' + parsedMessage.delay.fault_delay_secifier); // Should not be possible return null; } @@ -150,6 +162,7 @@ function parseHTTPFaultConfig(encodedConfig: Any__Output): FaultInjectionFilterC // Parse abort field if (parsedMessage.abort !== null) { if (parsedMessage.abort.percentage === null) { + trace('Config parsing failed: abort.percentage unset'); return null; } const percentage = envoyFractionToFraction(parsedMessage.abort.percentage); @@ -175,6 +188,7 @@ function parseHTTPFaultConfig(encodedConfig: Any__Output): FaultInjectionFilterC }; break; default: + trace('Config parsing failed: abort.error_type has unexpected value ' + parsedMessage.abort.error_type); // Should not be possible return null; } diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 3efc64b92..8ab6ed9d2 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -22,7 +22,7 @@ import { RdsState } from "./rds-state"; import { Watcher, XdsStreamState } from "./xds-stream-state"; import { HttpConnectionManager__Output } from '../generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V2, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from '../resources'; -import { validateTopLevelFilter } from '../http-filter'; +import { getTopLevelFilterUrl, validateTopLevelFilter } from '../http-filter'; import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; const TRACER_NAME = 'xds_client'; @@ -108,20 +108,25 @@ export class LdsState implements XdsStreamState { const filterNames = new Set(); for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { if (filterNames.has(httpFilter.name)) { + trace('LDS response validation failed: duplicate HTTP filter name ' + httpFilter.name); return false; } filterNames.add(httpFilter.name); if (!validateTopLevelFilter(httpFilter)) { + trace('LDS response validation failed: ' + httpFilter.name + ' filter validation failed'); return false; } /* Validate that the last filter, and only the last filter, is the * router filter. */ + const filterUrl = getTopLevelFilterUrl(httpFilter.typed_config!) if (index < httpConnectionManager.http_filters.length - 1) { - if (httpFilter.name === ROUTER_FILTER_URL) { + if (filterUrl === ROUTER_FILTER_URL) { + trace('LDS response validation failed: router filter is before end of list'); return false; } } else { - if (httpFilter.name !== ROUTER_FILTER_URL) { + if (filterUrl !== ROUTER_FILTER_URL) { + trace('LDS response validation failed: final filter is ' + filterUrl); return false; } } From cd50baed558ff8697113d4fadfd4842af3665111 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 10 Aug 2021 09:48:56 -0700 Subject: [PATCH 063/694] Add .gitattributes marking generated code in xDS package --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f0957c311 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +packages/grpc-js-xds/src/generated/** linguist-generated +packages/grpc-js-xds/interop/generated/** linguist-generated From 875f483cf6fd5a6b67cafa96dccbef6f4f24a87b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 10 Aug 2021 14:20:11 -0700 Subject: [PATCH 064/694] grpc-js: Map ETIMEDOUT errors to UNAVAILABLE --- packages/grpc-js/src/call-stream.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 5e034433a..c829f4229 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -601,7 +601,7 @@ export class Http2CallStream implements Call { * "Internal server error" message. */ details = `Received RST_STREAM with code ${stream.rstCode} (Internal server error)`; } else { - if (this.internalError.code === 'ECONNRESET') { + if (this.internalError.code === 'ECONNRESET' || this.internalError.code === 'ETIMEDOUT') { code = Status.UNAVAILABLE; details = this.internalError.message; } else { From 64a0b0ad7cd28e73e1e28ecad32437652f1bcc60 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 17 Aug 2021 14:59:22 -0700 Subject: [PATCH 065/694] grpc-js-xds: Distinguish v2 and v3 when handling messages --- packages/grpc-js-xds/src/resolver-xds.ts | 24 ++++++++++--------- packages/grpc-js-xds/src/xds-client.ts | 23 ++++++++++++++---- .../src/xds-stream-state/cds-state.ts | 9 ++++--- .../src/xds-stream-state/eds-state.ts | 9 ++++--- .../src/xds-stream-state/lds-state.ts | 17 +++++++------ .../src/xds-stream-state/rds-state.ts | 19 ++++++++------- .../src/xds-stream-state/xds-stream-state.ts | 9 +++++-- 7 files changed, 72 insertions(+), 38 deletions(-) diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index f6287ea67..ab036184e 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -212,6 +212,7 @@ class XdsResolver implements Resolver { private latestRouteConfigName: string | null = null; private latestRouteConfig: RouteConfiguration__Output | null = null; + private latestRouteConfigIsV2 = false; private clusterRefcounts = new Map(); @@ -225,7 +226,7 @@ class XdsResolver implements Resolver { private channelOptions: ChannelOptions ) { this.ldsWatcher = { - onValidUpdate: (update: Listener__Output) => { + onValidUpdate: (update: Listener__Output, isV2: boolean) => { const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, update.api_listener!.api_listener!.value); const defaultTimeout = httpConnectionManager.common_http_protocol_options?.idle_timeout; if (defaultTimeout === null || defaultTimeout === undefined) { @@ -233,7 +234,7 @@ class XdsResolver implements Resolver { } else { this.latestDefaultTimeout = protoDurationToDuration(defaultTimeout); } - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { this.ldsHttpFilterConfigs = []; for (const filter of httpConnectionManager.http_filters) { // typed_config must be set here, or validation would have failed @@ -259,7 +260,7 @@ class XdsResolver implements Resolver { if (this.latestRouteConfigName) { getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } - this.handleRouteConfig(httpConnectionManager.route_config!); + this.handleRouteConfig(httpConnectionManager.route_config!, isV2); break; default: // This is prevented by the validation rules @@ -279,8 +280,8 @@ class XdsResolver implements Resolver { } }; this.rdsWatcher = { - onValidUpdate: (update: RouteConfiguration__Output) => { - this.handleRouteConfig(update); + onValidUpdate: (update: RouteConfiguration__Output, isV2: boolean) => { + this.handleRouteConfig(update, isV2); }, onTransientError: (error: StatusObject) => { /* A transient error only needs to bubble up as a failure if we have @@ -310,20 +311,21 @@ class XdsResolver implements Resolver { refCount.refCount -= 1; if (!refCount.inLastConfig && refCount.refCount === 0) { this.clusterRefcounts.delete(clusterName); - this.handleRouteConfig(this.latestRouteConfig!); + this.handleRouteConfig(this.latestRouteConfig!, this.latestRouteConfigIsV2); } } } - private handleRouteConfig(routeConfig: RouteConfiguration__Output) { + private handleRouteConfig(routeConfig: RouteConfiguration__Output, isV2: boolean) { this.latestRouteConfig = routeConfig; + this.latestRouteConfigIsV2 = isV2; const virtualHost = findVirtualHostForDomain(routeConfig.virtual_hosts, this.target.path); if (virtualHost === null) { this.reportResolutionError('No matching route found'); return; } const virtualHostHttpFilterOverrides = new Map(); - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filter] of Object.entries(virtualHost.typed_per_filter_config ?? {})) { const parsedConfig = parseOverrideFilterConfig(filter); if (parsedConfig) { @@ -352,7 +354,7 @@ class XdsResolver implements Resolver { timeout = undefined; } const routeHttpFilterOverrides = new Map(); - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filter] of Object.entries(route.typed_per_filter_config ?? {})) { const parsedConfig = parseOverrideFilterConfig(filter); if (parsedConfig) { @@ -367,7 +369,7 @@ class XdsResolver implements Resolver { const cluster = route.route!.cluster!; allConfigClusters.add(cluster); const extraFilterFactories: FilterFactory[] = []; - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { for (const filterConfig of this.ldsHttpFilterConfigs) { if (routeHttpFilterOverrides.has(filterConfig.name)) { const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!); @@ -396,7 +398,7 @@ class XdsResolver implements Resolver { allConfigClusters.add(clusterWeight.name); const extraFilterFactories: FilterFactory[] = []; const clusterHttpFilterOverrides = new Map(); - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filter] of Object.entries(clusterWeight.typed_per_filter_config ?? {})) { const parsedConfig = parseOverrideFilterConfig(filter); if (parsedConfig) { diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 6828a8a8e..952ae81f4 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -435,32 +435,47 @@ export class XdsClient { private handleAdsResponse(message: DiscoveryResponse__Output) { let errorString: string | null; let serviceKind: AdsServiceKind; + let isV2: boolean; + switch (message.type_url) { + case EDS_TYPE_URL_V2: + case CDS_TYPE_URL_V2: + case RDS_TYPE_URL_V2: + case LDS_TYPE_URL_V2: + isV2 = true; + break; + default: + isV2 = false; + } switch (message.type_url) { case EDS_TYPE_URL_V2: case EDS_TYPE_URL_V3: errorString = this.adsState.eds.handleResponses( - getResponseMessages(EDS_TYPE_URL_V3, [EDS_TYPE_URL_V2, EDS_TYPE_URL_V3], message.resources) + getResponseMessages(EDS_TYPE_URL_V3, [EDS_TYPE_URL_V2, EDS_TYPE_URL_V3], message.resources), + isV2 ); serviceKind = 'eds'; break; case CDS_TYPE_URL_V2: case CDS_TYPE_URL_V3: errorString = this.adsState.cds.handleResponses( - getResponseMessages(CDS_TYPE_URL_V3, [CDS_TYPE_URL_V2, CDS_TYPE_URL_V3], message.resources) + getResponseMessages(CDS_TYPE_URL_V3, [CDS_TYPE_URL_V2, CDS_TYPE_URL_V3], message.resources), + isV2 ); serviceKind = 'cds'; break; case RDS_TYPE_URL_V2: case RDS_TYPE_URL_V3: errorString = this.adsState.rds.handleResponses( - getResponseMessages(RDS_TYPE_URL_V3, [RDS_TYPE_URL_V2, RDS_TYPE_URL_V3], message.resources) + getResponseMessages(RDS_TYPE_URL_V3, [RDS_TYPE_URL_V2, RDS_TYPE_URL_V3], message.resources), + isV2 ); serviceKind = 'rds'; break; case LDS_TYPE_URL_V2: case LDS_TYPE_URL_V3: errorString = this.adsState.lds.handleResponses( - getResponseMessages(LDS_TYPE_URL_V3, [LDS_TYPE_URL_V2, LDS_TYPE_URL_V3], message.resources) + getResponseMessages(LDS_TYPE_URL_V3, [LDS_TYPE_URL_V2, LDS_TYPE_URL_V3], message.resources), + isV2 ); serviceKind = 'lds'; break; diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index a6e89805d..7720c5673 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -36,6 +36,7 @@ export class CdsState implements XdsStreamState { >(); private latestResponses: Cluster__Output[] = []; + private latestIsV2 = false; constructor( private edsState: EdsState, @@ -61,13 +62,14 @@ export class CdsState implements XdsStreamState { /* If we have already received an update for the requested edsServiceName, * immediately pass that update along to the watcher */ + const isV2 = this.latestIsV2; for (const message of this.latestResponses) { if (message.name === clusterName) { /* These updates normally occur asynchronously, so we ensure that * the same happens here */ process.nextTick(() => { trace('Reporting existing CDS update for new watcher for clusterName ' + clusterName); - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); }); } } @@ -134,7 +136,7 @@ export class CdsState implements XdsStreamState { } } - handleResponses(responses: Cluster__Output[]): string | null { + handleResponses(responses: Cluster__Output[], isV2: boolean): string | null { for (const message of responses) { if (!this.validateResponse(message)) { trace('CDS validation failed for message ' + JSON.stringify(message)); @@ -142,6 +144,7 @@ export class CdsState implements XdsStreamState { } } this.latestResponses = responses; + this.latestIsV2 = isV2; const allEdsServiceNames: Set = new Set(); const allClusterNames: Set = new Set(); for (const message of responses) { @@ -152,7 +155,7 @@ export class CdsState implements XdsStreamState { ); const watchers = this.watchers.get(message.name) ?? []; for (const watcher of watchers) { - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); } } trace('Received CDS updates for cluster names ' + Array.from(allClusterNames)); diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index a0fb5f4dc..dbf18ef81 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -36,6 +36,7 @@ export class EdsState implements XdsStreamState { > = new Map[]>(); private latestResponses: ClusterLoadAssignment__Output[] = []; + private latestIsV2 = false; constructor(private updateResourceNames: () => void) {} @@ -61,13 +62,14 @@ export class EdsState implements XdsStreamState { /* If we have already received an update for the requested edsServiceName, * immediately pass that update along to the watcher */ + const isV2 = this.latestIsV2; for (const message of this.latestResponses) { if (message.cluster_name === edsServiceName) { /* These updates normally occur asynchronously, so we ensure that * the same happens here */ process.nextTick(() => { trace('Reporting existing EDS update for new watcher for edsServiceName ' + edsServiceName); - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); }); } } @@ -143,7 +145,7 @@ export class EdsState implements XdsStreamState { } } - handleResponses(responses: ClusterLoadAssignment__Output[]) { + handleResponses(responses: ClusterLoadAssignment__Output[], isV2: boolean) { for (const message of responses) { if (!this.validateResponse(message)) { trace('EDS validation failed for message ' + JSON.stringify(message)); @@ -151,12 +153,13 @@ export class EdsState implements XdsStreamState { } } this.latestResponses = responses; + this.latestIsV2 = isV2; const allClusterNames: Set = new Set(); for (const message of responses) { allClusterNames.add(message.cluster_name); const watchers = this.watchers.get(message.cluster_name) ?? []; for (const watcher of watchers) { - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); } } trace('Received EDS updates for cluster names ' + Array.from(allClusterNames)); diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 8ab6ed9d2..7e8ec0456 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -39,6 +39,7 @@ export class LdsState implements XdsStreamState { private watchers: Map[]> = new Map[]>(); private latestResponses: Listener__Output[] = []; + private latestIsV2 = false; constructor(private rdsState: RdsState, private updateResourceNames: () => void) {} @@ -55,13 +56,14 @@ export class LdsState implements XdsStreamState { /* If we have already received an update for the requested edsServiceName, * immediately pass that update along to the watcher */ + const isV2 = this.latestIsV2; for (const message of this.latestResponses) { if (message.name === targetName) { /* These updates normally occur asynchronously, so we ensure that * the same happens here */ process.nextTick(() => { trace('Reporting existing RDS update for new watcher for targetName ' + targetName); - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); }); } } @@ -93,7 +95,7 @@ export class LdsState implements XdsStreamState { return Array.from(this.watchers.keys()); } - private validateResponse(message: Listener__Output): boolean { + private validateResponse(message: Listener__Output, isV2: boolean): boolean { if ( !( message.api_listener?.api_listener && @@ -104,7 +106,7 @@ export class LdsState implements XdsStreamState { return false; } const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener.value); - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { const filterNames = new Set(); for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { if (filterNames.has(httpFilter.name)) { @@ -136,7 +138,7 @@ export class LdsState implements XdsStreamState { case 'rds': return !!httpConnectionManager.rds?.config_source?.ads; case 'route_config': - return this.rdsState.validateResponse(httpConnectionManager.route_config!); + return this.rdsState.validateResponse(httpConnectionManager.route_config!, isV2); } return false; } @@ -151,20 +153,21 @@ export class LdsState implements XdsStreamState { } } - handleResponses(responses: Listener__Output[]): string | null { + handleResponses(responses: Listener__Output[], isV2: boolean): string | null { for (const message of responses) { - if (!this.validateResponse(message)) { + if (!this.validateResponse(message, isV2)) { trace('LDS validation failed for message ' + JSON.stringify(message)); return 'LDS Error: Route validation failed'; } } this.latestResponses = responses; + this.latestIsV2 = isV2; const allTargetNames = new Set(); for (const message of responses) { allTargetNames.add(message.name); const watchers = this.watchers.get(message.name) ?? []; for (const watcher of watchers) { - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); } } trace('Received RDS response with route config names ' + Array.from(allTargetNames)); diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 58f11f0b4..9194529d8 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -45,6 +45,7 @@ export class RdsState implements XdsStreamState { private watchers: Map[]> = new Map[]>(); private latestResponses: RouteConfiguration__Output[] = []; + private latestIsV2 = false; constructor(private updateResourceNames: () => void) {} @@ -61,13 +62,14 @@ export class RdsState implements XdsStreamState { /* If we have already received an update for the requested edsServiceName, * immediately pass that update along to the watcher */ + const isV2 = this.latestIsV2; for (const message of this.latestResponses) { if (message.name === routeConfigName) { /* These updates normally occur asynchronously, so we ensure that * the same happens here */ process.nextTick(() => { trace('Reporting existing RDS update for new watcher for routeConfigName ' + routeConfigName); - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); }); } } @@ -99,7 +101,7 @@ export class RdsState implements XdsStreamState { return Array.from(this.watchers.keys()); } - validateResponse(message: RouteConfiguration__Output): boolean { + validateResponse(message: RouteConfiguration__Output, isV2: boolean): boolean { // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation for (const virtualHost of message.virtual_hosts) { for (const domainPattern of virtualHost.domains) { @@ -114,7 +116,7 @@ export class RdsState implements XdsStreamState { return false; } } - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { return false; @@ -140,7 +142,7 @@ export class RdsState implements XdsStreamState { if ((route.route === undefined) || (route.route === null) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { return false; } - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { return false; @@ -155,7 +157,7 @@ export class RdsState implements XdsStreamState { if (weightSum !== route.route.weighted_clusters!.total_weight?.value ?? 100) { return false; } - if (EXPERIMENTAL_FAULT_INJECTION) { + if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { for (const weightedCluster of route.route!.weighted_clusters!.clusters) { for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { @@ -180,20 +182,21 @@ export class RdsState implements XdsStreamState { } } - handleResponses(responses: RouteConfiguration__Output[]): string | null { + handleResponses(responses: RouteConfiguration__Output[], isV2: boolean): string | null { for (const message of responses) { - if (!this.validateResponse(message)) { + if (!this.validateResponse(message, isV2)) { trace('RDS validation failed for message ' + JSON.stringify(message)); return 'RDS Error: Route validation failed'; } } this.latestResponses = responses; + this.latestIsV2 = isV2; const allRouteConfigNames = new Set(); for (const message of responses) { allRouteConfigNames.add(message.name); const watchers = this.watchers.get(message.name) ?? []; for (const watcher of watchers) { - watcher.onValidUpdate(message); + watcher.onValidUpdate(message, isV2); } } trace('Received RDS response with route config names ' + Array.from(allRouteConfigNames)); diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 83db1781e..14f3d1c76 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -18,7 +18,12 @@ import { StatusObject } from "@grpc/grpc-js"; export interface Watcher { - onValidUpdate(update: UpdateType): void; + /* Including the isV2 flag here is a bit of a kludge. It would probably be + * better for XdsStreamState#handleResponses to transform the protobuf + * message type into a library-specific configuration object type, to + * remove a lot of duplicate logic, including logic for handling that + * flag. */ + onValidUpdate(update: UpdateType, isV2: boolean): void; onTransientError(error: StatusObject): void; onResourceDoesNotExist(): void; } @@ -32,7 +37,7 @@ export interface XdsStreamState { * or null if it should be acked. * @param responses */ - handleResponses(responses: ResponseType[]): string | null; + handleResponses(responses: ResponseType[], isV2: boolean): string | null; reportStreamError(status: StatusObject): void; } \ No newline at end of file From d4574a28fbaba840454bd62914a5d980661b0f31 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Aug 2021 09:28:32 -0700 Subject: [PATCH 066/694] grpc-js: Add channelz support --- packages/grpc-js/src/call-stream.ts | 14 + packages/grpc-js/src/channel.ts | 43 ++- packages/grpc-js/src/channelz.ts | 345 ++++++++++++++++++ .../src/load-balancer-child-handler.ts | 8 + .../grpc-js/src/load-balancer-pick-first.ts | 5 + .../grpc-js/src/load-balancer-round-robin.ts | 2 + packages/grpc-js/src/load-balancer.ts | 3 + .../grpc-js/src/resolving-load-balancer.ts | 8 +- packages/grpc-js/src/server-call.ts | 11 +- packages/grpc-js/src/server.ts | 233 ++++++++++-- packages/grpc-js/src/subchannel-address.ts | 17 + packages/grpc-js/src/subchannel.ts | 166 ++++++++- packages/grpc-tools/deps/protobuf | 2 +- 13 files changed, 826 insertions(+), 31 deletions(-) create mode 100644 packages/grpc-js/src/channelz.ts diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 87121154d..e030d9e39 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -249,6 +249,9 @@ export class Http2CallStream implements Call { private configDeadline: Deadline = Infinity; + private statusWatchers: ((status: StatusObject) => void)[] = []; + private streamEndWatchers: ((success: boolean) => void)[] = []; + constructor( private readonly methodName: string, private readonly channel: ChannelImplementation, @@ -283,6 +286,7 @@ export class Http2CallStream implements Call { const filteredStatus = this.filterStack.receiveTrailers( this.finalStatus! ); + this.statusWatchers.forEach(watcher => watcher(filteredStatus)); /* We delay the actual action of bubbling up the status to insulate the * cleanup code in this class from any errors that may be thrown in the * upper layers as a result of bubbling up the status. In particular, @@ -426,6 +430,7 @@ export class Http2CallStream implements Call { } private handleTrailers(headers: http2.IncomingHttpHeaders) { + this.streamEndWatchers.forEach(watcher => watcher(true)); let headersString = ''; for (const header of Object.keys(headers)) { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; @@ -647,6 +652,7 @@ export class Http2CallStream implements Call { ); this.internalError = err; } + this.streamEndWatchers.forEach(watcher => watcher(false)); }); if (!this.pendingRead) { stream.pause(); @@ -736,6 +742,14 @@ export class Http2CallStream implements Call { this.configDeadline = configDeadline; } + addStatusWatcher(watcher: (status: StatusObject) => void) { + this.statusWatchers.push(watcher); + } + + addStreamEndWatcher(watcher: (success: boolean) => void) { + this.streamEndWatchers.push(watcher); + } + startRead() { /* If the stream has ended with an error, we should not emit any more * messages and we should communicate that the stream has ended */ diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 9200043e4..4a6e88147 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -47,6 +47,7 @@ import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; import { SurfaceCall } from './call'; import { ConnectivityState } from './connectivity-state'; +import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; /** * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args @@ -160,6 +161,13 @@ export class ChannelImplementation implements Channel { */ private callRefTimer: NodeJS.Timer; private configSelector: ConfigSelector | null = null; + + // Channelz info + private channelzRef: ChannelRef; + private channelzTrace: ChannelzTrace; + private callTracker = new ChannelzCallTracker(); + private childrenTracker = new ChannelzChildrenTracker(); + constructor( target: string, private readonly credentials: ChannelCredentials, @@ -204,6 +212,10 @@ export class ChannelImplementation implements Channel { this.callRefTimer = setInterval(() => {}, MAX_TIMEOUT_TIME); this.callRefTimer.unref?.(); + this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); + this.channelzTrace = new ChannelzTrace(); + this.channelzTrace.addTrace('CT_INFO', 'Channel created'); + if (this.options['grpc.default_authority']) { this.defaultAuthority = this.options['grpc.default_authority'] as string; } else { @@ -223,12 +235,14 @@ export class ChannelImplementation implements Channel { subchannelAddress: SubchannelAddress, subchannelArgs: ChannelOptions ) => { - return this.subchannelPool.getOrCreateSubchannel( + const subchannel = this.subchannelPool.getOrCreateSubchannel( this.target, subchannelAddress, Object.assign({}, this.options, subchannelArgs), this.credentials ); + this.channelzTrace.addTrace('CT_INFO', 'Created or got existing subchannel', subchannel.getChannelzRef()); + return subchannel; }, updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.currentPicker = picker; @@ -246,12 +260,19 @@ export class ChannelImplementation implements Channel { 'Resolving load balancer should never call requestReresolution' ); }, + addChannelzChild: (child: ChannelRef | SubchannelRef) => { + this.childrenTracker.refChild(child); + }, + removeChannelzChild: (child: ChannelRef | SubchannelRef) => { + this.childrenTracker.unrefChild(child); + } }; this.resolvingLoadBalancer = new ResolvingLoadBalancer( this.target, channelControlHelper, options, (configSelector) => { + this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); this.configSelector = configSelector; /* We process the queue asynchronously to ensure that the corresponding * load balancer update has completed. */ @@ -266,6 +287,7 @@ export class ChannelImplementation implements Channel { }); }, (status) => { + this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); if (this.configSelectionQueue.length > 0) { trace( LogVerbosity.DEBUG, @@ -296,6 +318,15 @@ export class ChannelImplementation implements Channel { ]); } + private getChannelzInfo(): ChannelInfo { + return { + state: this.connectivityState, + trace: this.channelzTrace, + callTracker: this.callTracker, + children: this.childrenTracker.getChildLists() + }; + } + private callRefTimerRef() { // If the hasRef function does not exist, always run the code if (!this.callRefTimer.hasRef?.()) { @@ -526,6 +557,7 @@ export class ChannelImplementation implements Channel { ' -> ' + ConnectivityState[newState] ); + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); this.connectivityState = newState; const watchersCopy = this.connectivityStateWatchers.slice(); for (const watcherObject of watchersCopy) { @@ -590,6 +622,7 @@ export class ChannelImplementation implements Channel { this.resolvingLoadBalancer.destroy(); this.updateState(ConnectivityState.SHUTDOWN); clearInterval(this.callRefTimer); + unregisterChannelzRef(this.channelzRef); this.subchannelPool.unrefUnusedSubchannels(); } @@ -685,6 +718,14 @@ export class ChannelImplementation implements Channel { this.credentials._getCallCredentials(), callNumber ); + this.callTracker.addCallStarted(); + stream.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); return stream; } } diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts new file mode 100644 index 000000000..3edbd7bba --- /dev/null +++ b/packages/grpc-js/src/channelz.ts @@ -0,0 +1,345 @@ +/* + * Copyright 2021 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 { isIPv4, isIPv6 } from "net"; +import { ConnectivityState } from "./connectivity-state"; +import { SubchannelAddress } from "./subchannel-address"; + +export type TraceSeverity = 'CT_UNKNOWN' | 'CT_INFO' | 'CT_WARNING' | 'CT_ERROR'; + +export interface ChannelRef { + kind: 'channel'; + id: number; + name: string; +} + +export interface SubchannelRef { + kind: 'subchannel'; + id: number; + name: string; +} + +export interface ServerRef { + kind: 'server'; + id: number; +} + +export interface SocketRef { + kind: 'socket'; + id: number; + name: string; +} + +interface TraceEvent { + description: string; + severity: TraceSeverity; + timestamp: Date; + childChannel?: ChannelRef; + childSubchannel?: SubchannelRef; +} + +export class ChannelzTrace { + events: TraceEvent[] = []; + creationTimestamp: Date; + eventsLogged: number = 0; + + constructor() { + this.creationTimestamp = new Date(); + } + + addTrace(severity: TraceSeverity, description: string, child?: ChannelRef | SubchannelRef) { + const timestamp = new Date(); + this.events.push({ + description: description, + severity: severity, + timestamp: timestamp, + childChannel: child?.kind === 'channel' ? child : undefined, + childSubchannel: child?.kind === 'subchannel' ? child : undefined + }); + this.eventsLogged += 1; + } +} + +export class ChannelzChildrenTracker { + private channelChildren: Map = new Map(); + private subchannelChildren: Map = new Map(); + private socketChildren: Map = new Map(); + + refChild(child: ChannelRef | SubchannelRef | SocketRef) { + switch (child.kind) { + case 'channel': { + let trackedChild = this.channelChildren.get(child.id) ?? {ref: child, count: 0}; + trackedChild.count += 1; + this.channelChildren.set(child.id, trackedChild); + break; + } + case 'subchannel':{ + let trackedChild = this.subchannelChildren.get(child.id) ?? {ref: child, count: 0}; + trackedChild.count += 1; + this.subchannelChildren.set(child.id, trackedChild); + break; + } + case 'socket':{ + let trackedChild = this.socketChildren.get(child.id) ?? {ref: child, count: 0}; + trackedChild.count += 1; + this.socketChildren.set(child.id, trackedChild); + break; + } + } + } + + unrefChild(child: ChannelRef | SubchannelRef | SocketRef) { + switch (child.kind) { + case 'channel': { + let trackedChild = this.channelChildren.get(child.id); + if (trackedChild !== undefined) { + trackedChild.count -= 1; + if (trackedChild.count === 0) { + this.channelChildren.delete(child.id); + } else { + this.channelChildren.set(child.id, trackedChild); + } + } + break; + } + case 'subchannel': { + let trackedChild = this.subchannelChildren.get(child.id); + if (trackedChild !== undefined) { + trackedChild.count -= 1; + if (trackedChild.count === 0) { + this.subchannelChildren.delete(child.id); + } else { + this.subchannelChildren.set(child.id, trackedChild); + } + } + break; + } + case 'socket': { + let trackedChild = this.socketChildren.get(child.id); + if (trackedChild !== undefined) { + trackedChild.count -= 1; + if (trackedChild.count === 0) { + this.socketChildren.delete(child.id); + } else { + this.socketChildren.set(child.id, trackedChild); + } + } + break; + } + } + } + + getChildLists(): ChannelzChildren { + const channels: ChannelRef[] = []; + for (const {ref} of this.channelChildren.values()) { + channels.push(ref); + } + const subchannels: SubchannelRef[] = []; + for (const {ref} of this.subchannelChildren.values()) { + subchannels.push(ref); + } + const sockets: SocketRef[] = []; + for (const {ref} of this.socketChildren.values()) { + sockets.push(ref); + } + return {channels, subchannels, sockets}; + } +} + +export class ChannelzCallTracker { + callsStarted: number = 0; + callsSucceeded: number = 0; + callsFailed: number = 0; + lastCallStartedTimestamp: Date | null = null; + + addCallStarted() { + this.callsStarted += 1; + this.lastCallStartedTimestamp = new Date(); + } + addCallSucceeded() { + this.callsSucceeded += 1; + } + addCallFailed() { + this.callsFailed += 1; + } +} + +export interface ChannelzChildren { + channels: ChannelRef[]; + subchannels: SubchannelRef[]; + sockets: SocketRef[]; +} + +export interface ChannelInfo { + state: ConnectivityState; + trace: ChannelzTrace; + callTracker: ChannelzCallTracker; + children: ChannelzChildren; +} + +export interface SubchannelInfo extends ChannelInfo {} + +export interface ServerInfo { + trace: ChannelzTrace; + callTracker: ChannelzCallTracker; + children: ChannelzChildren; +} + +export interface TlsInfo { + cipherSuiteStandardName: string | null; + cipherSuiteOtherName: string | null; + localCertificate: Buffer | null; + remoteCertificate: Buffer | null; +} + +export interface SocketInfo { + localAddress: SubchannelAddress; + remoteAddress: SubchannelAddress | null; + security: TlsInfo | null; + remoteName: string | null; + streamsStarted: number; + streamsSucceeded: number; + streamsFailed: number; + messagesSent: number; + messagesReceived: number; + keepAlivesSent: number; + lastLocalStreamCreatedTimestamp: Date | null; + lastRemoteStreamCreatedTimestamp: Date | null; + lastMessageSentTimestamp: Date | null; + lastMessageReceivedTimestamp: Date | null; + localFlowControlWindow: number | null; + remoteFlowControlWindow: number | null; +} + +interface ChannelEntry { + ref: ChannelRef; + getInfo(): ChannelInfo; +} + +interface SubchannelEntry { + ref: SubchannelRef; + getInfo(): SubchannelInfo; +} + +interface ServerEntry { + ref: ServerRef; + getInfo(): ServerInfo; +} + +interface SocketEntry { + ref: SocketRef; + getInfo(): SocketInfo; +} + +let nextId = 1; + +function getNextId(): number { + return nextId++; +} + +const channels: (ChannelEntry | undefined)[] = []; +const subchannels: (SubchannelEntry | undefined)[] = []; +const servers: (ServerEntry | undefined)[] = []; +const sockets: (SocketEntry | undefined)[] = []; + +export function registerChannelzChannel(name: string, getInfo: () => ChannelInfo): ChannelRef { + const id = getNextId(); + const ref: ChannelRef = {id, name, kind: 'channel'}; + channels[id] = { ref, getInfo }; + return ref; +} + +export function registerChannelzSubchannel(name: string, getInfo:() => SubchannelInfo): SubchannelRef { + const id = getNextId(); + const ref: SubchannelRef = {id, name, kind: 'subchannel'}; + subchannels[id] = { ref, getInfo }; + return ref; +} + +export function registerChannelzServer(getInfo: () => ServerInfo): ServerRef { + const id = getNextId(); + const ref: ServerRef = {id, kind: 'server'}; + servers[id] = { ref, getInfo }; + return ref; +} + +export function registerChannelzSocket(name: string, getInfo: () => SocketInfo): SocketRef { + const id = getNextId(); + const ref: SocketRef = {id, name, kind: 'socket'}; + sockets[id] = { ref, getInfo}; + return ref; +} + +export function unregisterChannelzRef(ref: ChannelRef | SubchannelRef | ServerRef | SocketRef) { + switch (ref.kind) { + case 'channel': + delete channels[ref.id]; + return; + case 'subchannel': + delete subchannels[ref.id]; + return; + case 'server': + delete servers[ref.id]; + return; + case 'socket': + delete sockets[ref.id]; + return; + } +} + +export interface ChannelzClientView { + updateState(connectivityState: ConnectivityState): void; + addTrace(severity: TraceSeverity, description: string, child?: ChannelRef | SubchannelRef): void; + addCallStarted(): void; + addCallSucceeded(): void; + addCallFailed(): void; + addChild(child: ChannelRef | SubchannelRef): void; + removeChild(child: ChannelRef | SubchannelRef): void; +} + +export interface ChannelzSubchannelView extends ChannelzClientView { + getRef(): SubchannelRef; +} + +/** + * Converts an IPv4 or IPv6 address from string representation to binary + * representation + * @param ipAddress an IP address in standard IPv4 or IPv6 text format + * @returns + */ +function ipAddressStringToBuffer(ipAddress: string): Buffer | null { + if (isIPv4(ipAddress)) { + return Buffer.from(Uint8Array.from(ipAddress.split('.').map(segment => Number.parseInt(segment)))); + } else if (isIPv6(ipAddress)) { + let leftSection: string; + let rightSection: string | null; + const doubleColonIndex = ipAddress.indexOf('::'); + if (doubleColonIndex === -1) { + leftSection = ipAddress; + rightSection = null; + } else { + leftSection = ipAddress.substring(0, doubleColonIndex); + rightSection = ipAddress.substring(doubleColonIndex + 2); + } + const leftBuffer = Uint8Array.from(leftSection.split(':').map(segment => Number.parseInt(segment, 16))); + const rightBuffer = rightSection ? Uint8Array.from(rightSection.split(':').map(segment => Number.parseInt(segment, 16))) : new Uint8Array(); + const middleBuffer = Buffer.alloc(16 - leftBuffer.length - rightBuffer.length, 0); + return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]); + } else { + return null; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 8fae7b8a6..c53914942 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -26,6 +26,7 @@ import { SubchannelAddress } from './subchannel-address'; import { ChannelOptions } from './channel-options'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; +import { ChannelRef, SubchannelRef } from './channelz'; const TYPE_NAME = 'child_load_balancer_helper'; @@ -67,6 +68,13 @@ export class ChildLoadBalancerHandler implements LoadBalancer { setChild(newChild: LoadBalancer) { this.child = newChild; } + addChannelzChild(child: ChannelRef | SubchannelRef) { + this.parent.channelControlHelper.addChannelzChild(child); + } + removeChannelzChild(child: ChannelRef | SubchannelRef) { + this.parent.channelControlHelper.removeChannelzChild(child); + } + private calledByPendingChild(): boolean { return this.child === this.parent.pendingChild; } diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 65179b163..6cdb7cb91 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -229,6 +229,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { subchannel.removeConnectivityStateListener( this.pickedSubchannelStateListener ); + this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); if (this.subchannels.length > 0) { if (this.triedAllSubchannels) { let newLBState: ConnectivityState; @@ -321,6 +322,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.updateState(ConnectivityState.READY, new PickFirstPicker(subchannel)); subchannel.addConnectivityStateListener(this.pickedSubchannelStateListener); subchannel.ref(); + this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); this.resetSubchannelList(); clearTimeout(this.connectionDelayTimeout); } @@ -339,6 +341,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { for (const subchannel of this.subchannels) { subchannel.removeConnectivityStateListener(this.subchannelStateListener); subchannel.unref(); + this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); } this.currentSubchannelIndex = 0; this.subchannelStateCounts = { @@ -369,6 +372,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { ); for (const subchannel of this.subchannels) { subchannel.ref(); + this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); } for (const subchannel of this.subchannels) { subchannel.addConnectivityStateListener(this.subchannelStateListener); @@ -449,6 +453,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.currentPick.removeConnectivityStateListener( this.pickedSubchannelStateListener ); + this.channelControlHelper.removeChannelzChild(this.currentPick.getChannelzRef()); } } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 56703397f..918afab25 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -191,6 +191,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer { for (const subchannel of this.subchannels) { subchannel.removeConnectivityStateListener(this.subchannelStateListener); subchannel.unref(); + this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); } this.subchannelStateCounts = { [ConnectivityState.CONNECTING]: 0, @@ -217,6 +218,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer { for (const subchannel of this.subchannels) { subchannel.ref(); subchannel.addConnectivityStateListener(this.subchannelStateListener); + this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); const subchannelState = subchannel.getConnectivityState(); this.subchannelStateCounts[subchannelState] += 1; if ( diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index f509f73eb..870fdb30b 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -20,6 +20,7 @@ import { Subchannel } from './subchannel'; import { SubchannelAddress } from './subchannel-address'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; +import { ChannelRef, SubchannelRef } from './channelz'; /** * A collection of functions associated with a channel that a load balancer @@ -47,6 +48,8 @@ export interface ChannelControlHelper { * Request new data from the resolver. */ requestReresolution(): void; + addChannelzChild(child: ChannelRef | SubchannelRef): void; + removeChannelzChild(child: ChannelRef | SubchannelRef): void; } /** diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 5729937d1..a03991bc7 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -75,7 +75,7 @@ function getDefaultConfigSelector( return { methodConfig: { name: [] }, pickInformation: {}, - status: Status.OK, + status: Status.OK }; }; } @@ -170,6 +170,12 @@ export class ResolvingLoadBalancer implements LoadBalancer { this.latestChildPicker = picker; this.updateState(newState, picker); }, + addChannelzChild: channelControlHelper.addChannelzChild.bind( + channelControlHelper + ), + removeChannelzChild: channelControlHelper.removeChannelzChild.bind( + channelControlHelper + ) }); this.innerResolver = createResolver( target, diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 4d24cdc86..cde7d9185 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -411,6 +411,8 @@ export class Http2ServerCallStream< ); this.cancelled = true; this.emit('cancelled', 'cancelled'); + this.emit('streamEnd', false); + this.sendStatus({code: Status.CANCELLED, details: 'Cancelled by client', metadata: new Metadata()}); }); this.stream.on('drain', () => { @@ -513,6 +515,7 @@ export class Http2ServerCallStream< resolve(); } + this.emit('receiveMessage'); resolve(this.deserializeMessage(requestBytes)); } catch (err) { err.code = Status.INTERNAL; @@ -567,6 +570,7 @@ export class Http2ServerCallStream< const response = this.serializeMessage(value!); this.write(response); + this.emit('sendMessage'); this.sendStatus({ code: Status.OK, details: 'OK', metadata }); } catch (err) { err.code = Status.INTERNAL; @@ -575,6 +579,8 @@ export class Http2ServerCallStream< } sendStatus(statusObj: StatusObject) { + this.emit('callEnd', statusObj.code); + this.emit('streamEnd', statusObj.code === Status.OK); if (this.checkCancelled()) { return; } @@ -609,9 +615,6 @@ export class Http2ServerCallStream< } sendError(error: ServerErrorResponse | ServerStatusResponse) { - if (this.checkCancelled()) { - return; - } const status: StatusObject = { code: Status.UNKNOWN, details: 'message' in error ? error.message : 'Unknown Error', @@ -653,6 +656,7 @@ export class Http2ServerCallStream< } this.sendMetadata(); + this.emit('sendMessage'); return this.stream.write(chunk); } @@ -688,6 +692,7 @@ export class Http2ServerCallStream< }); return; } + this.emit('receiveMessage'); this.pushOrBufferMessage(readable, message); } }); diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 42b79a3cf..04c24c072 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -56,8 +56,11 @@ import { TcpSubchannelAddress, isTcpSubchannelAddress, subchannelAddressToString, + stringToSubchannelAddress, } from './subchannel-address'; import { parseUri } from './uri-parser'; +import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; +import { CipherNameAndProtocol, TLSSocket } from 'tls'; const TRACER_NAME = 'server'; @@ -125,19 +128,91 @@ function getDefaultHandler(handlerType: HandlerType, methodName: string) { } } +interface ChannelzSessionInfo { + ref: SocketRef; + streamTracker: ChannelzCallTracker; + messagesSent: number; + messagesReceived: number; + lastMessageSentTimestamp: Date | null; + lastMessageReceivedTimestamp: Date | null; +} + +interface ChannelzListenerInfo { + ref: SocketRef; +} + export class Server { - private http2ServerList: (http2.Http2Server | http2.Http2SecureServer)[] = []; + private http2ServerList: { server: (http2.Http2Server | http2.Http2SecureServer), channelzRef: SocketRef }[] = []; private handlers: Map = new Map< string, UntypedHandler >(); - private sessions = new Set(); + private sessions = new Map(); private started = false; private options: ChannelOptions; + // Channelz Info + private channelzRef: ServerRef; + private channelzTrace = new ChannelzTrace(); + private callTracker = new ChannelzCallTracker(); + private childrenTracker = new ChannelzChildrenTracker(); + constructor(options?: ChannelOptions) { this.options = options ?? {}; + this.channelzRef = registerChannelzServer(() => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Server created'); + } + + private getChannelzInfo(): ServerInfo { + return { + trace: this.channelzTrace, + callTracker: this.callTracker, + children: this.childrenTracker.getChildLists() + }; + } + + private getChannelzSessionInfoGetter(session: http2.ServerHttp2Session): () => SocketInfo { + return () => { + const sessionInfo = this.sessions.get(session)!; + const sessionSocket = session.socket; + const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; + const localAddress = stringToSubchannelAddress(sessionSocket.localAddress, sessionSocket.localPort); + let tlsInfo: TlsInfo | null; + if (session.encrypted) { + const tlsSocket: TLSSocket = sessionSocket as TLSSocket; + const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); + const certificate = tlsSocket.getCertificate(); + const peerCertificate = tlsSocket.getPeerCertificate(); + tlsInfo = { + cipherSuiteStandardName: cipherInfo.standardName ?? null, + cipherSuiteOtherName: cipherInfo.standardName ? cipherInfo.name: null, + localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, + remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null + }; + } else { + tlsInfo = null; + } + const socketInfo: SocketInfo = { + remoteAddress: remoteAddress, + localAddress: localAddress, + security: tlsInfo, + remoteName: null, + streamsStarted: sessionInfo.streamTracker.callsStarted, + streamsSucceeded: sessionInfo.streamTracker.callsSucceeded, + streamsFailed: sessionInfo.streamTracker.callsFailed, + messagesSent: sessionInfo.messagesSent, + messagesReceived: sessionInfo.messagesReceived, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: sessionInfo.streamTracker.lastCallStartedTimestamp, + lastMessageSentTimestamp: sessionInfo.lastMessageSentTimestamp, + lastMessageReceivedTimestamp: sessionInfo.lastMessageReceivedTimestamp, + localFlowControlWindow: session.state.localWindowSize ?? null, + remoteFlowControlWindow: session.state.remoteWindowSize ?? null + }; + return socketInfo; + }; } addProtoService(): void { @@ -315,14 +390,41 @@ export class Server { http2Server.once('error', onError); http2Server.listen(addr, () => { - trace('Successfully bound ' + subchannelAddressToString(address)); - this.http2ServerList.push(http2Server); const boundAddress = http2Server.address()!; + let boundSubchannelAddress: SubchannelAddress; if (typeof boundAddress === 'string') { - resolve(portNum); + boundSubchannelAddress = { + path: boundAddress + }; } else { - resolve(boundAddress.port); + boundSubchannelAddress = { + host: boundAddress.address, + port: boundAddress.port + } } + const channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }); + this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); + trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); + resolve('port' in boundSubchannelAddress ? boundSubchannelAddress.port : portNum); http2Server.removeListener('error', onError); }); }); @@ -362,11 +464,37 @@ export class Server { http2Server.once('error', onError); http2Server.listen(address, () => { - this.http2ServerList.push(http2Server); + const boundAddress = http2Server.address() as AddressInfo; + const boundSubchannelAddress: SubchannelAddress = { + host: boundAddress.address, + port: boundAddress.port + }; + const channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }); + this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); + trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); resolve( bindSpecificPort( addressList.slice(1), - (http2Server.address() as AddressInfo).port, + boundAddress.port, 1 ) ); @@ -437,9 +565,12 @@ export class Server { forceShutdown(): void { // Close the server if it is still running. - for (const http2Server of this.http2ServerList) { + for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { if (http2Server.listening) { - http2Server.close(); + http2Server.close(() => { + this.childrenTracker.unrefChild(ref); + unregisterChannelzRef(ref); + }); } } @@ -447,13 +578,14 @@ export class Server { // Always destroy any available sessions. It's possible that one or more // tryShutdown() calls are in progress. Don't wait on them to finish. - this.sessions.forEach((session) => { + this.sessions.forEach((channelzInfo, session) => { // Cast NGHTTP2_CANCEL to any because TypeScript doesn't seem to // recognize destroy(code) as a valid signature. // eslint-disable-next-line @typescript-eslint/no-explicit-any session.destroy(http2.constants.NGHTTP2_CANCEL as any); }); this.sessions.clear(); + unregisterChannelzRef(this.channelzRef); } register( @@ -485,7 +617,7 @@ export class Server { if ( this.http2ServerList.length === 0 || this.http2ServerList.every( - (http2Server) => http2Server.listening !== true + ({server: http2Server}) => http2Server.listening !== true ) ) { throw new Error('server must be bound in order to start'); @@ -494,39 +626,47 @@ export class Server { if (this.started === true) { throw new Error('server is already started'); } - + this.channelzTrace.addTrace('CT_INFO', 'Starting'); this.started = true; } tryShutdown(callback: (error?: Error) => void): void { + const wrappedCallback = (error?: Error) => { + unregisterChannelzRef(this.channelzRef); + callback(error); + }; let pendingChecks = 0; function maybeCallback(): void { pendingChecks--; if (pendingChecks === 0) { - callback(); + wrappedCallback(); } } // Close the server if necessary. this.started = false; - for (const http2Server of this.http2ServerList) { + for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { if (http2Server.listening) { pendingChecks++; - http2Server.close(maybeCallback); + http2Server.close(() => { + this.childrenTracker.unrefChild(ref); + unregisterChannelzRef(ref); + maybeCallback(); + }); } } - this.sessions.forEach((session) => { + this.sessions.forEach((channelzInfo, session) => { if (!session.closed) { pendingChecks += 1; session.close(maybeCallback); } }); if (pendingChecks === 0) { - callback(); + wrappedCallback(); } } @@ -544,6 +684,9 @@ export class Server { http2Server.on( 'stream', (stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) => { + const channelzSessionInfo = this.sessions.get(stream.session as http2.ServerHttp2Session); + this.callTracker.addCallStarted(); + channelzSessionInfo?.streamTracker.addCallStarted(); const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE]; if ( @@ -557,9 +700,13 @@ export class Server { }, { endStream: true } ); + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); return; } + let call: Http2ServerCallStream | null = null; + try { const path = headers[http2.constants.HTTP2_HEADER_PATH] as string; const serverAddress = http2Server.address(); @@ -589,7 +736,31 @@ export class Server { throw getUnimplementedStatusResponse(path); } - const call = new Http2ServerCallStream(stream, handler, this.options); + call = new Http2ServerCallStream(stream, handler, this.options); + call.once('callEnd', (code: Status) => { + if (code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + if (channelzSessionInfo) { + call.once('streamEnd', (success: boolean) => { + if (success) { + channelzSessionInfo.streamTracker.addCallSucceeded(); + } else { + channelzSessionInfo.streamTracker.addCallFailed(); + } + }); + call.on('sendMessage', () => { + channelzSessionInfo.messagesSent += 1; + channelzSessionInfo.lastMessageSentTimestamp = new Date(); + }); + call.on('receiveMessage', () => { + channelzSessionInfo.messagesReceived += 1; + channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); + }); + } const metadata: Metadata = call.receiveMetadata(headers) as Metadata; switch (handler.type) { case 'unary': @@ -620,7 +791,11 @@ export class Server { throw new Error(`Unknown handler type: ${handler.type}`); } } catch (err) { - const call = new Http2ServerCallStream(stream, null!, this.options); + if (!call) { + call = new Http2ServerCallStream(stream, null!, this.options); + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed() + } if (err.code === undefined) { err.code = Status.INTERNAL; @@ -637,9 +812,25 @@ export class Server { return; } - this.sessions.add(session); + const channelzRef = registerChannelzSocket(session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session)); + + const channelzSessionInfo: ChannelzSessionInfo = { + ref: channelzRef, + streamTracker: new ChannelzCallTracker(), + messagesSent: 0, + messagesReceived: 0, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null + }; + this.sessions.set(session, channelzSessionInfo); + const clientAddress = session.socket.remoteAddress; + this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); + this.childrenTracker.refChild(channelzRef); session.on('close', () => { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); + this.childrenTracker.unrefChild(channelzRef); + unregisterChannelzRef(channelzRef); this.sessions.delete(session); }); }); diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts index 4b08d8ba1..29022caa5 100644 --- a/packages/grpc-js/src/subchannel-address.ts +++ b/packages/grpc-js/src/subchannel-address.ts @@ -15,6 +15,8 @@ * */ +import { isIP } from "net"; + export interface TcpSubchannelAddress { port: number; host: string; @@ -60,3 +62,18 @@ export function subchannelAddressToString(address: SubchannelAddress): string { return address.path; } } + +const DEFAULT_PORT = 443; + +export function stringToSubchannelAddress(addressString: string, port?: number): SubchannelAddress { + if (isIP(addressString)) { + return { + host: addressString, + port: port ?? DEFAULT_PORT + }; + } else { + return { + path: addressString + }; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index ae23feb3d..1caf94ba1 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -18,23 +18,25 @@ import * as http2 from 'http2'; import { ChannelCredentials } from './channel-credentials'; import { Metadata } from './metadata'; -import { Http2CallStream } from './call-stream'; +import { Call, Http2CallStream, WriteObject } from './call-stream'; import { ChannelOptions } from './channel-options'; -import { PeerCertificate, checkServerIdentity } from 'tls'; +import { PeerCertificate, checkServerIdentity, TLSSocket, CipherNameAndProtocol } from 'tls'; import { ConnectivityState } from './connectivity-state'; import { BackoffTimeout, BackoffOptions } from './backoff-timeout'; import { getDefaultAuthority } from './resolver'; import * as logging from './logging'; -import { LogVerbosity } from './constants'; +import { LogVerbosity, Status } from './constants'; import { getProxiedConnection, ProxyConnectionResult } from './http_proxy'; import * as net from 'net'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { ConnectionOptions } from 'tls'; -import { FilterFactory, Filter } from './filter'; +import { FilterFactory, Filter, BaseFilter } from './filter'; import { + stringToSubchannelAddress, SubchannelAddress, subchannelAddressToString, } from './subchannel-address'; +import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, SocketInfo, SocketRef, unregisterChannelzRef, registerChannelzSocket, TlsInfo } from './channelz'; const clientVersion = require('../../package.json').version; @@ -157,6 +159,53 @@ export class Subchannel { */ private subchannelAddressString: string; + // Channelz info + private channelzRef: SubchannelRef; + private channelzTrace: ChannelzTrace; + private callTracker = new ChannelzCallTracker(); + private childrenTracker = new ChannelzChildrenTracker(); + + // Channelz socket info + private channelzSocketRef: SocketRef | null = null; + /** + * Name of the remote server, if it is not the same as the subchannel + * address, i.e. if connecting through an HTTP CONNECT proxy. + */ + private remoteName: string | null = null; + private streamTracker = new ChannelzCallTracker(); + private keepalivesSent = 0; + private messagesSent = 0; + private messagesReceived = 0; + private lastMessageSentTimestamp: Date | null = null; + private lastMessageReceivedTimestamp: Date | null = null; + private MessageCountFilter = class extends BaseFilter implements Filter { + private session: http2.ClientHttp2Session; + constructor(private parent: Subchannel) { + super(); + this.session = parent.session!; + } + sendMessage(message: Promise): Promise { + if (this.parent.session === this.session) { + this.parent.messagesSent += 1; + this.parent.lastMessageSentTimestamp = new Date(); + } + return message; + } + receiveMessage(message: Promise): Promise { + if (this.parent.session === this.session) { + this.parent.messagesReceived += 1; + this.parent.lastMessageReceivedTimestamp = new Date(); + } + return message; + } + }; + private MessageCountFilterFactory = class implements FilterFactory { + constructor(private parent: Subchannel) {} + createFilter(callStream: Call): Filter { + return new this.parent.MessageCountFilter(this.parent); + } + } + /** * A class representing a connection to a single backend. * @param channelTarget The target string for the channel as a whole @@ -206,6 +255,77 @@ export class Subchannel { this.handleBackoffTimer(); }, backoffOptions); this.subchannelAddressString = subchannelAddressToString(subchannelAddress); + + this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); + this.channelzTrace = new ChannelzTrace(); + this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); + } + + private getChannelzInfo(): SubchannelInfo { + return { + state: this.connectivityState, + trace: this.channelzTrace, + callTracker: this.callTracker, + children: this.childrenTracker.getChildLists() + }; + } + + private getChannelzSocketInfo(): SocketInfo | null { + if (this.session === null) { + return null; + } + const sessionSocket = this.session.socket; + const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; + const localAddress = stringToSubchannelAddress(sessionSocket.localAddress, sessionSocket.localPort); + let tlsInfo: TlsInfo | null; + if (this.session.encrypted) { + const tlsSocket: TLSSocket = sessionSocket as TLSSocket; + const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); + const certificate = tlsSocket.getCertificate(); + const peerCertificate = tlsSocket.getPeerCertificate(); + tlsInfo = { + cipherSuiteStandardName: cipherInfo.standardName ?? null, + cipherSuiteOtherName: cipherInfo.standardName ? cipherInfo.name: null, + localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, + remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null + }; + } else { + tlsInfo = null; + } + const socketInfo: SocketInfo = { + remoteAddress: remoteAddress, + localAddress: localAddress, + security: tlsInfo, + remoteName: this.remoteName, + streamsStarted: this.streamTracker.callsStarted, + streamsSucceeded: this.streamTracker.callsSucceeded, + streamsFailed: this.streamTracker.callsFailed, + messagesSent: this.messagesSent, + messagesReceived: this.messagesReceived, + keepAlivesSent: this.keepalivesSent, + lastLocalStreamCreatedTimestamp: this.streamTracker.lastCallStartedTimestamp, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: this.lastMessageSentTimestamp, + lastMessageReceivedTimestamp: this.lastMessageReceivedTimestamp, + localFlowControlWindow: this.session.state.localWindowSize ?? null, + remoteFlowControlWindow: this.session.state.remoteWindowSize ?? null + }; + return socketInfo; + } + + private resetChannelzSocketInfo() { + if (this.channelzSocketRef) { + unregisterChannelzRef(this.channelzSocketRef); + this.childrenTracker.unrefChild(this.channelzSocketRef); + this.channelzSocketRef = null; + } + this.remoteName = null; + this.streamTracker = new ChannelzCallTracker(); + this.keepalivesSent = 0; + this.messagesSent = 0; + this.messagesReceived = 0; + this.lastMessageSentTimestamp = null; + this.lastMessageReceivedTimestamp = null; } private handleBackoffTimer() { @@ -235,6 +355,7 @@ export class Subchannel { } private sendPing() { + this.keepalivesSent += 1; logging.trace( LogVerbosity.DEBUG, 'keepalive', @@ -266,6 +387,11 @@ export class Subchannel { } private createSession(proxyConnectionResult: ProxyConnectionResult) { + if (proxyConnectionResult.realTarget) { + this.remoteName = uriToString(proxyConnectionResult.realTarget); + } else { + this.remoteName = null; + } const targetAuthority = getDefaultAuthority( proxyConnectionResult.realTarget ?? this.channelTarget ); @@ -353,6 +479,8 @@ export class Subchannel { connectionOptions ); this.session = session; + this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); + this.childrenTracker.refChild(this.channelzSocketRef); session.unref(); /* For all of these events, check if the session at the time of the event * is the same one currently attached to this subchannel, to ensure that @@ -425,6 +553,7 @@ export class Subchannel { (error as Error).message ); }); + registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); } private startConnectingInternal() { @@ -506,6 +635,7 @@ export class Subchannel { ' -> ' + ConnectivityState[newState] ); + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); const previousState = this.connectivityState; this.connectivityState = newState; switch (newState) { @@ -530,6 +660,7 @@ export class Subchannel { this.session.close(); } this.session = null; + this.resetChannelzSocketInfo(); this.stopKeepalivePings(); /* If the backoff timer has already ended by the time we get to the * TRANSIENT_FAILURE state, we want to immediately transition out of @@ -545,6 +676,7 @@ export class Subchannel { this.session.close(); } this.session = null; + this.resetChannelzSocketInfo(); this.stopKeepalivePings(); break; default: @@ -566,10 +698,12 @@ export class Subchannel { /* If no calls, channels, or subchannel pools have any more references to * this subchannel, we can be sure it will never be used again. */ if (this.callRefcount === 0 && this.refcount === 0) { + this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); this.transitionToState( [ConnectivityState.CONNECTING, ConnectivityState.READY], ConnectivityState.TRANSIENT_FAILURE ); + unregisterChannelzRef(this.channelzRef); } } @@ -694,6 +828,26 @@ export class Subchannel { ' with headers\n' + headersString ); + this.callTracker.addCallStarted(); + callStream.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + const streamSession = this.session; + this.streamTracker.addCallStarted(); + callStream.addStreamEndWatcher(success => { + if (streamSession === this.session) { + if (success) { + this.streamTracker.addCallSucceeded(); + } else { + this.streamTracker.addCallFailed(); + } + } + }); + extraFilterFactories.push(new this.MessageCountFilterFactory(this)); callStream.attachHttp2Stream(http2Stream, this, extraFilterFactories); } @@ -773,4 +927,8 @@ export class Subchannel { getAddress(): string { return this.subchannelAddressString; } + + getChannelzRef(): SubchannelRef { + return this.channelzRef; + } } diff --git a/packages/grpc-tools/deps/protobuf b/packages/grpc-tools/deps/protobuf index 6aa539bf0..a6e1cc7e3 160000 --- a/packages/grpc-tools/deps/protobuf +++ b/packages/grpc-tools/deps/protobuf @@ -1 +1 @@ -Subproject commit 6aa539bf0195f188ff86efe6fb8bfa2b676cdd46 +Subproject commit a6e1cc7e328c45a0cb9856c530c8f6cd23314163 From 2efe0918a8724e081309a243c14a9c0fc9968c85 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 31 Aug 2021 11:45:15 -0700 Subject: [PATCH 067/694] grpc-js-xds: Enable fault injection feature by default --- packages/grpc-js-xds/scripts/xds.sh | 1 - packages/grpc-js-xds/src/environment.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 8d94ae195..131cbd551 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -51,7 +51,6 @@ grpc/tools/run_tests/helper_scripts/prep_xds.sh GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter \ GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ - GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ --test_case="all,timeout,circuit_breaking,fault_injection" \ --project_id=grpc-testing \ diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 18e9e70bd..e1c2d8158 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -15,4 +15,4 @@ * */ -export const EXPERIMENTAL_FAULT_INJECTION = process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION; \ No newline at end of file +export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; \ No newline at end of file From ca5045df8c91d9362aff74be702c4e9244c3083c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 31 Aug 2021 11:53:15 -0700 Subject: [PATCH 068/694] grpc-js-xds: Update readme feature list --- packages/grpc-js-xds/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index bcea70456..84d291508 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -22,4 +22,8 @@ const client = new MyServiceClient('xds:///example.com:123'); ## Supported Features - [xDS-Based Global Load Balancing](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md) - - [xDS traffic splitting and routing](https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md) \ No newline at end of file + - [xDS traffic splitting and routing](https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md) + - [xDS v3 API](https://github.com/grpc/proposal/blob/master/A30-xds-v3.md) + - [xDS Timeouts](https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md) + - [xDS Circuit Breaking](https://github.com/grpc/proposal/blob/master/A32-xds-circuit-breaking.md) + - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) \ No newline at end of file From 7a3dd6eb296f0b892867fcba61a2c640dcf35007 Mon Sep 17 00:00:00 2001 From: Pratyay Banerjee <48355572+Neilblaze@users.noreply.github.com> Date: Thu, 2 Sep 2021 22:54:56 +0530 Subject: [PATCH 069/694] =?UTF-8?q?fix(docs):=20supporst=20=E2=9F=B6=20sup?= =?UTF-8?q?ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pratyay Banerjee <48355572+Neilblaze@users.noreply.github.com> --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index dd5bce7af..89903392a 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -4,7 +4,7 @@ This guide is for troubleshooting the `grpc-js` library for Node.js ## Enabling extra logging and tracing -Extra logging can be very useful for diagnosing problems. `grpc-js` supporst +Extra logging can be very useful for diagnosing problems. `grpc-js` support the `GRPC_VERBOSITY` and `GRPC_TRACE` environment variables that can be used to increase the amount of information that gets printed to stderr. From f9ae440b1cc0b4c78c9a7b8a295c9e4d2cb07ac8 Mon Sep 17 00:00:00 2001 From: Pratyay Banerjee <48355572+Neilblaze@users.noreply.github.com> Date: Thu, 2 Sep 2021 23:00:11 +0530 Subject: [PATCH 070/694] updated with an extra 's' Signed-off-by: Pratyay Banerjee <48355572+Neilblaze@users.noreply.github.com> --- TROUBLESHOOTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 89903392a..bfcf94edc 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -4,7 +4,7 @@ This guide is for troubleshooting the `grpc-js` library for Node.js ## Enabling extra logging and tracing -Extra logging can be very useful for diagnosing problems. `grpc-js` support +Extra logging can be very useful for diagnosing problems. `grpc-js` supports the `GRPC_VERBOSITY` and `GRPC_TRACE` environment variables that can be used to increase the amount of information that gets printed to stderr. From c95219b1ea46c1db2d0e2edb817a3ed4232f1749 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Sep 2021 14:36:40 -0700 Subject: [PATCH 071/694] proto-loader: Avoid generating conflicting method names in service clients --- .../bin/proto-loader-gen-types.ts | 35 +++++++++++++++++++ packages/proto-loader/package.json | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index b441d425e..6c9596980 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -454,6 +454,38 @@ function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum formatter.writeLine('}'); } +/** + * This is a list of methods that are exist in the generic Client class in the + * gRPC libraries. TypeScript has a problem with methods in subclasses with the + * same names as methods in the superclass, but with mismatched APIs. So, we + * avoid generating methods with these names in the service client interfaces. + * We always generate two service client methods per service method: one camel + * cased, and one with the original casing. So we will still generate one + * service client method for any conflicting name. + * + * Technically, at runtime conflicting name in the service client method + * actually shadows the original method, but TypeScript does not have a good + * way to represent that. So this change is not 100% accurate, but it gets the + * generated code to compile. + * + * This is just a list of the methods in the Client class definitions in + * grpc@1.24.11 and @grpc/grpc-js@1.4.0. + */ +const CLIENT_RESERVED_METHOD_NAMES = new Set([ + 'close', + 'getChannel', + 'waitForReady', + 'makeUnaryRequest', + 'makeClientStreamRequest', + 'makeServerStreamRequest', + 'makeBidiStreamRequest', + 'resolveCallInterceptors', + /* These methods are private, but TypeScript is not happy with overriding even + * private methods with mismatched APIs. */ + 'checkOptionalUnaryResponseArguments', + 'checkMetadataAndOptions' +]); + function generateServiceClientInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { if (options.includeComments) { formatComment(formatter, serviceType.comment); @@ -463,6 +495,9 @@ function generateServiceClientInterface(formatter: TextFormatter, serviceType: P for (const methodName of Object.keys(serviceType.methods).sort()) { const method = serviceType.methods[methodName]; for (const name of [methodName, camelCase(methodName)]) { + if (CLIENT_RESERVED_METHOD_NAMES.has(name)) { + continue; + } if (options.includeComments) { formatComment(formatter, method.comment); } diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 824fa6f5e..9418df982 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.4", + "version": "0.6.5", "author": "Google Inc.", "contributors": [ { From bf0df1f94a04c1d41374af588b7c212f299ef56d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Sep 2021 14:21:48 -0700 Subject: [PATCH 072/694] Add channelz.proto and generated code --- packages/grpc-js/package.json | 5 +- packages/grpc-js/proto/channelz.proto | 564 ++++++++++++++++++ packages/grpc-js/src/generated/channelz.ts | 73 +++ .../src/generated/google/protobuf/Any.ts | 13 + .../generated/google/protobuf/BoolValue.ts | 10 + .../generated/google/protobuf/BytesValue.ts | 10 + .../generated/google/protobuf/DoubleValue.ts | 10 + .../src/generated/google/protobuf/Duration.ts | 13 + .../generated/google/protobuf/FloatValue.ts | 10 + .../generated/google/protobuf/Int32Value.ts | 10 + .../generated/google/protobuf/Int64Value.ts | 11 + .../generated/google/protobuf/StringValue.ts | 10 + .../generated/google/protobuf/Timestamp.ts | 13 + .../generated/google/protobuf/UInt32Value.ts | 10 + .../generated/google/protobuf/UInt64Value.ts | 11 + .../src/generated/grpc/channelz/v1/Address.ts | 89 +++ .../src/generated/grpc/channelz/v1/Channel.ts | 68 +++ .../channelz/v1/ChannelConnectivityState.ts | 29 + .../generated/grpc/channelz/v1/ChannelData.ts | 76 +++ .../generated/grpc/channelz/v1/ChannelRef.ts | 31 + .../grpc/channelz/v1/ChannelTrace.ts | 45 ++ .../grpc/channelz/v1/ChannelTraceEvent.ts | 73 +++ .../generated/grpc/channelz/v1/Channelz.ts | 178 ++++++ .../grpc/channelz/v1/GetChannelRequest.ts | 17 + .../grpc/channelz/v1/GetChannelResponse.ts | 19 + .../grpc/channelz/v1/GetServerRequest.ts | 17 + .../grpc/channelz/v1/GetServerResponse.ts | 19 + .../channelz/v1/GetServerSocketsRequest.ts | 39 ++ .../channelz/v1/GetServerSocketsResponse.ts | 33 + .../grpc/channelz/v1/GetServersRequest.ts | 37 ++ .../grpc/channelz/v1/GetServersResponse.ts | 33 + .../grpc/channelz/v1/GetSocketRequest.ts | 29 + .../grpc/channelz/v1/GetSocketResponse.ts | 19 + .../grpc/channelz/v1/GetSubchannelRequest.ts | 17 + .../grpc/channelz/v1/GetSubchannelResponse.ts | 19 + .../grpc/channelz/v1/GetTopChannelsRequest.ts | 37 ++ .../channelz/v1/GetTopChannelsResponse.ts | 33 + .../generated/grpc/channelz/v1/Security.ts | 87 +++ .../src/generated/grpc/channelz/v1/Server.ts | 45 ++ .../generated/grpc/channelz/v1/ServerData.ts | 57 ++ .../generated/grpc/channelz/v1/ServerRef.ts | 31 + .../src/generated/grpc/channelz/v1/Socket.ts | 70 +++ .../generated/grpc/channelz/v1/SocketData.ts | 150 +++++ .../grpc/channelz/v1/SocketOption.ts | 47 ++ .../grpc/channelz/v1/SocketOptionLinger.ts | 33 + .../grpc/channelz/v1/SocketOptionTcpInfo.ts | 74 +++ .../grpc/channelz/v1/SocketOptionTimeout.ts | 19 + .../generated/grpc/channelz/v1/SocketRef.ts | 31 + .../generated/grpc/channelz/v1/Subchannel.ts | 70 +++ .../grpc/channelz/v1/SubchannelRef.ts | 31 + 50 files changed, 2473 insertions(+), 2 deletions(-) create mode 100644 packages/grpc-js/proto/channelz.proto create mode 100644 packages/grpc-js/src/generated/channelz.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/Any.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/BoolValue.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/BytesValue.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/DoubleValue.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/Duration.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/FloatValue.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/Int32Value.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/Int64Value.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/StringValue.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/Timestamp.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/UInt32Value.ts create mode 100644 packages/grpc-js/src/generated/google/protobuf/UInt64Value.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/Address.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/Channel.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/ChannelConnectivityState.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/ChannelData.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/ChannelRef.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTrace.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTraceEvent.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelRequest.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelResponse.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetServerRequest.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetServerResponse.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsRequest.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsResponse.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetServersRequest.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetServersResponse.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketRequest.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketResponse.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelRequest.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelResponse.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsRequest.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsResponse.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/Security.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/Server.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/ServerData.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/ServerRef.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/Socket.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/SocketData.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/SocketOption.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionLinger.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTcpInfo.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTimeout.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/SocketRef.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/Subchannel.ts create mode 100644 packages/grpc-js/src/generated/grpc/channelz/v1/SubchannelRef.ts diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 79df40c52..7d8cd611d 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -15,7 +15,6 @@ "types": "build/src/index.d.ts", "license": "Apache-2.0", "devDependencies": { - "@grpc/proto-loader": "^0.5.5", "@types/gulp": "^4.0.6", "@types/gulp-mocha": "0.0.32", "@types/lodash": "^4.14.108", @@ -54,9 +53,11 @@ "check": "gts check src/**/*.ts", "fix": "gts fix src/*.ts", "pretest": "npm run compile", - "posttest": "npm run check && madge -c ./build/src" + "posttest": "npm run check && madge -c ./build/src", + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated/ --grpcLib ../index channelz.proto" }, "dependencies": { + "@grpc/proto-loader": "^0.6.4", "@types/node": ">=12.12.47" }, "files": [ diff --git a/packages/grpc-js/proto/channelz.proto b/packages/grpc-js/proto/channelz.proto new file mode 100644 index 000000000..446e9794b --- /dev/null +++ b/packages/grpc-js/proto/channelz.proto @@ -0,0 +1,564 @@ +// Copyright 2018 The 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. + +// This file defines an interface for exporting monitoring information +// out of gRPC servers. See the full design at +// https://github.com/grpc/proposal/blob/master/A14-channelz.md +// +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/channelz/v1/channelz.proto + +syntax = "proto3"; + +package grpc.channelz.v1; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option go_package = "google.golang.org/grpc/channelz/grpc_channelz_v1"; +option java_multiple_files = true; +option java_package = "io.grpc.channelz.v1"; +option java_outer_classname = "ChannelzProto"; + +// Channel is a logical grouping of channels, subchannels, and sockets. +message Channel { + // The identifier for this channel. This should bet set. + ChannelRef ref = 1; + // Data specific to this channel. + ChannelData data = 2; + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + + // There are no ordering guarantees on the order of channel refs. + // There may not be cycles in the ref graph. + // A channel ref may be present in more than one channel or subchannel. + repeated ChannelRef channel_ref = 3; + + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + // There are no ordering guarantees on the order of subchannel refs. + // There may not be cycles in the ref graph. + // A sub channel ref may be present in more than one channel or subchannel. + repeated SubchannelRef subchannel_ref = 4; + + // There are no ordering guarantees on the order of sockets. + repeated SocketRef socket_ref = 5; +} + +// Subchannel is a logical grouping of channels, subchannels, and sockets. +// A subchannel is load balanced over by it's ancestor +message Subchannel { + // The identifier for this channel. + SubchannelRef ref = 1; + // Data specific to this channel. + ChannelData data = 2; + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + + // There are no ordering guarantees on the order of channel refs. + // There may not be cycles in the ref graph. + // A channel ref may be present in more than one channel or subchannel. + repeated ChannelRef channel_ref = 3; + + // At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + // There are no ordering guarantees on the order of subchannel refs. + // There may not be cycles in the ref graph. + // A sub channel ref may be present in more than one channel or subchannel. + repeated SubchannelRef subchannel_ref = 4; + + // There are no ordering guarantees on the order of sockets. + repeated SocketRef socket_ref = 5; +} + +// These come from the specified states in this document: +// https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md +message ChannelConnectivityState { + enum State { + UNKNOWN = 0; + IDLE = 1; + CONNECTING = 2; + READY = 3; + TRANSIENT_FAILURE = 4; + SHUTDOWN = 5; + } + State state = 1; +} + +// Channel data is data related to a specific Channel or Subchannel. +message ChannelData { + // The connectivity state of the channel or subchannel. Implementations + // should always set this. + ChannelConnectivityState state = 1; + + // The target this channel originally tried to connect to. May be absent + string target = 2; + + // A trace of recent events on the channel. May be absent. + ChannelTrace trace = 3; + + // The number of calls started on the channel + int64 calls_started = 4; + // The number of calls that have completed with an OK status + int64 calls_succeeded = 5; + // The number of calls that have completed with a non-OK status + int64 calls_failed = 6; + + // The last time a call was started on the channel. + google.protobuf.Timestamp last_call_started_timestamp = 7; +} + +// A trace event is an interesting thing that happened to a channel or +// subchannel, such as creation, address resolution, subchannel creation, etc. +message ChannelTraceEvent { + // High level description of the event. + string description = 1; + // The supported severity levels of trace events. + enum Severity { + CT_UNKNOWN = 0; + CT_INFO = 1; + CT_WARNING = 2; + CT_ERROR = 3; + } + // the severity of the trace event + Severity severity = 2; + // When this event occurred. + google.protobuf.Timestamp timestamp = 3; + // ref of referenced channel or subchannel. + // Optional, only present if this event refers to a child object. For example, + // this field would be filled if this trace event was for a subchannel being + // created. + oneof child_ref { + ChannelRef channel_ref = 4; + SubchannelRef subchannel_ref = 5; + } +} + +// ChannelTrace represents the recent events that have occurred on the channel. +message ChannelTrace { + // Number of events ever logged in this tracing object. This can differ from + // events.size() because events can be overwritten or garbage collected by + // implementations. + int64 num_events_logged = 1; + // Time that this channel was created. + google.protobuf.Timestamp creation_timestamp = 2; + // List of events that have occurred on this channel. + repeated ChannelTraceEvent events = 3; +} + +// ChannelRef is a reference to a Channel. +message ChannelRef { + // The globally unique id for this channel. Must be a positive number. + int64 channel_id = 1; + // An optional name associated with the channel. + string name = 2; + // Intentionally don't use field numbers from other refs. + reserved 3, 4, 5, 6, 7, 8; +} + +// SubchannelRef is a reference to a Subchannel. +message SubchannelRef { + // The globally unique id for this subchannel. Must be a positive number. + int64 subchannel_id = 7; + // An optional name associated with the subchannel. + string name = 8; + // Intentionally don't use field numbers from other refs. + reserved 1, 2, 3, 4, 5, 6; +} + +// SocketRef is a reference to a Socket. +message SocketRef { + // The globally unique id for this socket. Must be a positive number. + int64 socket_id = 3; + // An optional name associated with the socket. + string name = 4; + // Intentionally don't use field numbers from other refs. + reserved 1, 2, 5, 6, 7, 8; +} + +// ServerRef is a reference to a Server. +message ServerRef { + // A globally unique identifier for this server. Must be a positive number. + int64 server_id = 5; + // An optional name associated with the server. + string name = 6; + // Intentionally don't use field numbers from other refs. + reserved 1, 2, 3, 4, 7, 8; +} + +// Server represents a single server. There may be multiple servers in a single +// program. +message Server { + // The identifier for a Server. This should be set. + ServerRef ref = 1; + // The associated data of the Server. + ServerData data = 2; + + // The sockets that the server is listening on. There are no ordering + // guarantees. This may be absent. + repeated SocketRef listen_socket = 3; +} + +// ServerData is data for a specific Server. +message ServerData { + // A trace of recent events on the server. May be absent. + ChannelTrace trace = 1; + + // The number of incoming calls started on the server + int64 calls_started = 2; + // The number of incoming calls that have completed with an OK status + int64 calls_succeeded = 3; + // The number of incoming calls that have a completed with a non-OK status + int64 calls_failed = 4; + + // The last time a call was started on the server. + google.protobuf.Timestamp last_call_started_timestamp = 5; +} + +// Information about an actual connection. Pronounced "sock-ay". +message Socket { + // The identifier for the Socket. + SocketRef ref = 1; + + // Data specific to this Socket. + SocketData data = 2; + // The locally bound address. + Address local = 3; + // The remote bound address. May be absent. + Address remote = 4; + // Security details for this socket. May be absent if not available, or + // there is no security on the socket. + Security security = 5; + + // Optional, represents the name of the remote endpoint, if different than + // the original target name. + string remote_name = 6; +} + +// SocketData is data associated for a specific Socket. The fields present +// are specific to the implementation, so there may be minor differences in +// the semantics. (e.g. flow control windows) +message SocketData { + // The number of streams that have been started. + int64 streams_started = 1; + // The number of streams that have ended successfully: + // On client side, received frame with eos bit set; + // On server side, sent frame with eos bit set. + int64 streams_succeeded = 2; + // The number of streams that have ended unsuccessfully: + // On client side, ended without receiving frame with eos bit set; + // On server side, ended without sending frame with eos bit set. + int64 streams_failed = 3; + // The number of grpc messages successfully sent on this socket. + int64 messages_sent = 4; + // The number of grpc messages received on this socket. + int64 messages_received = 5; + + // The number of keep alives sent. This is typically implemented with HTTP/2 + // ping messages. + int64 keep_alives_sent = 6; + + // The last time a stream was created by this endpoint. Usually unset for + // servers. + google.protobuf.Timestamp last_local_stream_created_timestamp = 7; + // The last time a stream was created by the remote endpoint. Usually unset + // for clients. + google.protobuf.Timestamp last_remote_stream_created_timestamp = 8; + + // The last time a message was sent by this endpoint. + google.protobuf.Timestamp last_message_sent_timestamp = 9; + // The last time a message was received by this endpoint. + google.protobuf.Timestamp last_message_received_timestamp = 10; + + // The amount of window, granted to the local endpoint by the remote endpoint. + // This may be slightly out of date due to network latency. This does NOT + // include stream level or TCP level flow control info. + google.protobuf.Int64Value local_flow_control_window = 11; + + // The amount of window, granted to the remote endpoint by the local endpoint. + // This may be slightly out of date due to network latency. This does NOT + // include stream level or TCP level flow control info. + google.protobuf.Int64Value remote_flow_control_window = 12; + + // Socket options set on this socket. May be absent if 'summary' is set + // on GetSocketRequest. + repeated SocketOption option = 13; +} + +// Address represents the address used to create the socket. +message Address { + message TcpIpAddress { + // Either the IPv4 or IPv6 address in bytes. Will be either 4 bytes or 16 + // bytes in length. + bytes ip_address = 1; + // 0-64k, or -1 if not appropriate. + int32 port = 2; + } + // A Unix Domain Socket address. + message UdsAddress { + string filename = 1; + } + // An address type not included above. + message OtherAddress { + // The human readable version of the value. This value should be set. + string name = 1; + // The actual address message. + google.protobuf.Any value = 2; + } + + oneof address { + TcpIpAddress tcpip_address = 1; + UdsAddress uds_address = 2; + OtherAddress other_address = 3; + } +} + +// Security represents details about how secure the socket is. +message Security { + message Tls { + oneof cipher_suite { + // The cipher suite name in the RFC 4346 format: + // https://tools.ietf.org/html/rfc4346#appendix-C + string standard_name = 1; + // Some other way to describe the cipher suite if + // the RFC 4346 name is not available. + string other_name = 2; + } + // the certificate used by this endpoint. + bytes local_certificate = 3; + // the certificate used by the remote endpoint. + bytes remote_certificate = 4; + } + message OtherSecurity { + // The human readable version of the value. + string name = 1; + // The actual security details message. + google.protobuf.Any value = 2; + } + oneof model { + Tls tls = 1; + OtherSecurity other = 2; + } +} + +// SocketOption represents socket options for a socket. Specifically, these +// are the options returned by getsockopt(). +message SocketOption { + // The full name of the socket option. Typically this will be the upper case + // name, such as "SO_REUSEPORT". + string name = 1; + // The human readable value of this socket option. At least one of value or + // additional will be set. + string value = 2; + // Additional data associated with the socket option. At least one of value + // or additional will be set. + google.protobuf.Any additional = 3; +} + +// For use with SocketOption's additional field. This is primarily used for +// SO_RCVTIMEO and SO_SNDTIMEO +message SocketOptionTimeout { + google.protobuf.Duration duration = 1; +} + +// For use with SocketOption's additional field. This is primarily used for +// SO_LINGER. +message SocketOptionLinger { + // active maps to `struct linger.l_onoff` + bool active = 1; + // duration maps to `struct linger.l_linger` + google.protobuf.Duration duration = 2; +} + +// For use with SocketOption's additional field. Tcp info for +// SOL_TCP and TCP_INFO. +message SocketOptionTcpInfo { + uint32 tcpi_state = 1; + + uint32 tcpi_ca_state = 2; + uint32 tcpi_retransmits = 3; + uint32 tcpi_probes = 4; + uint32 tcpi_backoff = 5; + uint32 tcpi_options = 6; + uint32 tcpi_snd_wscale = 7; + uint32 tcpi_rcv_wscale = 8; + + uint32 tcpi_rto = 9; + uint32 tcpi_ato = 10; + uint32 tcpi_snd_mss = 11; + uint32 tcpi_rcv_mss = 12; + + uint32 tcpi_unacked = 13; + uint32 tcpi_sacked = 14; + uint32 tcpi_lost = 15; + uint32 tcpi_retrans = 16; + uint32 tcpi_fackets = 17; + + uint32 tcpi_last_data_sent = 18; + uint32 tcpi_last_ack_sent = 19; + uint32 tcpi_last_data_recv = 20; + uint32 tcpi_last_ack_recv = 21; + + uint32 tcpi_pmtu = 22; + uint32 tcpi_rcv_ssthresh = 23; + uint32 tcpi_rtt = 24; + uint32 tcpi_rttvar = 25; + uint32 tcpi_snd_ssthresh = 26; + uint32 tcpi_snd_cwnd = 27; + uint32 tcpi_advmss = 28; + uint32 tcpi_reordering = 29; +} + +// Channelz is a service exposed by gRPC servers that provides detailed debug +// information. +service Channelz { + // Gets all root channels (i.e. channels the application has directly + // created). This does not include subchannels nor non-top level channels. + rpc GetTopChannels(GetTopChannelsRequest) returns (GetTopChannelsResponse); + // Gets all servers that exist in the process. + rpc GetServers(GetServersRequest) returns (GetServersResponse); + // Returns a single Server, or else a NOT_FOUND code. + rpc GetServer(GetServerRequest) returns (GetServerResponse); + // Gets all server sockets that exist in the process. + rpc GetServerSockets(GetServerSocketsRequest) returns (GetServerSocketsResponse); + // Returns a single Channel, or else a NOT_FOUND code. + rpc GetChannel(GetChannelRequest) returns (GetChannelResponse); + // Returns a single Subchannel, or else a NOT_FOUND code. + rpc GetSubchannel(GetSubchannelRequest) returns (GetSubchannelResponse); + // Returns a single Socket or else a NOT_FOUND code. + rpc GetSocket(GetSocketRequest) returns (GetSocketResponse); +} + +message GetTopChannelsRequest { + // start_channel_id indicates that only channels at or above this id should be + // included in the results. + // To request the first page, this should be set to 0. To request + // subsequent pages, the client generates this value by adding 1 to + // the highest seen result ID. + int64 start_channel_id = 1; + + // If non-zero, the server will return a page of results containing + // at most this many items. If zero, the server will choose a + // reasonable page size. Must never be negative. + int64 max_results = 2; +} + +message GetTopChannelsResponse { + // list of channels that the connection detail service knows about. Sorted in + // ascending channel_id order. + // Must contain at least 1 result, otherwise 'end' must be true. + repeated Channel channel = 1; + // If set, indicates that the list of channels is the final list. Requesting + // more channels can only return more if they are created after this RPC + // completes. + bool end = 2; +} + +message GetServersRequest { + // start_server_id indicates that only servers at or above this id should be + // included in the results. + // To request the first page, this must be set to 0. To request + // subsequent pages, the client generates this value by adding 1 to + // the highest seen result ID. + int64 start_server_id = 1; + + // If non-zero, the server will return a page of results containing + // at most this many items. If zero, the server will choose a + // reasonable page size. Must never be negative. + int64 max_results = 2; +} + +message GetServersResponse { + // list of servers that the connection detail service knows about. Sorted in + // ascending server_id order. + // Must contain at least 1 result, otherwise 'end' must be true. + repeated Server server = 1; + // If set, indicates that the list of servers is the final list. Requesting + // more servers will only return more if they are created after this RPC + // completes. + bool end = 2; +} + +message GetServerRequest { + // server_id is the identifier of the specific server to get. + int64 server_id = 1; +} + +message GetServerResponse { + // The Server that corresponds to the requested server_id. This field + // should be set. + Server server = 1; +} + +message GetServerSocketsRequest { + int64 server_id = 1; + // start_socket_id indicates that only sockets at or above this id should be + // included in the results. + // To request the first page, this must be set to 0. To request + // subsequent pages, the client generates this value by adding 1 to + // the highest seen result ID. + int64 start_socket_id = 2; + + // If non-zero, the server will return a page of results containing + // at most this many items. If zero, the server will choose a + // reasonable page size. Must never be negative. + int64 max_results = 3; +} + +message GetServerSocketsResponse { + // list of socket refs that the connection detail service knows about. Sorted in + // ascending socket_id order. + // Must contain at least 1 result, otherwise 'end' must be true. + repeated SocketRef socket_ref = 1; + // If set, indicates that the list of sockets is the final list. Requesting + // more sockets will only return more if they are created after this RPC + // completes. + bool end = 2; +} + +message GetChannelRequest { + // channel_id is the identifier of the specific channel to get. + int64 channel_id = 1; +} + +message GetChannelResponse { + // The Channel that corresponds to the requested channel_id. This field + // should be set. + Channel channel = 1; +} + +message GetSubchannelRequest { + // subchannel_id is the identifier of the specific subchannel to get. + int64 subchannel_id = 1; +} + +message GetSubchannelResponse { + // The Subchannel that corresponds to the requested subchannel_id. This + // field should be set. + Subchannel subchannel = 1; +} + +message GetSocketRequest { + // socket_id is the identifier of the specific socket to get. + int64 socket_id = 1; + + // If true, the response will contain only high level information + // that is inexpensive to obtain. Fields thay may be omitted are + // documented. + bool summary = 2; +} + +message GetSocketResponse { + // The Socket that corresponds to the requested socket_id. This field + // should be set. + Socket socket = 1; +} \ No newline at end of file diff --git a/packages/grpc-js/src/generated/channelz.ts b/packages/grpc-js/src/generated/channelz.ts new file mode 100644 index 000000000..367cf27f6 --- /dev/null +++ b/packages/grpc-js/src/generated/channelz.ts @@ -0,0 +1,73 @@ +import type * as grpc from '../index'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { ChannelzClient as _grpc_channelz_v1_ChannelzClient, ChannelzDefinition as _grpc_channelz_v1_ChannelzDefinition } from './grpc/channelz/v1/Channelz'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: MessageTypeDefinition + FloatValue: MessageTypeDefinition + Int32Value: MessageTypeDefinition + Int64Value: MessageTypeDefinition + StringValue: MessageTypeDefinition + Timestamp: MessageTypeDefinition + UInt32Value: MessageTypeDefinition + UInt64Value: MessageTypeDefinition + } + } + grpc: { + channelz: { + v1: { + Address: MessageTypeDefinition + Channel: MessageTypeDefinition + ChannelConnectivityState: MessageTypeDefinition + ChannelData: MessageTypeDefinition + ChannelRef: MessageTypeDefinition + ChannelTrace: MessageTypeDefinition + ChannelTraceEvent: MessageTypeDefinition + /** + * Channelz is a service exposed by gRPC servers that provides detailed debug + * information. + */ + Channelz: SubtypeConstructor & { service: _grpc_channelz_v1_ChannelzDefinition } + GetChannelRequest: MessageTypeDefinition + GetChannelResponse: MessageTypeDefinition + GetServerRequest: MessageTypeDefinition + GetServerResponse: MessageTypeDefinition + GetServerSocketsRequest: MessageTypeDefinition + GetServerSocketsResponse: MessageTypeDefinition + GetServersRequest: MessageTypeDefinition + GetServersResponse: MessageTypeDefinition + GetSocketRequest: MessageTypeDefinition + GetSocketResponse: MessageTypeDefinition + GetSubchannelRequest: MessageTypeDefinition + GetSubchannelResponse: MessageTypeDefinition + GetTopChannelsRequest: MessageTypeDefinition + GetTopChannelsResponse: MessageTypeDefinition + Security: MessageTypeDefinition + Server: MessageTypeDefinition + ServerData: MessageTypeDefinition + ServerRef: MessageTypeDefinition + Socket: MessageTypeDefinition + SocketData: MessageTypeDefinition + SocketOption: MessageTypeDefinition + SocketOptionLinger: MessageTypeDefinition + SocketOptionTcpInfo: MessageTypeDefinition + SocketOptionTimeout: MessageTypeDefinition + SocketRef: MessageTypeDefinition + Subchannel: MessageTypeDefinition + SubchannelRef: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js/src/generated/google/protobuf/Any.ts b/packages/grpc-js/src/generated/google/protobuf/Any.ts new file mode 100644 index 000000000..fcaa6724e --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Any.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { AnyExtension } from '@grpc/proto-loader'; + +export type Any = AnyExtension | { + type_url: string; + value: Buffer | Uint8Array | string; +} + +export interface Any__Output { + 'type_url': (string); + 'value': (Buffer); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/BoolValue.ts b/packages/grpc-js/src/generated/google/protobuf/BoolValue.ts new file mode 100644 index 000000000..86507eaf1 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/BoolValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface BoolValue { + 'value'?: (boolean); +} + +export interface BoolValue__Output { + 'value': (boolean); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/BytesValue.ts b/packages/grpc-js/src/generated/google/protobuf/BytesValue.ts new file mode 100644 index 000000000..9cec76f71 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/BytesValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface BytesValue { + 'value'?: (Buffer | Uint8Array | string); +} + +export interface BytesValue__Output { + 'value': (Buffer); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/DoubleValue.ts b/packages/grpc-js/src/generated/google/protobuf/DoubleValue.ts new file mode 100644 index 000000000..d70b303c2 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/DoubleValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface DoubleValue { + 'value'?: (number | string); +} + +export interface DoubleValue__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Duration.ts b/packages/grpc-js/src/generated/google/protobuf/Duration.ts new file mode 100644 index 000000000..8595377a0 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Duration.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Duration { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Duration__Output { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/FloatValue.ts b/packages/grpc-js/src/generated/google/protobuf/FloatValue.ts new file mode 100644 index 000000000..54a655fbb --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/FloatValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface FloatValue { + 'value'?: (number | string); +} + +export interface FloatValue__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Int32Value.ts b/packages/grpc-js/src/generated/google/protobuf/Int32Value.ts new file mode 100644 index 000000000..ec4eeb7ec --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Int32Value.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface Int32Value { + 'value'?: (number); +} + +export interface Int32Value__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Int64Value.ts b/packages/grpc-js/src/generated/google/protobuf/Int64Value.ts new file mode 100644 index 000000000..f7375196d --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Int64Value.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Int64Value { + 'value'?: (number | string | Long); +} + +export interface Int64Value__Output { + 'value': (string); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/StringValue.ts b/packages/grpc-js/src/generated/google/protobuf/StringValue.ts new file mode 100644 index 000000000..673090e3f --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/StringValue.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface StringValue { + 'value'?: (string); +} + +export interface StringValue__Output { + 'value': (string); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/Timestamp.ts b/packages/grpc-js/src/generated/google/protobuf/Timestamp.ts new file mode 100644 index 000000000..ceaa32b5f --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/Timestamp.ts @@ -0,0 +1,13 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface Timestamp { + 'seconds'?: (number | string | Long); + 'nanos'?: (number); +} + +export interface Timestamp__Output { + 'seconds': (string); + 'nanos': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/UInt32Value.ts b/packages/grpc-js/src/generated/google/protobuf/UInt32Value.ts new file mode 100644 index 000000000..973ab34a5 --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/UInt32Value.ts @@ -0,0 +1,10 @@ +// Original file: null + + +export interface UInt32Value { + 'value'?: (number); +} + +export interface UInt32Value__Output { + 'value': (number); +} diff --git a/packages/grpc-js/src/generated/google/protobuf/UInt64Value.ts b/packages/grpc-js/src/generated/google/protobuf/UInt64Value.ts new file mode 100644 index 000000000..7a85c39ce --- /dev/null +++ b/packages/grpc-js/src/generated/google/protobuf/UInt64Value.ts @@ -0,0 +1,11 @@ +// Original file: null + +import type { Long } from '@grpc/proto-loader'; + +export interface UInt64Value { + 'value'?: (number | string | Long); +} + +export interface UInt64Value__Output { + 'value': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Address.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Address.ts new file mode 100644 index 000000000..259cfeabe --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Address.ts @@ -0,0 +1,89 @@ +// Original file: proto/channelz.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * An address type not included above. + */ +export interface _grpc_channelz_v1_Address_OtherAddress { + /** + * The human readable version of the value. This value should be set. + */ + 'name'?: (string); + /** + * The actual address message. + */ + 'value'?: (_google_protobuf_Any | null); +} + +/** + * An address type not included above. + */ +export interface _grpc_channelz_v1_Address_OtherAddress__Output { + /** + * The human readable version of the value. This value should be set. + */ + 'name': (string); + /** + * The actual address message. + */ + 'value': (_google_protobuf_Any__Output | null); +} + +export interface _grpc_channelz_v1_Address_TcpIpAddress { + /** + * Either the IPv4 or IPv6 address in bytes. Will be either 4 bytes or 16 + * bytes in length. + */ + 'ip_address'?: (Buffer | Uint8Array | string); + /** + * 0-64k, or -1 if not appropriate. + */ + 'port'?: (number); +} + +export interface _grpc_channelz_v1_Address_TcpIpAddress__Output { + /** + * Either the IPv4 or IPv6 address in bytes. Will be either 4 bytes or 16 + * bytes in length. + */ + 'ip_address': (Buffer); + /** + * 0-64k, or -1 if not appropriate. + */ + 'port': (number); +} + +/** + * A Unix Domain Socket address. + */ +export interface _grpc_channelz_v1_Address_UdsAddress { + 'filename'?: (string); +} + +/** + * A Unix Domain Socket address. + */ +export interface _grpc_channelz_v1_Address_UdsAddress__Output { + 'filename': (string); +} + +/** + * Address represents the address used to create the socket. + */ +export interface Address { + 'tcpip_address'?: (_grpc_channelz_v1_Address_TcpIpAddress | null); + 'uds_address'?: (_grpc_channelz_v1_Address_UdsAddress | null); + 'other_address'?: (_grpc_channelz_v1_Address_OtherAddress | null); + 'address'?: "tcpip_address"|"uds_address"|"other_address"; +} + +/** + * Address represents the address used to create the socket. + */ +export interface Address__Output { + 'tcpip_address'?: (_grpc_channelz_v1_Address_TcpIpAddress__Output | null); + 'uds_address'?: (_grpc_channelz_v1_Address_UdsAddress__Output | null); + 'other_address'?: (_grpc_channelz_v1_Address_OtherAddress__Output | null); + 'address': "tcpip_address"|"uds_address"|"other_address"; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Channel.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Channel.ts new file mode 100644 index 000000000..93b4a261d --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Channel.ts @@ -0,0 +1,68 @@ +// Original file: proto/channelz.proto + +import type { ChannelRef as _grpc_channelz_v1_ChannelRef, ChannelRef__Output as _grpc_channelz_v1_ChannelRef__Output } from '../../../grpc/channelz/v1/ChannelRef'; +import type { ChannelData as _grpc_channelz_v1_ChannelData, ChannelData__Output as _grpc_channelz_v1_ChannelData__Output } from '../../../grpc/channelz/v1/ChannelData'; +import type { SubchannelRef as _grpc_channelz_v1_SubchannelRef, SubchannelRef__Output as _grpc_channelz_v1_SubchannelRef__Output } from '../../../grpc/channelz/v1/SubchannelRef'; +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +/** + * Channel is a logical grouping of channels, subchannels, and sockets. + */ +export interface Channel { + /** + * The identifier for this channel. This should bet set. + */ + 'ref'?: (_grpc_channelz_v1_ChannelRef | null); + /** + * Data specific to this channel. + */ + 'data'?: (_grpc_channelz_v1_ChannelData | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref'?: (_grpc_channelz_v1_SocketRef)[]; +} + +/** + * Channel is a logical grouping of channels, subchannels, and sockets. + */ +export interface Channel__Output { + /** + * The identifier for this channel. This should bet set. + */ + 'ref': (_grpc_channelz_v1_ChannelRef__Output | null); + /** + * Data specific to this channel. + */ + 'data': (_grpc_channelz_v1_ChannelData__Output | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref': (_grpc_channelz_v1_ChannelRef__Output)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref': (_grpc_channelz_v1_SubchannelRef__Output)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref': (_grpc_channelz_v1_SocketRef__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelConnectivityState.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelConnectivityState.ts new file mode 100644 index 000000000..be34ab98d --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelConnectivityState.ts @@ -0,0 +1,29 @@ +// Original file: proto/channelz.proto + + +// Original file: proto/channelz.proto + +export enum _grpc_channelz_v1_ChannelConnectivityState_State { + UNKNOWN = 0, + IDLE = 1, + CONNECTING = 2, + READY = 3, + TRANSIENT_FAILURE = 4, + SHUTDOWN = 5, +} + +/** + * These come from the specified states in this document: + * https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md + */ +export interface ChannelConnectivityState { + 'state'?: (_grpc_channelz_v1_ChannelConnectivityState_State | keyof typeof _grpc_channelz_v1_ChannelConnectivityState_State); +} + +/** + * These come from the specified states in this document: + * https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md + */ +export interface ChannelConnectivityState__Output { + 'state': (keyof typeof _grpc_channelz_v1_ChannelConnectivityState_State); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelData.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelData.ts new file mode 100644 index 000000000..6d6824af4 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelData.ts @@ -0,0 +1,76 @@ +// Original file: proto/channelz.proto + +import type { ChannelConnectivityState as _grpc_channelz_v1_ChannelConnectivityState, ChannelConnectivityState__Output as _grpc_channelz_v1_ChannelConnectivityState__Output } from '../../../grpc/channelz/v1/ChannelConnectivityState'; +import type { ChannelTrace as _grpc_channelz_v1_ChannelTrace, ChannelTrace__Output as _grpc_channelz_v1_ChannelTrace__Output } from '../../../grpc/channelz/v1/ChannelTrace'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Channel data is data related to a specific Channel or Subchannel. + */ +export interface ChannelData { + /** + * The connectivity state of the channel or subchannel. Implementations + * should always set this. + */ + 'state'?: (_grpc_channelz_v1_ChannelConnectivityState | null); + /** + * The target this channel originally tried to connect to. May be absent + */ + 'target'?: (string); + /** + * A trace of recent events on the channel. May be absent. + */ + 'trace'?: (_grpc_channelz_v1_ChannelTrace | null); + /** + * The number of calls started on the channel + */ + 'calls_started'?: (number | string | Long); + /** + * The number of calls that have completed with an OK status + */ + 'calls_succeeded'?: (number | string | Long); + /** + * The number of calls that have completed with a non-OK status + */ + 'calls_failed'?: (number | string | Long); + /** + * The last time a call was started on the channel. + */ + 'last_call_started_timestamp'?: (_google_protobuf_Timestamp | null); +} + +/** + * Channel data is data related to a specific Channel or Subchannel. + */ +export interface ChannelData__Output { + /** + * The connectivity state of the channel or subchannel. Implementations + * should always set this. + */ + 'state': (_grpc_channelz_v1_ChannelConnectivityState__Output | null); + /** + * The target this channel originally tried to connect to. May be absent + */ + 'target': (string); + /** + * A trace of recent events on the channel. May be absent. + */ + 'trace': (_grpc_channelz_v1_ChannelTrace__Output | null); + /** + * The number of calls started on the channel + */ + 'calls_started': (string); + /** + * The number of calls that have completed with an OK status + */ + 'calls_succeeded': (string); + /** + * The number of calls that have completed with a non-OK status + */ + 'calls_failed': (string); + /** + * The last time a call was started on the channel. + */ + 'last_call_started_timestamp': (_google_protobuf_Timestamp__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelRef.ts new file mode 100644 index 000000000..231d00876 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * ChannelRef is a reference to a Channel. + */ +export interface ChannelRef { + /** + * The globally unique id for this channel. Must be a positive number. + */ + 'channel_id'?: (number | string | Long); + /** + * An optional name associated with the channel. + */ + 'name'?: (string); +} + +/** + * ChannelRef is a reference to a Channel. + */ +export interface ChannelRef__Output { + /** + * The globally unique id for this channel. Must be a positive number. + */ + 'channel_id': (string); + /** + * An optional name associated with the channel. + */ + 'name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTrace.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTrace.ts new file mode 100644 index 000000000..7dbc8d924 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTrace.ts @@ -0,0 +1,45 @@ +// Original file: proto/channelz.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { ChannelTraceEvent as _grpc_channelz_v1_ChannelTraceEvent, ChannelTraceEvent__Output as _grpc_channelz_v1_ChannelTraceEvent__Output } from '../../../grpc/channelz/v1/ChannelTraceEvent'; +import type { Long } from '@grpc/proto-loader'; + +/** + * ChannelTrace represents the recent events that have occurred on the channel. + */ +export interface ChannelTrace { + /** + * Number of events ever logged in this tracing object. This can differ from + * events.size() because events can be overwritten or garbage collected by + * implementations. + */ + 'num_events_logged'?: (number | string | Long); + /** + * Time that this channel was created. + */ + 'creation_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * List of events that have occurred on this channel. + */ + 'events'?: (_grpc_channelz_v1_ChannelTraceEvent)[]; +} + +/** + * ChannelTrace represents the recent events that have occurred on the channel. + */ +export interface ChannelTrace__Output { + /** + * Number of events ever logged in this tracing object. This can differ from + * events.size() because events can be overwritten or garbage collected by + * implementations. + */ + 'num_events_logged': (string); + /** + * Time that this channel was created. + */ + 'creation_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * List of events that have occurred on this channel. + */ + 'events': (_grpc_channelz_v1_ChannelTraceEvent__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTraceEvent.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTraceEvent.ts new file mode 100644 index 000000000..24b97fbcd --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ChannelTraceEvent.ts @@ -0,0 +1,73 @@ +// Original file: proto/channelz.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { ChannelRef as _grpc_channelz_v1_ChannelRef, ChannelRef__Output as _grpc_channelz_v1_ChannelRef__Output } from '../../../grpc/channelz/v1/ChannelRef'; +import type { SubchannelRef as _grpc_channelz_v1_SubchannelRef, SubchannelRef__Output as _grpc_channelz_v1_SubchannelRef__Output } from '../../../grpc/channelz/v1/SubchannelRef'; + +// Original file: proto/channelz.proto + +/** + * The supported severity levels of trace events. + */ +export enum _grpc_channelz_v1_ChannelTraceEvent_Severity { + CT_UNKNOWN = 0, + CT_INFO = 1, + CT_WARNING = 2, + CT_ERROR = 3, +} + +/** + * A trace event is an interesting thing that happened to a channel or + * subchannel, such as creation, address resolution, subchannel creation, etc. + */ +export interface ChannelTraceEvent { + /** + * High level description of the event. + */ + 'description'?: (string); + /** + * the severity of the trace event + */ + 'severity'?: (_grpc_channelz_v1_ChannelTraceEvent_Severity | keyof typeof _grpc_channelz_v1_ChannelTraceEvent_Severity); + /** + * When this event occurred. + */ + 'timestamp'?: (_google_protobuf_Timestamp | null); + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef | null); + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef | null); + /** + * ref of referenced channel or subchannel. + * Optional, only present if this event refers to a child object. For example, + * this field would be filled if this trace event was for a subchannel being + * created. + */ + 'child_ref'?: "channel_ref"|"subchannel_ref"; +} + +/** + * A trace event is an interesting thing that happened to a channel or + * subchannel, such as creation, address resolution, subchannel creation, etc. + */ +export interface ChannelTraceEvent__Output { + /** + * High level description of the event. + */ + 'description': (string); + /** + * the severity of the trace event + */ + 'severity': (keyof typeof _grpc_channelz_v1_ChannelTraceEvent_Severity); + /** + * When this event occurred. + */ + 'timestamp': (_google_protobuf_Timestamp__Output | null); + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef__Output | null); + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef__Output | null); + /** + * ref of referenced channel or subchannel. + * Optional, only present if this event refers to a child object. For example, + * this field would be filled if this trace event was for a subchannel being + * created. + */ + 'child_ref': "channel_ref"|"subchannel_ref"; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts new file mode 100644 index 000000000..ace712454 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts @@ -0,0 +1,178 @@ +// Original file: proto/channelz.proto + +import type * as grpc from '../../../../index' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { GetChannelRequest as _grpc_channelz_v1_GetChannelRequest, GetChannelRequest__Output as _grpc_channelz_v1_GetChannelRequest__Output } from '../../../grpc/channelz/v1/GetChannelRequest'; +import type { GetChannelResponse as _grpc_channelz_v1_GetChannelResponse, GetChannelResponse__Output as _grpc_channelz_v1_GetChannelResponse__Output } from '../../../grpc/channelz/v1/GetChannelResponse'; +import type { GetServerRequest as _grpc_channelz_v1_GetServerRequest, GetServerRequest__Output as _grpc_channelz_v1_GetServerRequest__Output } from '../../../grpc/channelz/v1/GetServerRequest'; +import type { GetServerResponse as _grpc_channelz_v1_GetServerResponse, GetServerResponse__Output as _grpc_channelz_v1_GetServerResponse__Output } from '../../../grpc/channelz/v1/GetServerResponse'; +import type { GetServerSocketsRequest as _grpc_channelz_v1_GetServerSocketsRequest, GetServerSocketsRequest__Output as _grpc_channelz_v1_GetServerSocketsRequest__Output } from '../../../grpc/channelz/v1/GetServerSocketsRequest'; +import type { GetServerSocketsResponse as _grpc_channelz_v1_GetServerSocketsResponse, GetServerSocketsResponse__Output as _grpc_channelz_v1_GetServerSocketsResponse__Output } from '../../../grpc/channelz/v1/GetServerSocketsResponse'; +import type { GetServersRequest as _grpc_channelz_v1_GetServersRequest, GetServersRequest__Output as _grpc_channelz_v1_GetServersRequest__Output } from '../../../grpc/channelz/v1/GetServersRequest'; +import type { GetServersResponse as _grpc_channelz_v1_GetServersResponse, GetServersResponse__Output as _grpc_channelz_v1_GetServersResponse__Output } from '../../../grpc/channelz/v1/GetServersResponse'; +import type { GetSocketRequest as _grpc_channelz_v1_GetSocketRequest, GetSocketRequest__Output as _grpc_channelz_v1_GetSocketRequest__Output } from '../../../grpc/channelz/v1/GetSocketRequest'; +import type { GetSocketResponse as _grpc_channelz_v1_GetSocketResponse, GetSocketResponse__Output as _grpc_channelz_v1_GetSocketResponse__Output } from '../../../grpc/channelz/v1/GetSocketResponse'; +import type { GetSubchannelRequest as _grpc_channelz_v1_GetSubchannelRequest, GetSubchannelRequest__Output as _grpc_channelz_v1_GetSubchannelRequest__Output } from '../../../grpc/channelz/v1/GetSubchannelRequest'; +import type { GetSubchannelResponse as _grpc_channelz_v1_GetSubchannelResponse, GetSubchannelResponse__Output as _grpc_channelz_v1_GetSubchannelResponse__Output } from '../../../grpc/channelz/v1/GetSubchannelResponse'; +import type { GetTopChannelsRequest as _grpc_channelz_v1_GetTopChannelsRequest, GetTopChannelsRequest__Output as _grpc_channelz_v1_GetTopChannelsRequest__Output } from '../../../grpc/channelz/v1/GetTopChannelsRequest'; +import type { GetTopChannelsResponse as _grpc_channelz_v1_GetTopChannelsResponse, GetTopChannelsResponse__Output as _grpc_channelz_v1_GetTopChannelsResponse__Output } from '../../../grpc/channelz/v1/GetTopChannelsResponse'; + +/** + * Channelz is a service exposed by gRPC servers that provides detailed debug + * information. + */ +export interface ChannelzClient extends grpc.Client { + /** + * Returns a single Channel, or else a NOT_FOUND code. + */ + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * Returns a single Server, or else a NOT_FOUND code. + */ + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Returns a single Server, or else a NOT_FOUND code. + */ + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * Gets all server sockets that exist in the process. + */ + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Gets all server sockets that exist in the process. + */ + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * Gets all servers that exist in the process. + */ + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Gets all servers that exist in the process. + */ + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * Returns a single Socket or else a NOT_FOUND code. + */ + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Returns a single Socket or else a NOT_FOUND code. + */ + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * Returns a single Subchannel, or else a NOT_FOUND code. + */ + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Returns a single Subchannel, or else a NOT_FOUND code. + */ + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + + /** + * Gets all root channels (i.e. channels the application has directly + * created). This does not include subchannels nor non-top level channels. + */ + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + /** + * Gets all root channels (i.e. channels the application has directly + * created). This does not include subchannels nor non-top level channels. + */ + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + +} + +/** + * Channelz is a service exposed by gRPC servers that provides detailed debug + * information. + */ +export interface ChannelzHandlers extends grpc.UntypedServiceImplementation { + /** + * Returns a single Channel, or else a NOT_FOUND code. + */ + GetChannel: grpc.handleUnaryCall<_grpc_channelz_v1_GetChannelRequest__Output, _grpc_channelz_v1_GetChannelResponse>; + + /** + * Returns a single Server, or else a NOT_FOUND code. + */ + GetServer: grpc.handleUnaryCall<_grpc_channelz_v1_GetServerRequest__Output, _grpc_channelz_v1_GetServerResponse>; + + /** + * Gets all server sockets that exist in the process. + */ + GetServerSockets: grpc.handleUnaryCall<_grpc_channelz_v1_GetServerSocketsRequest__Output, _grpc_channelz_v1_GetServerSocketsResponse>; + + /** + * Gets all servers that exist in the process. + */ + GetServers: grpc.handleUnaryCall<_grpc_channelz_v1_GetServersRequest__Output, _grpc_channelz_v1_GetServersResponse>; + + /** + * Returns a single Socket or else a NOT_FOUND code. + */ + GetSocket: grpc.handleUnaryCall<_grpc_channelz_v1_GetSocketRequest__Output, _grpc_channelz_v1_GetSocketResponse>; + + /** + * Returns a single Subchannel, or else a NOT_FOUND code. + */ + GetSubchannel: grpc.handleUnaryCall<_grpc_channelz_v1_GetSubchannelRequest__Output, _grpc_channelz_v1_GetSubchannelResponse>; + + /** + * Gets all root channels (i.e. channels the application has directly + * created). This does not include subchannels nor non-top level channels. + */ + GetTopChannels: grpc.handleUnaryCall<_grpc_channelz_v1_GetTopChannelsRequest__Output, _grpc_channelz_v1_GetTopChannelsResponse>; + +} + +export interface ChannelzDefinition extends grpc.ServiceDefinition { + GetChannel: MethodDefinition<_grpc_channelz_v1_GetChannelRequest, _grpc_channelz_v1_GetChannelResponse, _grpc_channelz_v1_GetChannelRequest__Output, _grpc_channelz_v1_GetChannelResponse__Output> + GetServer: MethodDefinition<_grpc_channelz_v1_GetServerRequest, _grpc_channelz_v1_GetServerResponse, _grpc_channelz_v1_GetServerRequest__Output, _grpc_channelz_v1_GetServerResponse__Output> + GetServerSockets: MethodDefinition<_grpc_channelz_v1_GetServerSocketsRequest, _grpc_channelz_v1_GetServerSocketsResponse, _grpc_channelz_v1_GetServerSocketsRequest__Output, _grpc_channelz_v1_GetServerSocketsResponse__Output> + GetServers: MethodDefinition<_grpc_channelz_v1_GetServersRequest, _grpc_channelz_v1_GetServersResponse, _grpc_channelz_v1_GetServersRequest__Output, _grpc_channelz_v1_GetServersResponse__Output> + GetSocket: MethodDefinition<_grpc_channelz_v1_GetSocketRequest, _grpc_channelz_v1_GetSocketResponse, _grpc_channelz_v1_GetSocketRequest__Output, _grpc_channelz_v1_GetSocketResponse__Output> + GetSubchannel: MethodDefinition<_grpc_channelz_v1_GetSubchannelRequest, _grpc_channelz_v1_GetSubchannelResponse, _grpc_channelz_v1_GetSubchannelRequest__Output, _grpc_channelz_v1_GetSubchannelResponse__Output> + GetTopChannels: MethodDefinition<_grpc_channelz_v1_GetTopChannelsRequest, _grpc_channelz_v1_GetTopChannelsResponse, _grpc_channelz_v1_GetTopChannelsRequest__Output, _grpc_channelz_v1_GetTopChannelsResponse__Output> +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelRequest.ts new file mode 100644 index 000000000..437e2d60a --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelRequest.ts @@ -0,0 +1,17 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetChannelRequest { + /** + * channel_id is the identifier of the specific channel to get. + */ + 'channel_id'?: (number | string | Long); +} + +export interface GetChannelRequest__Output { + /** + * channel_id is the identifier of the specific channel to get. + */ + 'channel_id': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelResponse.ts new file mode 100644 index 000000000..2e967a458 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetChannelResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Channel as _grpc_channelz_v1_Channel, Channel__Output as _grpc_channelz_v1_Channel__Output } from '../../../grpc/channelz/v1/Channel'; + +export interface GetChannelResponse { + /** + * The Channel that corresponds to the requested channel_id. This field + * should be set. + */ + 'channel'?: (_grpc_channelz_v1_Channel | null); +} + +export interface GetChannelResponse__Output { + /** + * The Channel that corresponds to the requested channel_id. This field + * should be set. + */ + 'channel': (_grpc_channelz_v1_Channel__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerRequest.ts new file mode 100644 index 000000000..f5d4a298f --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerRequest.ts @@ -0,0 +1,17 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetServerRequest { + /** + * server_id is the identifier of the specific server to get. + */ + 'server_id'?: (number | string | Long); +} + +export interface GetServerRequest__Output { + /** + * server_id is the identifier of the specific server to get. + */ + 'server_id': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerResponse.ts new file mode 100644 index 000000000..fe0078209 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Server as _grpc_channelz_v1_Server, Server__Output as _grpc_channelz_v1_Server__Output } from '../../../grpc/channelz/v1/Server'; + +export interface GetServerResponse { + /** + * The Server that corresponds to the requested server_id. This field + * should be set. + */ + 'server'?: (_grpc_channelz_v1_Server | null); +} + +export interface GetServerResponse__Output { + /** + * The Server that corresponds to the requested server_id. This field + * should be set. + */ + 'server': (_grpc_channelz_v1_Server__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsRequest.ts new file mode 100644 index 000000000..c33056edc --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsRequest.ts @@ -0,0 +1,39 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetServerSocketsRequest { + 'server_id'?: (number | string | Long); + /** + * start_socket_id indicates that only sockets at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_socket_id'?: (number | string | Long); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results'?: (number | string | Long); +} + +export interface GetServerSocketsRequest__Output { + 'server_id': (string); + /** + * start_socket_id indicates that only sockets at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_socket_id': (string); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsResponse.ts new file mode 100644 index 000000000..112f277e3 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServerSocketsResponse.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +export interface GetServerSocketsResponse { + /** + * list of socket refs that the connection detail service knows about. Sorted in + * ascending socket_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'socket_ref'?: (_grpc_channelz_v1_SocketRef)[]; + /** + * If set, indicates that the list of sockets is the final list. Requesting + * more sockets will only return more if they are created after this RPC + * completes. + */ + 'end'?: (boolean); +} + +export interface GetServerSocketsResponse__Output { + /** + * list of socket refs that the connection detail service knows about. Sorted in + * ascending socket_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'socket_ref': (_grpc_channelz_v1_SocketRef__Output)[]; + /** + * If set, indicates that the list of sockets is the final list. Requesting + * more sockets will only return more if they are created after this RPC + * completes. + */ + 'end': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersRequest.ts new file mode 100644 index 000000000..2defea62d --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersRequest.ts @@ -0,0 +1,37 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetServersRequest { + /** + * start_server_id indicates that only servers at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_server_id'?: (number | string | Long); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results'?: (number | string | Long); +} + +export interface GetServersRequest__Output { + /** + * start_server_id indicates that only servers at or above this id should be + * included in the results. + * To request the first page, this must be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_server_id': (string); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersResponse.ts new file mode 100644 index 000000000..b07893b8c --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetServersResponse.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { Server as _grpc_channelz_v1_Server, Server__Output as _grpc_channelz_v1_Server__Output } from '../../../grpc/channelz/v1/Server'; + +export interface GetServersResponse { + /** + * list of servers that the connection detail service knows about. Sorted in + * ascending server_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'server'?: (_grpc_channelz_v1_Server)[]; + /** + * If set, indicates that the list of servers is the final list. Requesting + * more servers will only return more if they are created after this RPC + * completes. + */ + 'end'?: (boolean); +} + +export interface GetServersResponse__Output { + /** + * list of servers that the connection detail service knows about. Sorted in + * ascending server_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'server': (_grpc_channelz_v1_Server__Output)[]; + /** + * If set, indicates that the list of servers is the final list. Requesting + * more servers will only return more if they are created after this RPC + * completes. + */ + 'end': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketRequest.ts new file mode 100644 index 000000000..b3dc1608e --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketRequest.ts @@ -0,0 +1,29 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetSocketRequest { + /** + * socket_id is the identifier of the specific socket to get. + */ + 'socket_id'?: (number | string | Long); + /** + * If true, the response will contain only high level information + * that is inexpensive to obtain. Fields thay may be omitted are + * documented. + */ + 'summary'?: (boolean); +} + +export interface GetSocketRequest__Output { + /** + * socket_id is the identifier of the specific socket to get. + */ + 'socket_id': (string); + /** + * If true, the response will contain only high level information + * that is inexpensive to obtain. Fields thay may be omitted are + * documented. + */ + 'summary': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketResponse.ts new file mode 100644 index 000000000..b6304b7f0 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSocketResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Socket as _grpc_channelz_v1_Socket, Socket__Output as _grpc_channelz_v1_Socket__Output } from '../../../grpc/channelz/v1/Socket'; + +export interface GetSocketResponse { + /** + * The Socket that corresponds to the requested socket_id. This field + * should be set. + */ + 'socket'?: (_grpc_channelz_v1_Socket | null); +} + +export interface GetSocketResponse__Output { + /** + * The Socket that corresponds to the requested socket_id. This field + * should be set. + */ + 'socket': (_grpc_channelz_v1_Socket__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelRequest.ts new file mode 100644 index 000000000..f481a81d2 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelRequest.ts @@ -0,0 +1,17 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetSubchannelRequest { + /** + * subchannel_id is the identifier of the specific subchannel to get. + */ + 'subchannel_id'?: (number | string | Long); +} + +export interface GetSubchannelRequest__Output { + /** + * subchannel_id is the identifier of the specific subchannel to get. + */ + 'subchannel_id': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelResponse.ts new file mode 100644 index 000000000..57d2bf2dc --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetSubchannelResponse.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Subchannel as _grpc_channelz_v1_Subchannel, Subchannel__Output as _grpc_channelz_v1_Subchannel__Output } from '../../../grpc/channelz/v1/Subchannel'; + +export interface GetSubchannelResponse { + /** + * The Subchannel that corresponds to the requested subchannel_id. This + * field should be set. + */ + 'subchannel'?: (_grpc_channelz_v1_Subchannel | null); +} + +export interface GetSubchannelResponse__Output { + /** + * The Subchannel that corresponds to the requested subchannel_id. This + * field should be set. + */ + 'subchannel': (_grpc_channelz_v1_Subchannel__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsRequest.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsRequest.ts new file mode 100644 index 000000000..a122d7a85 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsRequest.ts @@ -0,0 +1,37 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface GetTopChannelsRequest { + /** + * start_channel_id indicates that only channels at or above this id should be + * included in the results. + * To request the first page, this should be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_channel_id'?: (number | string | Long); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results'?: (number | string | Long); +} + +export interface GetTopChannelsRequest__Output { + /** + * start_channel_id indicates that only channels at or above this id should be + * included in the results. + * To request the first page, this should be set to 0. To request + * subsequent pages, the client generates this value by adding 1 to + * the highest seen result ID. + */ + 'start_channel_id': (string); + /** + * If non-zero, the server will return a page of results containing + * at most this many items. If zero, the server will choose a + * reasonable page size. Must never be negative. + */ + 'max_results': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsResponse.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsResponse.ts new file mode 100644 index 000000000..d96e63673 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/GetTopChannelsResponse.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { Channel as _grpc_channelz_v1_Channel, Channel__Output as _grpc_channelz_v1_Channel__Output } from '../../../grpc/channelz/v1/Channel'; + +export interface GetTopChannelsResponse { + /** + * list of channels that the connection detail service knows about. Sorted in + * ascending channel_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'channel'?: (_grpc_channelz_v1_Channel)[]; + /** + * If set, indicates that the list of channels is the final list. Requesting + * more channels can only return more if they are created after this RPC + * completes. + */ + 'end'?: (boolean); +} + +export interface GetTopChannelsResponse__Output { + /** + * list of channels that the connection detail service knows about. Sorted in + * ascending channel_id order. + * Must contain at least 1 result, otherwise 'end' must be true. + */ + 'channel': (_grpc_channelz_v1_Channel__Output)[]; + /** + * If set, indicates that the list of channels is the final list. Requesting + * more channels can only return more if they are created after this RPC + * completes. + */ + 'end': (boolean); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Security.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Security.ts new file mode 100644 index 000000000..e555d698e --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Security.ts @@ -0,0 +1,87 @@ +// Original file: proto/channelz.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +export interface _grpc_channelz_v1_Security_OtherSecurity { + /** + * The human readable version of the value. + */ + 'name'?: (string); + /** + * The actual security details message. + */ + 'value'?: (_google_protobuf_Any | null); +} + +export interface _grpc_channelz_v1_Security_OtherSecurity__Output { + /** + * The human readable version of the value. + */ + 'name': (string); + /** + * The actual security details message. + */ + 'value': (_google_protobuf_Any__Output | null); +} + +export interface _grpc_channelz_v1_Security_Tls { + /** + * The cipher suite name in the RFC 4346 format: + * https://tools.ietf.org/html/rfc4346#appendix-C + */ + 'standard_name'?: (string); + /** + * Some other way to describe the cipher suite if + * the RFC 4346 name is not available. + */ + 'other_name'?: (string); + /** + * the certificate used by this endpoint. + */ + 'local_certificate'?: (Buffer | Uint8Array | string); + /** + * the certificate used by the remote endpoint. + */ + 'remote_certificate'?: (Buffer | Uint8Array | string); + 'cipher_suite'?: "standard_name"|"other_name"; +} + +export interface _grpc_channelz_v1_Security_Tls__Output { + /** + * The cipher suite name in the RFC 4346 format: + * https://tools.ietf.org/html/rfc4346#appendix-C + */ + 'standard_name'?: (string); + /** + * Some other way to describe the cipher suite if + * the RFC 4346 name is not available. + */ + 'other_name'?: (string); + /** + * the certificate used by this endpoint. + */ + 'local_certificate': (Buffer); + /** + * the certificate used by the remote endpoint. + */ + 'remote_certificate': (Buffer); + 'cipher_suite': "standard_name"|"other_name"; +} + +/** + * Security represents details about how secure the socket is. + */ +export interface Security { + 'tls'?: (_grpc_channelz_v1_Security_Tls | null); + 'other'?: (_grpc_channelz_v1_Security_OtherSecurity | null); + 'model'?: "tls"|"other"; +} + +/** + * Security represents details about how secure the socket is. + */ +export interface Security__Output { + 'tls'?: (_grpc_channelz_v1_Security_Tls__Output | null); + 'other'?: (_grpc_channelz_v1_Security_OtherSecurity__Output | null); + 'model': "tls"|"other"; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Server.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Server.ts new file mode 100644 index 000000000..958343358 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Server.ts @@ -0,0 +1,45 @@ +// Original file: proto/channelz.proto + +import type { ServerRef as _grpc_channelz_v1_ServerRef, ServerRef__Output as _grpc_channelz_v1_ServerRef__Output } from '../../../grpc/channelz/v1/ServerRef'; +import type { ServerData as _grpc_channelz_v1_ServerData, ServerData__Output as _grpc_channelz_v1_ServerData__Output } from '../../../grpc/channelz/v1/ServerData'; +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +/** + * Server represents a single server. There may be multiple servers in a single + * program. + */ +export interface Server { + /** + * The identifier for a Server. This should be set. + */ + 'ref'?: (_grpc_channelz_v1_ServerRef | null); + /** + * The associated data of the Server. + */ + 'data'?: (_grpc_channelz_v1_ServerData | null); + /** + * The sockets that the server is listening on. There are no ordering + * guarantees. This may be absent. + */ + 'listen_socket'?: (_grpc_channelz_v1_SocketRef)[]; +} + +/** + * Server represents a single server. There may be multiple servers in a single + * program. + */ +export interface Server__Output { + /** + * The identifier for a Server. This should be set. + */ + 'ref': (_grpc_channelz_v1_ServerRef__Output | null); + /** + * The associated data of the Server. + */ + 'data': (_grpc_channelz_v1_ServerData__Output | null); + /** + * The sockets that the server is listening on. There are no ordering + * guarantees. This may be absent. + */ + 'listen_socket': (_grpc_channelz_v1_SocketRef__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ServerData.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerData.ts new file mode 100644 index 000000000..ce48e36f5 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerData.ts @@ -0,0 +1,57 @@ +// Original file: proto/channelz.proto + +import type { ChannelTrace as _grpc_channelz_v1_ChannelTrace, ChannelTrace__Output as _grpc_channelz_v1_ChannelTrace__Output } from '../../../grpc/channelz/v1/ChannelTrace'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Long } from '@grpc/proto-loader'; + +/** + * ServerData is data for a specific Server. + */ +export interface ServerData { + /** + * A trace of recent events on the server. May be absent. + */ + 'trace'?: (_grpc_channelz_v1_ChannelTrace | null); + /** + * The number of incoming calls started on the server + */ + 'calls_started'?: (number | string | Long); + /** + * The number of incoming calls that have completed with an OK status + */ + 'calls_succeeded'?: (number | string | Long); + /** + * The number of incoming calls that have a completed with a non-OK status + */ + 'calls_failed'?: (number | string | Long); + /** + * The last time a call was started on the server. + */ + 'last_call_started_timestamp'?: (_google_protobuf_Timestamp | null); +} + +/** + * ServerData is data for a specific Server. + */ +export interface ServerData__Output { + /** + * A trace of recent events on the server. May be absent. + */ + 'trace': (_grpc_channelz_v1_ChannelTrace__Output | null); + /** + * The number of incoming calls started on the server + */ + 'calls_started': (string); + /** + * The number of incoming calls that have completed with an OK status + */ + 'calls_succeeded': (string); + /** + * The number of incoming calls that have a completed with a non-OK status + */ + 'calls_failed': (string); + /** + * The last time a call was started on the server. + */ + 'last_call_started_timestamp': (_google_protobuf_Timestamp__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/ServerRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerRef.ts new file mode 100644 index 000000000..389183bdc --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/ServerRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * ServerRef is a reference to a Server. + */ +export interface ServerRef { + /** + * A globally unique identifier for this server. Must be a positive number. + */ + 'server_id'?: (number | string | Long); + /** + * An optional name associated with the server. + */ + 'name'?: (string); +} + +/** + * ServerRef is a reference to a Server. + */ +export interface ServerRef__Output { + /** + * A globally unique identifier for this server. Must be a positive number. + */ + 'server_id': (string); + /** + * An optional name associated with the server. + */ + 'name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Socket.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Socket.ts new file mode 100644 index 000000000..5829afe98 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Socket.ts @@ -0,0 +1,70 @@ +// Original file: proto/channelz.proto + +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; +import type { SocketData as _grpc_channelz_v1_SocketData, SocketData__Output as _grpc_channelz_v1_SocketData__Output } from '../../../grpc/channelz/v1/SocketData'; +import type { Address as _grpc_channelz_v1_Address, Address__Output as _grpc_channelz_v1_Address__Output } from '../../../grpc/channelz/v1/Address'; +import type { Security as _grpc_channelz_v1_Security, Security__Output as _grpc_channelz_v1_Security__Output } from '../../../grpc/channelz/v1/Security'; + +/** + * Information about an actual connection. Pronounced "sock-ay". + */ +export interface Socket { + /** + * The identifier for the Socket. + */ + 'ref'?: (_grpc_channelz_v1_SocketRef | null); + /** + * Data specific to this Socket. + */ + 'data'?: (_grpc_channelz_v1_SocketData | null); + /** + * The locally bound address. + */ + 'local'?: (_grpc_channelz_v1_Address | null); + /** + * The remote bound address. May be absent. + */ + 'remote'?: (_grpc_channelz_v1_Address | null); + /** + * Security details for this socket. May be absent if not available, or + * there is no security on the socket. + */ + 'security'?: (_grpc_channelz_v1_Security | null); + /** + * Optional, represents the name of the remote endpoint, if different than + * the original target name. + */ + 'remote_name'?: (string); +} + +/** + * Information about an actual connection. Pronounced "sock-ay". + */ +export interface Socket__Output { + /** + * The identifier for the Socket. + */ + 'ref': (_grpc_channelz_v1_SocketRef__Output | null); + /** + * Data specific to this Socket. + */ + 'data': (_grpc_channelz_v1_SocketData__Output | null); + /** + * The locally bound address. + */ + 'local': (_grpc_channelz_v1_Address__Output | null); + /** + * The remote bound address. May be absent. + */ + 'remote': (_grpc_channelz_v1_Address__Output | null); + /** + * Security details for this socket. May be absent if not available, or + * there is no security on the socket. + */ + 'security': (_grpc_channelz_v1_Security__Output | null); + /** + * Optional, represents the name of the remote endpoint, if different than + * the original target name. + */ + 'remote_name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketData.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketData.ts new file mode 100644 index 000000000..c62d4d10c --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketData.ts @@ -0,0 +1,150 @@ +// Original file: proto/channelz.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Int64Value as _google_protobuf_Int64Value, Int64Value__Output as _google_protobuf_Int64Value__Output } from '../../../google/protobuf/Int64Value'; +import type { SocketOption as _grpc_channelz_v1_SocketOption, SocketOption__Output as _grpc_channelz_v1_SocketOption__Output } from '../../../grpc/channelz/v1/SocketOption'; +import type { Long } from '@grpc/proto-loader'; + +/** + * SocketData is data associated for a specific Socket. The fields present + * are specific to the implementation, so there may be minor differences in + * the semantics. (e.g. flow control windows) + */ +export interface SocketData { + /** + * The number of streams that have been started. + */ + 'streams_started'?: (number | string | Long); + /** + * The number of streams that have ended successfully: + * On client side, received frame with eos bit set; + * On server side, sent frame with eos bit set. + */ + 'streams_succeeded'?: (number | string | Long); + /** + * The number of streams that have ended unsuccessfully: + * On client side, ended without receiving frame with eos bit set; + * On server side, ended without sending frame with eos bit set. + */ + 'streams_failed'?: (number | string | Long); + /** + * The number of grpc messages successfully sent on this socket. + */ + 'messages_sent'?: (number | string | Long); + /** + * The number of grpc messages received on this socket. + */ + 'messages_received'?: (number | string | Long); + /** + * The number of keep alives sent. This is typically implemented with HTTP/2 + * ping messages. + */ + 'keep_alives_sent'?: (number | string | Long); + /** + * The last time a stream was created by this endpoint. Usually unset for + * servers. + */ + 'last_local_stream_created_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The last time a stream was created by the remote endpoint. Usually unset + * for clients. + */ + 'last_remote_stream_created_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The last time a message was sent by this endpoint. + */ + 'last_message_sent_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The last time a message was received by this endpoint. + */ + 'last_message_received_timestamp'?: (_google_protobuf_Timestamp | null); + /** + * The amount of window, granted to the local endpoint by the remote endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'local_flow_control_window'?: (_google_protobuf_Int64Value | null); + /** + * The amount of window, granted to the remote endpoint by the local endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'remote_flow_control_window'?: (_google_protobuf_Int64Value | null); + /** + * Socket options set on this socket. May be absent if 'summary' is set + * on GetSocketRequest. + */ + 'option'?: (_grpc_channelz_v1_SocketOption)[]; +} + +/** + * SocketData is data associated for a specific Socket. The fields present + * are specific to the implementation, so there may be minor differences in + * the semantics. (e.g. flow control windows) + */ +export interface SocketData__Output { + /** + * The number of streams that have been started. + */ + 'streams_started': (string); + /** + * The number of streams that have ended successfully: + * On client side, received frame with eos bit set; + * On server side, sent frame with eos bit set. + */ + 'streams_succeeded': (string); + /** + * The number of streams that have ended unsuccessfully: + * On client side, ended without receiving frame with eos bit set; + * On server side, ended without sending frame with eos bit set. + */ + 'streams_failed': (string); + /** + * The number of grpc messages successfully sent on this socket. + */ + 'messages_sent': (string); + /** + * The number of grpc messages received on this socket. + */ + 'messages_received': (string); + /** + * The number of keep alives sent. This is typically implemented with HTTP/2 + * ping messages. + */ + 'keep_alives_sent': (string); + /** + * The last time a stream was created by this endpoint. Usually unset for + * servers. + */ + 'last_local_stream_created_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The last time a stream was created by the remote endpoint. Usually unset + * for clients. + */ + 'last_remote_stream_created_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The last time a message was sent by this endpoint. + */ + 'last_message_sent_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The last time a message was received by this endpoint. + */ + 'last_message_received_timestamp': (_google_protobuf_Timestamp__Output | null); + /** + * The amount of window, granted to the local endpoint by the remote endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'local_flow_control_window': (_google_protobuf_Int64Value__Output | null); + /** + * The amount of window, granted to the remote endpoint by the local endpoint. + * This may be slightly out of date due to network latency. This does NOT + * include stream level or TCP level flow control info. + */ + 'remote_flow_control_window': (_google_protobuf_Int64Value__Output | null); + /** + * Socket options set on this socket. May be absent if 'summary' is set + * on GetSocketRequest. + */ + 'option': (_grpc_channelz_v1_SocketOption__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOption.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOption.ts new file mode 100644 index 000000000..115b36aae --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOption.ts @@ -0,0 +1,47 @@ +// Original file: proto/channelz.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * SocketOption represents socket options for a socket. Specifically, these + * are the options returned by getsockopt(). + */ +export interface SocketOption { + /** + * The full name of the socket option. Typically this will be the upper case + * name, such as "SO_REUSEPORT". + */ + 'name'?: (string); + /** + * The human readable value of this socket option. At least one of value or + * additional will be set. + */ + 'value'?: (string); + /** + * Additional data associated with the socket option. At least one of value + * or additional will be set. + */ + 'additional'?: (_google_protobuf_Any | null); +} + +/** + * SocketOption represents socket options for a socket. Specifically, these + * are the options returned by getsockopt(). + */ +export interface SocketOption__Output { + /** + * The full name of the socket option. Typically this will be the upper case + * name, such as "SO_REUSEPORT". + */ + 'name': (string); + /** + * The human readable value of this socket option. At least one of value or + * additional will be set. + */ + 'value': (string); + /** + * Additional data associated with the socket option. At least one of value + * or additional will be set. + */ + 'additional': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionLinger.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionLinger.ts new file mode 100644 index 000000000..d83fa3238 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionLinger.ts @@ -0,0 +1,33 @@ +// Original file: proto/channelz.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_LINGER. + */ +export interface SocketOptionLinger { + /** + * active maps to `struct linger.l_onoff` + */ + 'active'?: (boolean); + /** + * duration maps to `struct linger.l_linger` + */ + 'duration'?: (_google_protobuf_Duration | null); +} + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_LINGER. + */ +export interface SocketOptionLinger__Output { + /** + * active maps to `struct linger.l_onoff` + */ + 'active': (boolean); + /** + * duration maps to `struct linger.l_linger` + */ + 'duration': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTcpInfo.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTcpInfo.ts new file mode 100644 index 000000000..2f8affe80 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTcpInfo.ts @@ -0,0 +1,74 @@ +// Original file: proto/channelz.proto + + +/** + * For use with SocketOption's additional field. Tcp info for + * SOL_TCP and TCP_INFO. + */ +export interface SocketOptionTcpInfo { + 'tcpi_state'?: (number); + 'tcpi_ca_state'?: (number); + 'tcpi_retransmits'?: (number); + 'tcpi_probes'?: (number); + 'tcpi_backoff'?: (number); + 'tcpi_options'?: (number); + 'tcpi_snd_wscale'?: (number); + 'tcpi_rcv_wscale'?: (number); + 'tcpi_rto'?: (number); + 'tcpi_ato'?: (number); + 'tcpi_snd_mss'?: (number); + 'tcpi_rcv_mss'?: (number); + 'tcpi_unacked'?: (number); + 'tcpi_sacked'?: (number); + 'tcpi_lost'?: (number); + 'tcpi_retrans'?: (number); + 'tcpi_fackets'?: (number); + 'tcpi_last_data_sent'?: (number); + 'tcpi_last_ack_sent'?: (number); + 'tcpi_last_data_recv'?: (number); + 'tcpi_last_ack_recv'?: (number); + 'tcpi_pmtu'?: (number); + 'tcpi_rcv_ssthresh'?: (number); + 'tcpi_rtt'?: (number); + 'tcpi_rttvar'?: (number); + 'tcpi_snd_ssthresh'?: (number); + 'tcpi_snd_cwnd'?: (number); + 'tcpi_advmss'?: (number); + 'tcpi_reordering'?: (number); +} + +/** + * For use with SocketOption's additional field. Tcp info for + * SOL_TCP and TCP_INFO. + */ +export interface SocketOptionTcpInfo__Output { + 'tcpi_state': (number); + 'tcpi_ca_state': (number); + 'tcpi_retransmits': (number); + 'tcpi_probes': (number); + 'tcpi_backoff': (number); + 'tcpi_options': (number); + 'tcpi_snd_wscale': (number); + 'tcpi_rcv_wscale': (number); + 'tcpi_rto': (number); + 'tcpi_ato': (number); + 'tcpi_snd_mss': (number); + 'tcpi_rcv_mss': (number); + 'tcpi_unacked': (number); + 'tcpi_sacked': (number); + 'tcpi_lost': (number); + 'tcpi_retrans': (number); + 'tcpi_fackets': (number); + 'tcpi_last_data_sent': (number); + 'tcpi_last_ack_sent': (number); + 'tcpi_last_data_recv': (number); + 'tcpi_last_ack_recv': (number); + 'tcpi_pmtu': (number); + 'tcpi_rcv_ssthresh': (number); + 'tcpi_rtt': (number); + 'tcpi_rttvar': (number); + 'tcpi_snd_ssthresh': (number); + 'tcpi_snd_cwnd': (number); + 'tcpi_advmss': (number); + 'tcpi_reordering': (number); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTimeout.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTimeout.ts new file mode 100644 index 000000000..185839b2c --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketOptionTimeout.ts @@ -0,0 +1,19 @@ +// Original file: proto/channelz.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_RCVTIMEO and SO_SNDTIMEO + */ +export interface SocketOptionTimeout { + 'duration'?: (_google_protobuf_Duration | null); +} + +/** + * For use with SocketOption's additional field. This is primarily used for + * SO_RCVTIMEO and SO_SNDTIMEO + */ +export interface SocketOptionTimeout__Output { + 'duration': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SocketRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketRef.ts new file mode 100644 index 000000000..52fdb2bd3 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SocketRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * SocketRef is a reference to a Socket. + */ +export interface SocketRef { + /** + * The globally unique id for this socket. Must be a positive number. + */ + 'socket_id'?: (number | string | Long); + /** + * An optional name associated with the socket. + */ + 'name'?: (string); +} + +/** + * SocketRef is a reference to a Socket. + */ +export interface SocketRef__Output { + /** + * The globally unique id for this socket. Must be a positive number. + */ + 'socket_id': (string); + /** + * An optional name associated with the socket. + */ + 'name': (string); +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Subchannel.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Subchannel.ts new file mode 100644 index 000000000..7122fac83 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Subchannel.ts @@ -0,0 +1,70 @@ +// Original file: proto/channelz.proto + +import type { SubchannelRef as _grpc_channelz_v1_SubchannelRef, SubchannelRef__Output as _grpc_channelz_v1_SubchannelRef__Output } from '../../../grpc/channelz/v1/SubchannelRef'; +import type { ChannelData as _grpc_channelz_v1_ChannelData, ChannelData__Output as _grpc_channelz_v1_ChannelData__Output } from '../../../grpc/channelz/v1/ChannelData'; +import type { ChannelRef as _grpc_channelz_v1_ChannelRef, ChannelRef__Output as _grpc_channelz_v1_ChannelRef__Output } from '../../../grpc/channelz/v1/ChannelRef'; +import type { SocketRef as _grpc_channelz_v1_SocketRef, SocketRef__Output as _grpc_channelz_v1_SocketRef__Output } from '../../../grpc/channelz/v1/SocketRef'; + +/** + * Subchannel is a logical grouping of channels, subchannels, and sockets. + * A subchannel is load balanced over by it's ancestor + */ +export interface Subchannel { + /** + * The identifier for this channel. + */ + 'ref'?: (_grpc_channelz_v1_SubchannelRef | null); + /** + * Data specific to this channel. + */ + 'data'?: (_grpc_channelz_v1_ChannelData | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref'?: (_grpc_channelz_v1_ChannelRef)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref'?: (_grpc_channelz_v1_SubchannelRef)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref'?: (_grpc_channelz_v1_SocketRef)[]; +} + +/** + * Subchannel is a logical grouping of channels, subchannels, and sockets. + * A subchannel is load balanced over by it's ancestor + */ +export interface Subchannel__Output { + /** + * The identifier for this channel. + */ + 'ref': (_grpc_channelz_v1_SubchannelRef__Output | null); + /** + * Data specific to this channel. + */ + 'data': (_grpc_channelz_v1_ChannelData__Output | null); + /** + * There are no ordering guarantees on the order of channel refs. + * There may not be cycles in the ref graph. + * A channel ref may be present in more than one channel or subchannel. + */ + 'channel_ref': (_grpc_channelz_v1_ChannelRef__Output)[]; + /** + * At most one of 'channel_ref+subchannel_ref' and 'socket' is set. + * There are no ordering guarantees on the order of subchannel refs. + * There may not be cycles in the ref graph. + * A sub channel ref may be present in more than one channel or subchannel. + */ + 'subchannel_ref': (_grpc_channelz_v1_SubchannelRef__Output)[]; + /** + * There are no ordering guarantees on the order of sockets. + */ + 'socket_ref': (_grpc_channelz_v1_SocketRef__Output)[]; +} diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/SubchannelRef.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/SubchannelRef.ts new file mode 100644 index 000000000..b6911c773 --- /dev/null +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/SubchannelRef.ts @@ -0,0 +1,31 @@ +// Original file: proto/channelz.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * SubchannelRef is a reference to a Subchannel. + */ +export interface SubchannelRef { + /** + * The globally unique id for this subchannel. Must be a positive number. + */ + 'subchannel_id'?: (number | string | Long); + /** + * An optional name associated with the subchannel. + */ + 'name'?: (string); +} + +/** + * SubchannelRef is a reference to a Subchannel. + */ +export interface SubchannelRef__Output { + /** + * The globally unique id for this subchannel. Must be a positive number. + */ + 'subchannel_id': (string); + /** + * An optional name associated with the subchannel. + */ + 'name': (string); +} From 74fdd85123acac606659cf07bb1d1bdebf836439 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 13 Sep 2021 10:58:12 -0700 Subject: [PATCH 073/694] Add channelz service implementation --- packages/grpc-js/src/channel.ts | 3 + packages/grpc-js/src/channelz.ts | 375 ++++++++++++++++++++++++++++- packages/grpc-js/src/server.ts | 14 +- packages/grpc-js/src/subchannel.ts | 5 +- 4 files changed, 387 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 4a6e88147..c5f7352e4 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -163,6 +163,7 @@ export class ChannelImplementation implements Channel { private configSelector: ConfigSelector | null = null; // Channelz info + private originalTarget: string; private channelzRef: ChannelRef; private channelzTrace: ChannelzTrace; private callTracker = new ChannelzCallTracker(); @@ -196,6 +197,7 @@ export class ChannelImplementation implements Channel { ); } } + this.originalTarget = target; const originalTargetUri = parseUri(target); if (originalTargetUri === null) { throw new Error(`Could not parse target name "${target}"`); @@ -320,6 +322,7 @@ export class ChannelImplementation implements Channel { private getChannelzInfo(): ChannelInfo { return { + target: this.originalTarget, state: this.connectivityState, trace: this.channelzTrace, callTracker: this.callTracker, diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 3edbd7bba..7965ad3c2 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -17,7 +17,39 @@ import { isIPv4, isIPv6 } from "net"; import { ConnectivityState } from "./connectivity-state"; -import { SubchannelAddress } from "./subchannel-address"; +import { Status } from "./constants"; +import { Timestamp } from "./generated/google/protobuf/Timestamp"; +import { Channel as ChannelMessage } from "./generated/grpc/channelz/v1/Channel"; +import { ChannelConnectivityState__Output } from "./generated/grpc/channelz/v1/ChannelConnectivityState"; +import { ChannelRef as ChannelRefMessage } from "./generated/grpc/channelz/v1/ChannelRef"; +import { ChannelTrace } from "./generated/grpc/channelz/v1/ChannelTrace"; +import { GetChannelRequest__Output } from "./generated/grpc/channelz/v1/GetChannelRequest"; +import { GetChannelResponse } from "./generated/grpc/channelz/v1/GetChannelResponse"; +import { sendUnaryData, ServerUnaryCall } from "./server-call"; +import { ServerRef as ServerRefMessage } from "./generated/grpc/channelz/v1/ServerRef"; +import { SocketRef as SocketRefMessage } from "./generated/grpc/channelz/v1/SocketRef"; +import { isTcpSubchannelAddress, SubchannelAddress } from "./subchannel-address"; +import { SubchannelRef as SubchannelRefMessage } from "./generated/grpc/channelz/v1/SubchannelRef"; +import { GetServerRequest__Output } from "./generated/grpc/channelz/v1/GetServerRequest"; +import { GetServerResponse } from "./generated/grpc/channelz/v1/GetServerResponse"; +import { Server as ServerMessage } from "./generated/grpc/channelz/v1/Server"; +import { GetServersRequest__Output } from "./generated/grpc/channelz/v1/GetServersRequest"; +import { GetServersResponse } from "./generated/grpc/channelz/v1/GetServersResponse"; +import { GetTopChannelsRequest__Output } from "./generated/grpc/channelz/v1/GetTopChannelsRequest"; +import { GetTopChannelsResponse } from "./generated/grpc/channelz/v1/GetTopChannelsResponse"; +import { GetSubchannelRequest__Output } from "./generated/grpc/channelz/v1/GetSubchannelRequest"; +import { GetSubchannelResponse } from "./generated/grpc/channelz/v1/GetSubchannelResponse"; +import { Subchannel as SubchannelMessage } from "./generated/grpc/channelz/v1/Subchannel"; +import { GetSocketRequest__Output } from "./generated/grpc/channelz/v1/GetSocketRequest"; +import { GetSocketResponse } from "./generated/grpc/channelz/v1/GetSocketResponse"; +import { Socket as SocketMessage } from "./generated/grpc/channelz/v1/Socket"; +import { Address } from "./generated/grpc/channelz/v1/Address"; +import { Security } from "./generated/grpc/channelz/v1/Security"; +import { GetServerSocketsRequest__Output } from "./generated/grpc/channelz/v1/GetServerSocketsRequest"; +import { GetServerSocketsResponse } from "./generated/grpc/channelz/v1/GetServerSocketsResponse"; +import { ChannelzDefinition, ChannelzHandlers } from "./generated/grpc/channelz/v1/Channelz"; +import { ProtoGrpcType as ChannelzProtoGrpcType } from "./generated/channelz"; +import type { loadSync } from '@grpc/proto-loader'; export type TraceSeverity = 'CT_UNKNOWN' | 'CT_INFO' | 'CT_WARNING' | 'CT_ERROR'; @@ -44,6 +76,33 @@ export interface SocketRef { name: string; } +function channelRefToMessage(ref: ChannelRef): ChannelRefMessage { + return { + channel_id: ref.id, + name: ref.name + }; +} + +function subchannelRefToMessage(ref: SubchannelRef): SubchannelRefMessage { + return { + subchannel_id: ref.id, + name: ref.name + } +} + +function serverRefToMessage(ref: ServerRef): ServerRefMessage { + return { + server_id: ref.id + } +} + +function socketRefToMessage(ref: SocketRef): SocketRefMessage { + return { + socket_id: ref.id, + name: ref.name + } +} + interface TraceEvent { description: string; severity: TraceSeverity; @@ -72,6 +131,22 @@ export class ChannelzTrace { }); this.eventsLogged += 1; } + + getTraceMessage(): ChannelTrace { + return { + creation_timestamp: dateToProtoTimestamp(this.creationTimestamp), + num_events_logged: this.eventsLogged, + events: this.events.map(event => { + return { + description: event.description, + severity: event.severity, + timestamp: dateToProtoTimestamp(event.timestamp), + channel_ref: event.childChannel ? channelRefToMessage(event.childChannel) : null, + subchannel_ref: event.childSubchannel ? subchannelRefToMessage(event.childSubchannel) : null + } + }) + }; + } } export class ChannelzChildrenTracker { @@ -185,6 +260,7 @@ export interface ChannelzChildren { } export interface ChannelInfo { + target: string; state: ConnectivityState; trace: ChannelzTrace; callTracker: ChannelzCallTracker; @@ -196,7 +272,8 @@ export interface SubchannelInfo extends ChannelInfo {} export interface ServerInfo { trace: ChannelzTrace; callTracker: ChannelzCallTracker; - children: ChannelzChildren; + listenerChildren: ChannelzChildren; + sessionChildren: ChannelzChildren; } export interface TlsInfo { @@ -342,4 +419,298 @@ function ipAddressStringToBuffer(ipAddress: string): Buffer | null { } else { return null; } +} + +function connectivityStateToMessage(state: ConnectivityState): ChannelConnectivityState__Output { + switch (state) { + case ConnectivityState.CONNECTING: + return { + state: 'CONNECTING' + }; + case ConnectivityState.IDLE: + return { + state: 'IDLE' + }; + case ConnectivityState.READY: + return { + state: 'READY' + }; + case ConnectivityState.SHUTDOWN: + return { + state: 'SHUTDOWN' + }; + case ConnectivityState.TRANSIENT_FAILURE: + return { + state: 'TRANSIENT_FAILURE' + }; + default: + return { + state: 'UNKNOWN' + }; + } +} + +function dateToProtoTimestamp(date?: Date | null): Timestamp | null { + if (!date) { + return null; + } + return { + seconds: date.getSeconds(), + nanos: date.getMilliseconds() * 1_000_000 + } +} + +function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { + const resolvedInfo = channelEntry.getInfo(); + return { + ref: channelRefToMessage(channelEntry.ref), + data: { + target: resolvedInfo.target, + state: connectivityStateToMessage(resolvedInfo.state), + calls_started: resolvedInfo.callTracker.callsStarted, + calls_succeeded: resolvedInfo.callTracker.callsSucceeded, + calls_failed: resolvedInfo.callTracker.callsFailed, + last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), + trace: resolvedInfo.trace.getTraceMessage() + }, + channel_ref: resolvedInfo.children.channels.map(ref => channelRefToMessage(ref)), + subchannel_ref: resolvedInfo.children.subchannels.map(ref => subchannelRefToMessage(ref)) + }; +} + +function GetChannel(call: ServerUnaryCall, callback: sendUnaryData): void { + const channelId = Number.parseInt(call.request.channel_id); + const channelEntry = channels[channelId]; + if (channelEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No channel data found for id ' + channelId + }); + return; + } + callback(null, {channel: getChannelMessage(channelEntry)}); +} + +function GetTopChannels(call: ServerUnaryCall, callback: sendUnaryData): void { + const maxResults = Number.parseInt(call.request.max_results); + const resultList: ChannelMessage[] = []; + let i = Number.parseInt(call.request.start_channel_id); + for (; i < channels.length; i++) { + const channelEntry = channels[i]; + if (channelEntry === undefined) { + continue; + } + resultList.push(getChannelMessage(channelEntry)); + if (resultList.length >= maxResults) { + break; + } + } + callback(null, { + channel: resultList, + end: i >= servers.length + }); +} + +function getServerMessage(serverEntry: ServerEntry): ServerMessage { + const resolvedInfo = serverEntry.getInfo(); + return { + ref: serverRefToMessage(serverEntry.ref), + data: { + calls_started: resolvedInfo.callTracker.callsStarted, + calls_succeeded: resolvedInfo.callTracker.callsSucceeded, + calls_failed: resolvedInfo.callTracker.callsFailed, + last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), + trace: resolvedInfo.trace.getTraceMessage() + }, + listen_socket: resolvedInfo.listenerChildren.sockets.map(ref => socketRefToMessage(ref)) + }; +} + +function GetServer(call: ServerUnaryCall, callback: sendUnaryData): void { + const serverId = Number.parseInt(call.request.server_id); + const serverEntry = servers[serverId]; + if (serverEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No server data found for id ' + serverId + }); + return; + } + callback(null, {server: getServerMessage(serverEntry)}); +} + +function GetServers(call: ServerUnaryCall, callback: sendUnaryData): void { + const maxResults = Number.parseInt(call.request.max_results); + const resultList: ServerMessage[] = []; + let i = Number.parseInt(call.request.start_server_id); + for (; i < servers.length; i++) { + const serverEntry = servers[i]; + if (serverEntry === undefined) { + continue; + } + resultList.push(getServerMessage(serverEntry)); + if (resultList.length >= maxResults) { + break; + } + } + callback(null, { + server: resultList, + end: i >= servers.length + }); +} + +function GetSubchannel(call: ServerUnaryCall, callback: sendUnaryData): void { + const subchannelId = Number.parseInt(call.request.subchannel_id); + const subchannelEntry = subchannels[subchannelId]; + if (subchannelEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No subchannel data found for id ' + subchannelId + }); + return; + } + const resolvedInfo = subchannelEntry.getInfo(); + const subchannelMessage: SubchannelMessage = { + ref: subchannelRefToMessage(subchannelEntry.ref), + data: { + target: resolvedInfo.target, + state: connectivityStateToMessage(resolvedInfo.state), + calls_started: resolvedInfo.callTracker.callsStarted, + calls_succeeded: resolvedInfo.callTracker.callsSucceeded, + calls_failed: resolvedInfo.callTracker.callsFailed, + last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), + trace: resolvedInfo.trace.getTraceMessage() + }, + socket_ref: resolvedInfo.children.sockets.map(ref => socketRefToMessage(ref)) + }; + callback(null, {subchannel: subchannelMessage}); +} + +function subchannelAddressToAddressMessage(subchannelAddress: SubchannelAddress): Address { + if (isTcpSubchannelAddress(subchannelAddress)) { + return { + address: 'tcpip_address', + tcpip_address: { + ip_address: ipAddressStringToBuffer(subchannelAddress.host) ?? undefined, + port: subchannelAddress.port + } + }; + } else { + return { + address: 'uds_address', + uds_address: { + filename: subchannelAddress.path + } + }; + } +} + +function GetSocket(call: ServerUnaryCall, callback: sendUnaryData): void { + const socketId = Number.parseInt(call.request.socket_id); + const socketEntry = sockets[socketId]; + if (socketEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No socket data found for id ' + socketId + }); + return; + } + const resolvedInfo = socketEntry.getInfo(); + const securityMessage: Security | null = resolvedInfo.security ? { + model: 'tls', + tls: { + cipher_suite: resolvedInfo.security.cipherSuiteStandardName ? 'standard_name' : 'other_name', + standard_name: resolvedInfo.security.cipherSuiteStandardName ?? undefined, + other_name: resolvedInfo.security.cipherSuiteOtherName ?? undefined, + local_certificate: resolvedInfo.security.localCertificate ?? undefined, + remote_certificate: resolvedInfo.security.remoteCertificate ?? undefined + } + } : null; + const socketMessage: SocketMessage = { + ref: socketRefToMessage(socketEntry.ref), + local: subchannelAddressToAddressMessage(resolvedInfo.localAddress), + remote: resolvedInfo.remoteAddress ? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress) : null, + remote_name: resolvedInfo.remoteName ?? undefined, + security: securityMessage, + data: { + keep_alives_sent: resolvedInfo.keepAlivesSent, + streams_started: resolvedInfo.streamsStarted, + streams_succeeded: resolvedInfo.streamsSucceeded, + streams_failed: resolvedInfo.streamsFailed, + last_local_stream_created_timestamp: dateToProtoTimestamp(resolvedInfo.lastLocalStreamCreatedTimestamp), + last_remote_stream_created_timestamp: dateToProtoTimestamp(resolvedInfo.lastRemoteStreamCreatedTimestamp), + messages_received: resolvedInfo.messagesReceived, + messages_sent: resolvedInfo.messagesSent, + last_message_received_timestamp: dateToProtoTimestamp(resolvedInfo.lastMessageReceivedTimestamp), + last_message_sent_timestamp: dateToProtoTimestamp(resolvedInfo.lastMessageSentTimestamp), + local_flow_control_window: resolvedInfo.localFlowControlWindow ? { value: resolvedInfo.localFlowControlWindow } : null, + remote_flow_control_window: resolvedInfo.remoteFlowControlWindow ? { value: resolvedInfo.remoteFlowControlWindow } : null, + } + }; + callback(null, {socket: socketMessage}); +} + +function GetServerSockets(call: ServerUnaryCall, callback: sendUnaryData): void { + const serverId = Number.parseInt(call.request.server_id); + const serverEntry = servers[serverId]; + if (serverEntry === undefined) { + callback({ + 'code': Status.NOT_FOUND, + 'details': 'No server data found for id ' + serverId + }); + return; + } + const startId = Number.parseInt(call.request.start_socket_id); + const maxResults = Number.parseInt(call.request.max_results); + const resolvedInfo = serverEntry.getInfo(); + const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id); + const resultList: SocketRefMessage[] = []; + let i = 0; + for (; i < allSockets.length; i++) { + if (allSockets[i].id >= startId) { + resultList.push(socketRefToMessage(allSockets[i])); + if (resultList.length >= maxResults) { + break; + } + } + } + callback(null, { + socket_ref: resultList, + end: i >= allSockets.length + }); +} + +export function getChannelzHandlers(): ChannelzHandlers { + return { + GetChannel, + GetTopChannels, + GetServer, + GetServers, + GetSubchannel, + GetSocket, + GetServerSockets + }; +} + +let loadedChannelzDefinition: ChannelzDefinition | null = null; + +export function getChannelzServiceDefinition(): ChannelzDefinition { + if (loadedChannelzDefinition) { + return loadedChannelzDefinition; + } + /* The purpose of this complexity is to avoid loading @grpc/proto-loader at + * runtime for users who will not use/enable channelz. */ + const loaderLoadSync = require('@grpc/proto-loader').loadSync as typeof loadSync; + const loadedProto = loaderLoadSync('channelz.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + '../../proto' + ] + }) as unknown as ChannelzProtoGrpcType; + loadedChannelzDefinition = loadedProto.grpc.channelz.v1.Channelz.service; + return loadedChannelzDefinition; } \ No newline at end of file diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 04c24c072..ef31dadce 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -156,7 +156,8 @@ export class Server { private channelzRef: ServerRef; private channelzTrace = new ChannelzTrace(); private callTracker = new ChannelzCallTracker(); - private childrenTracker = new ChannelzChildrenTracker(); + private listenerChildrenTracker = new ChannelzChildrenTracker(); + private sessionChildrenTracker = new ChannelzChildrenTracker(); constructor(options?: ChannelOptions) { this.options = options ?? {}; @@ -168,7 +169,8 @@ export class Server { return { trace: this.channelzTrace, callTracker: this.callTracker, - children: this.childrenTracker.getChildLists() + listenerChildren: this.listenerChildrenTracker.getChildLists(), + sessionChildren: this.sessionChildrenTracker.getChildLists() }; } @@ -568,7 +570,7 @@ export class Server { for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { if (http2Server.listening) { http2Server.close(() => { - this.childrenTracker.unrefChild(ref); + this.listenerChildrenTracker.unrefChild(ref); unregisterChannelzRef(ref); }); } @@ -652,7 +654,7 @@ export class Server { if (http2Server.listening) { pendingChecks++; http2Server.close(() => { - this.childrenTracker.unrefChild(ref); + this.listenerChildrenTracker.unrefChild(ref); unregisterChannelzRef(ref); maybeCallback(); }); @@ -826,10 +828,10 @@ export class Server { this.sessions.set(session, channelzSessionInfo); const clientAddress = session.socket.remoteAddress; this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); - this.childrenTracker.refChild(channelzRef); + this.sessionChildrenTracker.refChild(channelzRef); session.on('close', () => { this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); - this.childrenTracker.unrefChild(channelzRef); + this.sessionChildrenTracker.unrefChild(channelzRef); unregisterChannelzRef(channelzRef); this.sessions.delete(session); }); diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 1caf94ba1..e7e7a9f08 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -266,7 +266,8 @@ export class Subchannel { state: this.connectivityState, trace: this.channelzTrace, callTracker: this.callTracker, - children: this.childrenTracker.getChildLists() + children: this.childrenTracker.getChildLists(), + target: this.subchannelAddressString }; } @@ -285,7 +286,7 @@ export class Subchannel { const peerCertificate = tlsSocket.getPeerCertificate(); tlsInfo = { cipherSuiteStandardName: cipherInfo.standardName ?? null, - cipherSuiteOtherName: cipherInfo.standardName ? cipherInfo.name: null, + cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null }; From 737dae88db117e090b9b5020c1de9c1717f7f7b4 Mon Sep 17 00:00:00 2001 From: bbesset Date: Wed, 15 Sep 2021 09:03:49 +0200 Subject: [PATCH 074/694] adds sometimes required Host header to proxy connection --- packages/grpc-js/src/http_proxy.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index 14e88f068..248949a80 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -190,6 +190,9 @@ export function getProxiedConnection( method: 'CONNECT', path: parsedTarget.path, }; + const headers: http.OutgoingHttpHeaders = { + Host: parsedTarget.path, + }; // Connect to the subchannel address as a proxy if (isTcpSubchannelAddress(address)) { options.host = address.host; @@ -198,14 +201,13 @@ export function getProxiedConnection( options.socketPath = address.path; } if ('grpc.http_connect_creds' in channelOptions) { - options.headers = { - 'Proxy-Authorization': - 'Basic ' + - Buffer.from( - channelOptions['grpc.http_connect_creds'] as string - ).toString('base64'), - }; + headers['Proxy-Authorization'] = + 'Basic ' + + Buffer.from( + channelOptions['grpc.http_connect_creds'] as string + ).toString('base64'); } + options.headers = headers const proxyAddressString = subchannelAddressToString(address); trace('Using proxy ' + proxyAddressString + ' to connect to ' + options.path); return new Promise((resolve, reject) => { From acd991368746dba1cd616bc190caea6af3b679d3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 13 Sep 2021 10:58:24 -0700 Subject: [PATCH 075/694] Add admin interface --- packages/grpc-js/src/admin.ts | 39 ++++++++++++++++++++++++++++ packages/grpc-js/src/channelz.ts | 5 ++++ packages/grpc-js/src/experimental.ts | 1 + packages/grpc-js/src/index.ts | 9 +++++++ 4 files changed, 54 insertions(+) create mode 100644 packages/grpc-js/src/admin.ts diff --git a/packages/grpc-js/src/admin.ts b/packages/grpc-js/src/admin.ts new file mode 100644 index 000000000..7745d07d8 --- /dev/null +++ b/packages/grpc-js/src/admin.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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 { ServiceDefinition } from "./make-client"; +import { Server, UntypedServiceImplementation } from "./server"; + +interface GetServiceDefinition { + (): ServiceDefinition; +} + +interface GetHandlers { + (): UntypedServiceImplementation; +} + +const registeredAdminServices: {getServiceDefinition: GetServiceDefinition, getHandlers: GetHandlers}[] = []; + +export function registerAdminService(getServiceDefinition: GetServiceDefinition, getHandlers: GetHandlers) { + registeredAdminServices.push({getServiceDefinition, getHandlers}); +} + +export function addAdminServicesToServer(server: Server): void { + for (const {getServiceDefinition, getHandlers} of registeredAdminServices) { + server.addService(getServiceDefinition(), getHandlers()); + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 7965ad3c2..1e2cbf708 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -50,6 +50,7 @@ import { GetServerSocketsResponse } from "./generated/grpc/channelz/v1/GetServer import { ChannelzDefinition, ChannelzHandlers } from "./generated/grpc/channelz/v1/Channelz"; import { ProtoGrpcType as ChannelzProtoGrpcType } from "./generated/channelz"; import type { loadSync } from '@grpc/proto-loader'; +import { registerAdminService } from "./admin"; export type TraceSeverity = 'CT_UNKNOWN' | 'CT_INFO' | 'CT_WARNING' | 'CT_ERROR'; @@ -713,4 +714,8 @@ export function getChannelzServiceDefinition(): ChannelzDefinition { }) as unknown as ChannelzProtoGrpcType; loadedChannelzDefinition = loadedProto.grpc.channelz.v1.Channelz.service; return loadedChannelzDefinition; +} + +export function setup() { + registerAdminService(getChannelzServiceDefinition, getChannelzHandlers); } \ No newline at end of file diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 24b795f06..4d158a53f 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -32,3 +32,4 @@ export { export { Call as CallStream } from './call-stream'; export { Filter, BaseFilter, FilterFactory } from './filter'; export { FilterStackFactory } from './filter-stack'; +export { registerAdminService } from './admin'; diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 0730a26eb..29941d27e 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -249,6 +249,13 @@ export { GrpcObject } from './make-client'; export { ChannelOptions } from './channel-options'; +export { + getChannelzServiceDefinition, + getChannelzHandlers +} from './channelz'; + +export { addAdminServicesToServer } from './admin'; + import * as experimental from './experimental'; export { experimental }; @@ -257,6 +264,7 @@ import * as resolver_uds from './resolver-uds'; import * as resolver_ip from './resolver-ip'; import * as load_balancer_pick_first from './load-balancer-pick-first'; import * as load_balancer_round_robin from './load-balancer-round-robin'; +import * as channelz from './channelz'; (() => { resolver_dns.setup(); @@ -264,4 +272,5 @@ import * as load_balancer_round_robin from './load-balancer-round-robin'; resolver_ip.setup(); load_balancer_pick_first.setup(); load_balancer_round_robin.setup(); + channelz.setup(); })(); From 305623e2eba833af1432685872f03cad8041cb97 Mon Sep 17 00:00:00 2001 From: bbesset Date: Wed, 15 Sep 2021 09:03:49 +0200 Subject: [PATCH 076/694] adds sometimes required Host header to proxy connection --- packages/grpc-js/src/http_proxy.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index 14e88f068..248949a80 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -190,6 +190,9 @@ export function getProxiedConnection( method: 'CONNECT', path: parsedTarget.path, }; + const headers: http.OutgoingHttpHeaders = { + Host: parsedTarget.path, + }; // Connect to the subchannel address as a proxy if (isTcpSubchannelAddress(address)) { options.host = address.host; @@ -198,14 +201,13 @@ export function getProxiedConnection( options.socketPath = address.path; } if ('grpc.http_connect_creds' in channelOptions) { - options.headers = { - 'Proxy-Authorization': - 'Basic ' + - Buffer.from( - channelOptions['grpc.http_connect_creds'] as string - ).toString('base64'), - }; + headers['Proxy-Authorization'] = + 'Basic ' + + Buffer.from( + channelOptions['grpc.http_connect_creds'] as string + ).toString('base64'); } + options.headers = headers const proxyAddressString = subchannelAddressToString(address); trace('Using proxy ' + proxyAddressString + ' to connect to ' + options.path); return new Promise((resolve, reject) => { From 1ae04af7ba97b59c23da69345d3eb3a1b6c7a4af Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 16 Sep 2021 14:48:06 -0700 Subject: [PATCH 077/694] grpc-js-xds: fix use of splice in interop test code --- packages/grpc-js-xds/interop/xds-interop-client.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index a414ffe7f..6cd3aeb33 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -130,6 +130,13 @@ class CallStatsTracker { private subscribers: CallSubscriber[] = []; + private removeSubscriber(subscriber: CallSubscriber) { + const index = this.subscribers.indexOf(subscriber); + if (index >= 0) { + this.subscribers.splice(index, 1); + } + } + getCallStats(callCount: number, timeoutSec: number): Promise { return new Promise((resolve, reject) => { let finished = false; @@ -142,7 +149,7 @@ class CallStatsTracker { setTimeout(() => { if (!finished) { finished = true; - this.subscribers.splice(this.subscribers.indexOf(subscriber), 1); + this.removeSubscriber(subscriber); resolve(subscriber.getFinalStats()); } }, timeoutSec * 1000) @@ -155,7 +162,7 @@ class CallStatsTracker { for (const subscriber of callSubscribers) { subscriber.addCallStarted(); if (!subscriber.needsMoreCalls()) { - this.subscribers.splice(this.subscribers.indexOf(subscriber), 1); + this.removeSubscriber(subscriber); } } return { From 7b656758397d4b5a02098980a16abf67061d3994 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 20 Sep 2021 11:20:41 -0700 Subject: [PATCH 078/694] grpc-js-xds: Log loaded bootstrap info in xDS client --- packages/grpc-js-xds/src/xds-client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 952ae81f4..de4fba23e 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -340,6 +340,7 @@ export class XdsClient { if (this.hasShutdown) { return; } + trace('Loaded bootstrap info: ' + JSON.stringify(bootstrapInfo, undefined, 2)); if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('xds_v3') >= 0) { this.apiVersion = XdsApiVersion.V3; } else { From de5eb821d121e27a5c0d0b3a92e054f691381b42 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 20 Sep 2021 11:20:41 -0700 Subject: [PATCH 079/694] grpc-js-xds: Log loaded bootstrap info in xDS client --- packages/grpc-js-xds/src/xds-client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 952ae81f4..de4fba23e 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -340,6 +340,7 @@ export class XdsClient { if (this.hasShutdown) { return; } + trace('Loaded bootstrap info: ' + JSON.stringify(bootstrapInfo, undefined, 2)); if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('xds_v3') >= 0) { this.apiVersion = XdsApiVersion.V3; } else { From 4229b76812f6b85a8c8bec95e319a7d2c59066bf Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 20 Sep 2021 11:40:15 -0700 Subject: [PATCH 080/694] grpc-js-xds: Add Node message logging --- packages/grpc-js-xds/src/xds-client.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index de4fba23e..41bc27147 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -371,6 +371,13 @@ export class XdsClient { ...nodeV3, client_features: ['envoy.lrs.supports_send_all_clusters'], }; + if (this.apiVersion === XdsApiVersion.V2) { + trace('ADS Node: ' + JSON.stringify(this.adsNodeV2, undefined, 2)); + trace('LRS Node: ' + JSON.stringify(this.lrsNodeV2, undefined, 2)); + } else { + trace('ADS Node: ' + JSON.stringify(this.adsNodeV3, undefined, 2)); + trace('LRS Node: ' + JSON.stringify(this.lrsNodeV3, undefined, 2)); + } const credentialsConfigs = bootstrapInfo.xdsServers[0].channelCreds; let channelCreds: ChannelCredentials | null = null; for (const config of credentialsConfigs) { From cfcc491a61057f9d92459fa76828a5e1b125acea Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 20 Sep 2021 11:40:15 -0700 Subject: [PATCH 081/694] grpc-js-xds: Add Node message logging --- packages/grpc-js-xds/src/xds-client.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index de4fba23e..41bc27147 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -371,6 +371,13 @@ export class XdsClient { ...nodeV3, client_features: ['envoy.lrs.supports_send_all_clusters'], }; + if (this.apiVersion === XdsApiVersion.V2) { + trace('ADS Node: ' + JSON.stringify(this.adsNodeV2, undefined, 2)); + trace('LRS Node: ' + JSON.stringify(this.lrsNodeV2, undefined, 2)); + } else { + trace('ADS Node: ' + JSON.stringify(this.adsNodeV3, undefined, 2)); + trace('LRS Node: ' + JSON.stringify(this.lrsNodeV3, undefined, 2)); + } const credentialsConfigs = bootstrapInfo.xdsServers[0].channelCreds; let channelCreds: ChannelCredentials | null = null; for (const config of credentialsConfigs) { From d60c4ea16f599d205fad63e379edb210da1faaad Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 27 Sep 2021 10:12:11 -0700 Subject: [PATCH 082/694] Add channelz tests and fix some bugs --- packages/grpc-js/src/call-stream.ts | 18 +- packages/grpc-js/src/channel.ts | 12 +- packages/grpc-js/src/channelz.ts | 18 +- packages/grpc-js/src/server-call.ts | 1 - packages/grpc-js/src/server.ts | 8 +- packages/grpc-js/src/subchannel.ts | 43 ++-- packages/grpc-js/test/test-channelz.ts | 289 +++++++++++++++++++++++++ test/channelz/channelz_manual_test.js | 73 +++++++ test/interop/interop_server.js | 2 +- 9 files changed, 421 insertions(+), 43 deletions(-) create mode 100644 packages/grpc-js/test/test-channelz.ts create mode 100644 test/channelz/channelz_manual_test.js diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index e030d9e39..f8bca8db7 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -25,7 +25,7 @@ import { FilterStackFactory, FilterStack } from './filter-stack'; import { Metadata } from './metadata'; import { StreamDecoder } from './stream-decoder'; import { ChannelImplementation } from './channel'; -import { Subchannel } from './subchannel'; +import { SubchannelCallStatsTracker, Subchannel } from './subchannel'; import * as logging from './logging'; import { LogVerbosity } from './constants'; import { ServerSurfaceCall } from './server-call'; @@ -252,6 +252,8 @@ export class Http2CallStream implements Call { private statusWatchers: ((status: StatusObject) => void)[] = []; private streamEndWatchers: ((success: boolean) => void)[] = []; + private callStatsTracker: SubchannelCallStatsTracker | null = null; + constructor( private readonly methodName: string, private readonly channel: ChannelImplementation, @@ -468,10 +470,16 @@ export class Http2CallStream implements Call { this.endCall(status); } + private writeMessageToStream(message: Buffer, callback: WriteCallback) { + this.callStatsTracker?.addMessageSent(); + this.http2Stream!.write(message, callback); + } + attachHttp2Stream( stream: http2.ClientHttp2Stream, subchannel: Subchannel, - extraFilters: FilterFactory[] + extraFilters: FilterFactory[], + callStatsTracker: SubchannelCallStatsTracker ): void { this.filterStack.push( extraFilters.map((filterFactory) => filterFactory.createFilter(this)) @@ -484,6 +492,7 @@ export class Http2CallStream implements Call { ); this.http2Stream = stream; this.subchannel = subchannel; + this.callStatsTracker = callStatsTracker; subchannel.addDisconnectListener(this.disconnectListener); subchannel.callRef(); stream.on('response', (headers, flags) => { @@ -549,6 +558,7 @@ export class Http2CallStream implements Call { for (const message of messages) { this.trace('parsed message of length ' + message.length); + this.callStatsTracker!.addMessageReceived(); this.tryPush(message); } }); @@ -666,7 +676,7 @@ export class Http2CallStream implements Call { this.pendingWrite.length + ' (deferred)' ); - stream.write(this.pendingWrite, this.pendingWriteCallback); + this.writeMessageToStream(this.pendingWrite, this.pendingWriteCallback); } this.maybeCloseWrites(); } @@ -802,7 +812,7 @@ export class Http2CallStream implements Call { this.pendingWriteCallback = cb; } else { this.trace('sending data chunk of length ' + message.message.length); - this.http2Stream.write(message.message, cb); + this.writeMessageToStream(message.message, cb); this.maybeCloseWrites(); } }, this.handleFilterError.bind(this)); diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index c5f7352e4..636ebc44b 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -103,6 +103,12 @@ export interface Channel { deadline: Date | number, callback: (error?: Error) => void ): void; + /** + * Get the channelz reference object for this channel. A request to the + * channelz service for the id in this object will provide information + * about this channel. + */ + getChannelzRef(): ChannelRef; /** * Create a call object. Call is an opaque type that is used by the Client * class. This function is called by the gRPC library when starting a @@ -243,7 +249,7 @@ export class ChannelImplementation implements Channel { Object.assign({}, this.options, subchannelArgs), this.credentials ); - this.channelzTrace.addTrace('CT_INFO', 'Created or got existing subchannel', subchannel.getChannelzRef()); + this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); return subchannel; }, updateState: (connectivityState: ConnectivityState, picker: Picker) => { @@ -677,6 +683,10 @@ export class ChannelImplementation implements Channel { this.connectivityStateWatchers.push(watcherObject); } + getChannelzRef() { + return this.channelzRef; + } + createCall( method: string, deadline: Deadline, diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 1e2cbf708..7efe54780 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -51,6 +51,7 @@ import { ChannelzDefinition, ChannelzHandlers } from "./generated/grpc/channelz/ import { ProtoGrpcType as ChannelzProtoGrpcType } from "./generated/channelz"; import type { loadSync } from '@grpc/proto-loader'; import { registerAdminService } from "./admin"; +import { loadPackageDefinition } from "./make-client"; export type TraceSeverity = 'CT_UNKNOWN' | 'CT_INFO' | 'CT_WARNING' | 'CT_ERROR'; @@ -455,9 +456,10 @@ function dateToProtoTimestamp(date?: Date | null): Timestamp | null { if (!date) { return null; } + const millisSinceEpoch = date.getTime(); return { - seconds: date.getSeconds(), - nanos: date.getMilliseconds() * 1_000_000 + seconds: (millisSinceEpoch / 1000) | 0, + nanos: (millisSinceEpoch % 1000) * 1_000_000 } } @@ -664,7 +666,10 @@ function GetServerSockets(call: ServerUnaryCall ref1.id - ref2.id); + // If we wanted to include listener sockets in the result, this line would + // instead say + // const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id); + const allSockets = resolvedInfo.sessionChildren.sockets.sort((ref1, ref2) => ref1.id - ref2.id); const resultList: SocketRefMessage[] = []; let i = 0; for (; i < allSockets.length; i++) { @@ -709,10 +714,11 @@ export function getChannelzServiceDefinition(): ChannelzDefinition { defaults: true, oneofs: true, includeDirs: [ - '../../proto' + `${__dirname}/../../proto` ] - }) as unknown as ChannelzProtoGrpcType; - loadedChannelzDefinition = loadedProto.grpc.channelz.v1.Channelz.service; + }); + const channelzGrpcObject = loadPackageDefinition(loadedProto) as unknown as ChannelzProtoGrpcType; + loadedChannelzDefinition = channelzGrpcObject.grpc.channelz.v1.Channelz.service; return loadedChannelzDefinition; } diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index cde7d9185..1bd56634f 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -570,7 +570,6 @@ export class Http2ServerCallStream< const response = this.serializeMessage(value!); this.write(response); - this.emit('sendMessage'); this.sendStatus({ code: Status.OK, details: 'OK', metadata }); } catch (err) { err.code = Status.INTERNAL; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index ef31dadce..4a4091038 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -188,7 +188,7 @@ export class Server { const peerCertificate = tlsSocket.getPeerCertificate(); tlsInfo = { cipherSuiteStandardName: cipherInfo.standardName ?? null, - cipherSuiteOtherName: cipherInfo.standardName ? cipherInfo.name: null, + cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null }; @@ -424,6 +424,7 @@ export class Server { remoteFlowControlWindow: null }; }); + this.listenerChildrenTracker.refChild(channelzRef); this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); resolve('port' in boundSubchannelAddress ? boundSubchannelAddress.port : portNum); @@ -491,6 +492,7 @@ export class Server { remoteFlowControlWindow: null }; }); + this.listenerChildrenTracker.refChild(channelzRef); this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); resolve( @@ -676,6 +678,10 @@ export class Server { throw new Error('Not yet implemented'); } + getChannelzRef() { + return this.channelzRef; + } + private _setupHandlers( http2Server: http2.Http2Server | http2.Http2SecureServer ): void { diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index e7e7a9f08..c11a752ab 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -68,6 +68,11 @@ export type ConnectivityStateListener = ( newState: ConnectivityState ) => void; +export interface SubchannelCallStatsTracker { + addMessageSent(): void; + addMessageReceived(): void; +} + const { HTTP2_HEADER_AUTHORITY, HTTP2_HEADER_CONTENT_TYPE, @@ -178,33 +183,6 @@ export class Subchannel { private messagesReceived = 0; private lastMessageSentTimestamp: Date | null = null; private lastMessageReceivedTimestamp: Date | null = null; - private MessageCountFilter = class extends BaseFilter implements Filter { - private session: http2.ClientHttp2Session; - constructor(private parent: Subchannel) { - super(); - this.session = parent.session!; - } - sendMessage(message: Promise): Promise { - if (this.parent.session === this.session) { - this.parent.messagesSent += 1; - this.parent.lastMessageSentTimestamp = new Date(); - } - return message; - } - receiveMessage(message: Promise): Promise { - if (this.parent.session === this.session) { - this.parent.messagesReceived += 1; - this.parent.lastMessageReceivedTimestamp = new Date(); - } - return message; - } - }; - private MessageCountFilterFactory = class implements FilterFactory { - constructor(private parent: Subchannel) {} - createFilter(callStream: Call): Filter { - return new this.parent.MessageCountFilter(this.parent); - } - } /** * A class representing a connection to a single backend. @@ -848,8 +826,15 @@ export class Subchannel { } } }); - extraFilterFactories.push(new this.MessageCountFilterFactory(this)); - callStream.attachHttp2Stream(http2Stream, this, extraFilterFactories); + callStream.attachHttp2Stream(http2Stream, this, extraFilterFactories, { + addMessageSent: () => { + this.messagesSent += 1; + this.lastMessageSentTimestamp = new Date(); + }, + addMessageReceived: () => { + this.messagesReceived += 1; + } + }); } /** diff --git a/packages/grpc-js/test/test-channelz.ts b/packages/grpc-js/test/test-channelz.ts new file mode 100644 index 000000000..2cca780e8 --- /dev/null +++ b/packages/grpc-js/test/test-channelz.ts @@ -0,0 +1,289 @@ +/* + * 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 * as assert from 'assert'; +import * as protoLoader from '@grpc/proto-loader'; +import * as grpc from '../src'; + +import { ProtoGrpcType } from '../src/generated/channelz' +import { ChannelzClient } from '../src/generated/grpc/channelz/v1/Channelz'; +import { Channel__Output } from '../src/generated/grpc/channelz/v1/Channel'; +import { Server__Output } from '../src/generated/grpc/channelz/v1/Server'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { loadProtoFile } from './common'; + +const loadedChannelzProto = protoLoader.loadSync('channelz.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + `${__dirname}/../../proto` + ] +}); +const channelzGrpcObject = grpc.loadPackageDefinition(loadedChannelzProto) as unknown as ProtoGrpcType; + +const TestServiceClient = loadProtoFile(`${__dirname}/fixtures/test_service.proto`).TestService as ServiceClientConstructor; + +const testServiceImpl: grpc.UntypedServiceImplementation = { + unary(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) { + if (call.request.error) { + setTimeout(() => { + callback({ + code: grpc.status.INVALID_ARGUMENT, + details: call.request.message + }); + }, call.request.errorAfter) + } else { + callback(null, {count: 1}); + } + } +} + +describe('Channelz', () => { + let channelzServer: grpc.Server; + let channelzClient: ChannelzClient; + let testServer: grpc.Server; + let testClient: ServiceClient; + + before((done) => { + channelzServer = new grpc.Server(); + channelzServer.addService(grpc.getChannelzServiceDefinition(), grpc.getChannelzHandlers()); + channelzServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + channelzServer.start(); + channelzClient = new channelzGrpcObject.grpc.channelz.v1.Channelz(`localhost:${port}`, grpc.credentials.createInsecure()); + done(); + }); + }); + + after(() => { + channelzClient.close(); + channelzServer.forceShutdown(); + }); + + beforeEach((done) => { + testServer = new grpc.Server(); + testServer.addService(TestServiceClient.service, testServiceImpl); + testServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + testServer.start(); + testClient = new TestServiceClient(`localhost:${port}`, grpc.credentials.createInsecure()); + done(); + }); + }); + + afterEach(() => { + testClient.close(); + testServer.forceShutdown(); + }); + + it('should see a newly created channel', (done) => { + // Test that the specific test client channel info can be retrieved + channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, result) => { + assert.ifError(error); + assert(result); + assert(result.channel); + assert(result.channel.ref); + assert.strictEqual(+result.channel.ref.channel_id, testClient.getChannel().getChannelzRef().id); + // Test that the channel is in the list of top channels + channelzClient.getTopChannels({start_channel_id: testClient.getChannel().getChannelzRef().id, max_results:1}, (error, result) => { + assert.ifError(error); + assert(result); + assert.strictEqual(result.channel.length, 1); + assert(result.channel[0].ref); + assert.strictEqual(+result.channel[0].ref.channel_id, testClient.getChannel().getChannelzRef().id); + done(); + }); + }); + }); + + it('should see a newly created server', (done) => { + // Test that the specific test server info can be retrieved + channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, result) => { + assert.ifError(error); + assert(result); + assert(result.server); + assert(result.server.ref); + assert.strictEqual(+result.server.ref.server_id, testServer.getChannelzRef().id); + // Test that the server is in the list of servers + channelzClient.getServers({start_server_id: testServer.getChannelzRef().id, max_results: 1}, (error, result) => { + assert.ifError(error); + assert(result); + assert.strictEqual(result.server.length, 1); + assert(result.server[0].ref); + assert.strictEqual(+result.server[0].ref.server_id, testServer.getChannelzRef().id); + done(); + }); + }); + }); + + it('should count successful calls', (done) => { + testClient.unary({}, (error: grpc.ServiceError, value: unknown) => { + assert.ifError(error); + // Channel data tests + channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, channelResult) => { + assert.ifError(error); + assert(channelResult); + assert(channelResult.channel); + assert(channelResult.channel.ref); + assert(channelResult.channel.data); + assert.strictEqual(+channelResult.channel.data.calls_started, 1); + assert.strictEqual(+channelResult.channel.data.calls_succeeded, 1); + assert.strictEqual(+channelResult.channel.data.calls_failed, 0); + assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); + channelzClient.getSubchannel({subchannel_id: channelResult.channel.subchannel_ref[0].subchannel_id}, (error, subchannelResult) => { + assert.ifError(error); + assert(subchannelResult); + assert(subchannelResult.subchannel); + assert(subchannelResult.subchannel.ref); + assert(subchannelResult.subchannel.data); + assert.strictEqual(subchannelResult.subchannel.ref.subchannel_id, channelResult.channel!.subchannel_ref[0].subchannel_id); + assert.strictEqual(+subchannelResult.subchannel.data.calls_started, 1); + assert.strictEqual(+subchannelResult.subchannel.data.calls_succeeded, 1); + assert.strictEqual(+subchannelResult.subchannel.data.calls_failed, 0); + assert.strictEqual(subchannelResult.subchannel.socket_ref.length, 1); + channelzClient.getSocket({socket_id: subchannelResult.subchannel.socket_ref[0].socket_id}, (error, socketResult) => { + assert.ifError(error); + assert(socketResult); + assert(socketResult.socket); + assert(socketResult.socket.ref); + assert(socketResult.socket.data); + assert.strictEqual(socketResult.socket.ref.socket_id, subchannelResult.subchannel!.socket_ref[0].socket_id); + assert.strictEqual(+socketResult.socket.data.streams_started, 1); + assert.strictEqual(+socketResult.socket.data.streams_succeeded, 1); + assert.strictEqual(+socketResult.socket.data.streams_failed, 0); + assert.strictEqual(+socketResult.socket.data.messages_received, 1); + assert.strictEqual(+socketResult.socket.data.messages_sent, 1); + // Server data tests + channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, serverResult) => { + assert.ifError(error); + assert(serverResult); + assert(serverResult.server); + assert(serverResult.server.ref); + assert(serverResult.server.data); + assert.strictEqual(+serverResult.server.ref.server_id, testServer.getChannelzRef().id); + assert.strictEqual(+serverResult.server.data.calls_started, 1); + assert.strictEqual(+serverResult.server.data.calls_succeeded, 1); + assert.strictEqual(+serverResult.server.data.calls_failed, 0); + channelzClient.getServerSockets({server_id: testServer.getChannelzRef().id}, (error, socketsResult) => { + assert.ifError(error); + assert(socketsResult); + assert.strictEqual(socketsResult.socket_ref.length, 1); + channelzClient.getSocket({socket_id: socketsResult.socket_ref[0].socket_id}, (error, serverSocketResult) => { + assert.ifError(error); + assert(serverSocketResult); + assert(serverSocketResult.socket); + assert(serverSocketResult.socket.ref); + assert(serverSocketResult.socket.data); + assert.strictEqual(serverSocketResult.socket.ref.socket_id, socketsResult.socket_ref[0].socket_id); + assert.strictEqual(+serverSocketResult.socket.data.streams_started, 1); + assert.strictEqual(+serverSocketResult.socket.data.streams_succeeded, 1); + assert.strictEqual(+serverSocketResult.socket.data.streams_failed, 0); + assert.strictEqual(+serverSocketResult.socket.data.messages_received, 1); + assert.strictEqual(+serverSocketResult.socket.data.messages_sent, 1); + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('should count failed calls', (done) => { + testClient.unary({error: true}, (error: grpc.ServiceError, value: unknown) => { + assert(error); + // Channel data tests + channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, channelResult) => { + assert.ifError(error); + assert(channelResult); + assert(channelResult.channel); + assert(channelResult.channel.ref); + assert(channelResult.channel.data); + assert.strictEqual(+channelResult.channel.data.calls_started, 1); + assert.strictEqual(+channelResult.channel.data.calls_succeeded, 0); + assert.strictEqual(+channelResult.channel.data.calls_failed, 1); + assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); + channelzClient.getSubchannel({subchannel_id: channelResult.channel.subchannel_ref[0].subchannel_id}, (error, subchannelResult) => { + assert.ifError(error); + assert(subchannelResult); + assert(subchannelResult.subchannel); + assert(subchannelResult.subchannel.ref); + assert(subchannelResult.subchannel.data); + assert.strictEqual(subchannelResult.subchannel.ref.subchannel_id, channelResult.channel!.subchannel_ref[0].subchannel_id); + assert.strictEqual(+subchannelResult.subchannel.data.calls_started, 1); + assert.strictEqual(+subchannelResult.subchannel.data.calls_succeeded, 0); + assert.strictEqual(+subchannelResult.subchannel.data.calls_failed, 1); + assert.strictEqual(subchannelResult.subchannel.socket_ref.length, 1); + channelzClient.getSocket({socket_id: subchannelResult.subchannel.socket_ref[0].socket_id}, (error, socketResult) => { + assert.ifError(error); + assert(socketResult); + assert(socketResult.socket); + assert(socketResult.socket.ref); + assert(socketResult.socket.data); + assert.strictEqual(socketResult.socket.ref.socket_id, subchannelResult.subchannel!.socket_ref[0].socket_id); + assert.strictEqual(+socketResult.socket.data.streams_started, 1); + assert.strictEqual(+socketResult.socket.data.streams_succeeded, 1); + assert.strictEqual(+socketResult.socket.data.streams_failed, 0); + assert.strictEqual(+socketResult.socket.data.messages_received, 0); + assert.strictEqual(+socketResult.socket.data.messages_sent, 1); + // Server data tests + channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, serverResult) => { + assert.ifError(error); + assert(serverResult); + assert(serverResult.server); + assert(serverResult.server.ref); + assert(serverResult.server.data); + assert.strictEqual(+serverResult.server.ref.server_id, testServer.getChannelzRef().id); + assert.strictEqual(+serverResult.server.data.calls_started, 1); + assert.strictEqual(+serverResult.server.data.calls_succeeded, 0); + assert.strictEqual(+serverResult.server.data.calls_failed, 1); + channelzClient.getServerSockets({server_id: testServer.getChannelzRef().id}, (error, socketsResult) => { + assert.ifError(error); + assert(socketsResult); + assert.strictEqual(socketsResult.socket_ref.length, 1); + channelzClient.getSocket({socket_id: socketsResult.socket_ref[0].socket_id}, (error, serverSocketResult) => { + assert.ifError(error); + assert(serverSocketResult); + assert(serverSocketResult.socket); + assert(serverSocketResult.socket.ref); + assert(serverSocketResult.socket.data); + assert.strictEqual(serverSocketResult.socket.ref.socket_id, socketsResult.socket_ref[0].socket_id); + assert.strictEqual(+serverSocketResult.socket.data.streams_started, 1); + assert.strictEqual(+serverSocketResult.socket.data.streams_succeeded, 0); + assert.strictEqual(+serverSocketResult.socket.data.streams_failed, 1); + assert.strictEqual(+serverSocketResult.socket.data.messages_received, 1); + assert.strictEqual(+serverSocketResult.socket.data.messages_sent, 0); + done(); + }); + }); + }); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/channelz/channelz_manual_test.js b/test/channelz/channelz_manual_test.js new file mode 100644 index 000000000..2f77df3cc --- /dev/null +++ b/test/channelz/channelz_manual_test.js @@ -0,0 +1,73 @@ +/* + * + * Copyright 2021 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. + * + */ + +'use strict'; + +require('../fixtures/js_js'); +const interopClient = require('../interop/interop_client'); +const interopServer = require('../interop/interop_server'); +const serverGrpc = require('../any_grpc').server; + +const hostOverride = 'foo.test.google.fr'; + +const testCases = [ + 'empty_unary', + 'large_unary', + 'client_streaming', + 'server_streaming', + 'ping_pong', + 'empty_stream', + 'cancel_after_begin', + 'cancel_after_first_response', + 'timeout_on_sleeping_server', + 'custom_metadata', + 'status_code_and_message', + 'special_status_message', + 'unimplemented_service', + 'unimplemented_method' +]; + +function getRandomTest() { + return testCases[(Math.random() * testCases.length) | 0]; +} + +let testCompleteCount = 0; + +interopServer.getServer('0', true, (error, result) => { + if (error) { + throw error; + } + const channelzServer = new serverGrpc.Server(); + channelzServer.bindAsync('localhost:0', serverGrpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + throw error; + } + console.log(`Serving channelz at port ${port}`); + serverGrpc.addAdminServicesToServer(channelzServer); + channelzServer.start(); + result.server.start(); + setInterval(() => { + interopClient.runTest(`localhost:${result.port}`, hostOverride, getRandomTest(), true, true, () => { + testCompleteCount += 1; + if (testCompleteCount % 100 === 0) { + console.log(`Completed ${testCompleteCount} tests`); + } + }); + }, 100); + }); +}) \ No newline at end of file diff --git a/test/interop/interop_server.js b/test/interop/interop_server.js index cf7ae354e..b67ec5a8a 100644 --- a/test/interop/interop_server.js +++ b/test/interop/interop_server.js @@ -200,7 +200,7 @@ function handleHalfDuplex(call) { * Get a server object bound to the given port * @param {string} port Port to which to bind * @param {boolean} tls Indicates that the bound port should use TLS - * @param {function(Error, {{server: Server, port: number}})} callback Callback + * @param {function(Error, {server: Server, port: number})} callback Callback * to call with result or error * @param {object?} options Optional additional options to use when * constructing the server From 4b6ea2b78113c8e8b0c1caad0e0a010d8ca6f310 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 27 Sep 2021 10:47:22 -0700 Subject: [PATCH 083/694] Add channelz ID to trace logs, add a few trace log lines --- packages/grpc-js/src/channel.ts | 56 ++++++++++++----------------- packages/grpc-js/src/server.ts | 20 ++++++----- packages/grpc-js/src/subchannel.ts | 57 ++++++++++++++---------------- 3 files changed, 59 insertions(+), 74 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 636ebc44b..1b5ed3cc0 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -297,13 +297,7 @@ export class ChannelImplementation implements Channel { (status) => { this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); if (this.configSelectionQueue.length > 0) { - trace( - LogVerbosity.DEBUG, - 'channel', - 'Name resolution failed for target ' + - uriToString(this.target) + - ' with calls queued for config selection' - ); + this.trace('Name resolution failed with calls queued for config selection'); } const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; @@ -324,6 +318,7 @@ export class ChannelImplementation implements Channel { new MaxMessageSizeFilterFactory(this.options), new CompressionFilterFactory(this), ]); + this.trace('Constructed channel'); } private getChannelzInfo(): ChannelInfo { @@ -336,12 +331,14 @@ export class ChannelImplementation implements Channel { }; } + private trace(text: string, verbosityOverride?: LogVerbosity) { + trace(verbosityOverride ?? LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + uriToString(this.target) + ' ' + text); + } + private callRefTimerRef() { // If the hasRef function does not exist, always run the code if (!this.callRefTimer.hasRef?.()) { - trace( - LogVerbosity.DEBUG, - 'channel', + this.trace( 'callRefTimer.ref | configSelectionQueue.length=' + this.configSelectionQueue.length + ' pickQueue.length=' + @@ -354,9 +351,7 @@ export class ChannelImplementation implements Channel { private callRefTimerUnref() { // If the hasRef function does not exist, always run the code if (!this.callRefTimer.hasRef || this.callRefTimer.hasRef()) { - trace( - LogVerbosity.DEBUG, - 'channel', + this.trace( 'callRefTimer.unref | configSelectionQueue.length=' + this.configSelectionQueue.length + ' pickQueue.length=' + @@ -391,9 +386,7 @@ export class ChannelImplementation implements Channel { metadata: callMetadata, extraPickInfo: callConfig.pickInformation, }); - trace( - LogVerbosity.DEBUG, - 'channel', + this.trace( 'Pick result: ' + PickResultType[pickResult.pickResultType] + ' subchannel: ' + @@ -466,25 +459,23 @@ export class ChannelImplementation implements Channel { * the stream because the correct behavior may be * re-queueing instead, based on the logic in the rest of * tryPick */ - trace( - LogVerbosity.INFO, - 'channel', + this.trace( 'Failed to start call on picked subchannel ' + pickResult.subchannel!.getAddress() + ' with error ' + (error as Error).message + - '. Retrying pick' + '. Retrying pick', + LogVerbosity.INFO ); this.tryPick(callStream, callMetadata, callConfig); } else { - trace( - LogVerbosity.INFO, - 'channel', + this.trace( 'Failed to start call on picked subchanel ' + pickResult.subchannel!.getAddress() + ' with error ' + (error as Error).message + - '. Ending call' + '. Ending call', + LogVerbosity.INFO ); callStream.cancelWithStatus( Status.INTERNAL, @@ -497,14 +488,13 @@ export class ChannelImplementation implements Channel { } else { /* The logic for doing this here is the same as in the catch * block above */ - trace( - LogVerbosity.INFO, - 'channel', + this.trace( 'Picked subchannel ' + pickResult.subchannel!.getAddress() + ' has state ' + ConnectivityState[subchannelState] + - ' after metadata filters. Retrying pick' + ' after metadata filters. Retrying pick', + LogVerbosity.INFO ); this.tryPick(callStream, callMetadata, callConfig); } @@ -560,7 +550,8 @@ export class ChannelImplementation implements Channel { trace( LogVerbosity.DEBUG, 'connectivity_state', - uriToString(this.target) + + '(' + this.channelzRef.id + ') ' + + uriToString(this.target) + ' ' + ConnectivityState[this.connectivityState] + ' -> ' + @@ -706,11 +697,8 @@ export class ChannelImplementation implements Channel { throw new Error('Channel has been shut down'); } const callNumber = getNewCallNumber(); - trace( - LogVerbosity.DEBUG, - 'channel', - uriToString(this.target) + - ' createCall [' + + this.trace( + 'createCall [' + callNumber + '] method="' + method + diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 4a4091038..caaa2fa3d 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -64,10 +64,6 @@ import { CipherNameAndProtocol, TLSSocket } from 'tls'; const TRACER_NAME = 'server'; -function trace(text: string): void { - logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); -} - interface BindResult { port: number; count: number; @@ -163,6 +159,7 @@ export class Server { this.options = options ?? {}; this.channelzRef = registerChannelzServer(() => this.getChannelzInfo()); this.channelzTrace.addTrace('CT_INFO', 'Server created'); + this.trace('Server constructed'); } private getChannelzInfo(): ServerInfo { @@ -217,6 +214,11 @@ export class Server { }; } + private trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + text); + } + + addProtoService(): void { throw new Error('Not implemented. Use addService() instead'); } @@ -372,7 +374,7 @@ export class Server { } return Promise.all( addressList.map((address) => { - trace('Attempting to bind ' + subchannelAddressToString(address)); + this.trace('Attempting to bind ' + subchannelAddressToString(address)); let addr: SubchannelAddress; if (isTcpSubchannelAddress(address)) { addr = { @@ -426,7 +428,7 @@ export class Server { }); this.listenerChildrenTracker.refChild(channelzRef); this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); - trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); + this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); resolve('port' in boundSubchannelAddress ? boundSubchannelAddress.port : portNum); http2Server.removeListener('error', onError); }); @@ -494,7 +496,7 @@ export class Server { }); this.listenerChildrenTracker.refChild(channelzRef); this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); - trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); + this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); resolve( bindSpecificPort( addressList.slice(1), @@ -727,7 +729,7 @@ export class Server { serverAddress.address + ':' + serverAddress.port; } } - trace( + this.trace( 'Received call to method ' + path + ' at address ' + @@ -736,7 +738,7 @@ export class Server { const handler = this.handlers.get(path); if (handler === undefined) { - trace( + this.trace( 'No handler registered for method ' + path + '. Sending UNIMPLEMENTED status.' diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index c11a752ab..25f06b144 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -42,14 +42,6 @@ const clientVersion = require('../../package.json').version; const TRACER_NAME = 'subchannel'; -function trace(text: string): void { - logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); -} - -function refTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, 'subchannel_refcount', text); -} - const MIN_CONNECT_TIMEOUT_MS = 20000; const INITIAL_BACKOFF_MS = 1000; const BACKOFF_MULTIPLIER = 1.6; @@ -237,6 +229,7 @@ export class Subchannel { this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); this.channelzTrace = new ChannelzTrace(); this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); + this.trace('Subchannel constructed'); } private getChannelzInfo(): SubchannelInfo { @@ -307,6 +300,14 @@ export class Subchannel { this.lastMessageReceivedTimestamp = null; } + private trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private refTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'subchannel_refcount', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + private handleBackoffTimer() { if (this.continueConnecting) { this.transitionToState( @@ -338,7 +339,8 @@ export class Subchannel { logging.trace( LogVerbosity.DEBUG, 'keepalive', - 'Sending ping to ' + this.subchannelAddressString + '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + + 'Sending ping' ); this.keepaliveTimeoutId = setTimeout(() => { this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); @@ -511,9 +513,8 @@ export class Subchannel { } ms` ); } - trace( - this.subchannelAddressString + - ' connection closed by GOAWAY with code ' + + this.trace( + 'connection closed by GOAWAY with code ' + errorCode ); this.transitionToState( @@ -526,9 +527,8 @@ export class Subchannel { session.once('error', (error) => { /* Do nothing here. Any error should also trigger a close event, which is * where we want to handle that. */ - trace( - this.subchannelAddressString + - ' connection closed with error ' + + this.trace( + 'connection closed with error ' + (error as Error).message ); }); @@ -607,10 +607,8 @@ export class Subchannel { if (oldStates.indexOf(this.connectivityState) === -1) { return false; } - trace( - this.subchannelAddressString + - ' ' + - ConnectivityState[this.connectivityState] + + this.trace( + ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState] ); @@ -687,9 +685,8 @@ export class Subchannel { } callRef() { - refTrace( - this.subchannelAddressString + - ' callRefcount ' + + this.refTrace( + 'callRefcount ' + this.callRefcount + ' -> ' + (this.callRefcount + 1) @@ -707,9 +704,8 @@ export class Subchannel { } callUnref() { - refTrace( - this.subchannelAddressString + - ' callRefcount ' + + this.refTrace( + 'callRefcount ' + this.callRefcount + ' -> ' + (this.callRefcount - 1) @@ -728,9 +724,8 @@ export class Subchannel { } ref() { - refTrace( - this.subchannelAddressString + - ' refcount ' + + this.refTrace( + 'refcount ' + this.refcount + ' -> ' + (this.refcount + 1) @@ -739,9 +734,8 @@ export class Subchannel { } unref() { - refTrace( - this.subchannelAddressString + - ' refcount ' + + this.refTrace( + 'refcount ' + this.refcount + ' -> ' + (this.refcount - 1) @@ -803,6 +797,7 @@ export class Subchannel { LogVerbosity.DEBUG, 'call_stream', 'Starting stream on subchannel ' + + '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' with headers\n' + headersString From 8cc5f6cd24490583d204877d1d5cd9772a197f58 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 27 Sep 2021 12:35:21 -0700 Subject: [PATCH 084/694] grpc-js: Loosen requirements on channel option types --- packages/grpc-js/src/channel.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 6dfca312d..ebdac7bce 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -177,18 +177,8 @@ export class ChannelImplementation implements Channel { ); } if (options) { - if ( - typeof options !== 'object' || - !Object.values(options).every( - (value) => - typeof value === 'string' || - typeof value === 'number' || - typeof value === 'undefined' - ) - ) { - throw new TypeError( - 'Channel options must be an object with string or number values' - ); + if (typeof options !== 'object') { + throw new TypeError('Channel options must be an object'); } } const originalTargetUri = parseUri(target); From 157882da451b8be623cc50a139c75b4031c9dab4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 27 Sep 2021 15:45:22 -0700 Subject: [PATCH 085/694] grpc-js-xds: A few fixes for xDS tests --- packages/grpc-js-xds/scripts/xds.sh | 2 +- packages/grpc-js-xds/src/xds-bootstrap.ts | 2 +- packages/grpc-js-xds/src/xds-client.ts | 13 +++++++++++-- packages/grpc-js/src/server.ts | 2 ++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 131cbd551..a06cde84b 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -59,7 +59,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh --gcp_suffix=$(date '+%s') \ --verbose \ ${XDS_V3_OPT-} \ - --client_cmd="$(which node) grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ + --client_cmd="$(which node) --enable-source-maps grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ --stats_port={stats_port} \ --qps={qps} \ diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 64a7bcb94..876b6d958 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -109,7 +109,7 @@ function validateXdsServerConfig(obj: any): XdsServerConfig { return { serverUri: obj.server_uri, channelCreds: obj.channel_creds.map(validateChannelCredsConfig), - serverFeatures: obj.server_features + serverFeatures: obj.server_features ?? [] }; } diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 41bc27147..cfb8b66ac 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -341,6 +341,16 @@ export class XdsClient { return; } trace('Loaded bootstrap info: ' + JSON.stringify(bootstrapInfo, undefined, 2)); + if (bootstrapInfo.xdsServers.length < 1) { + trace('Failed to initialize xDS Client. No servers provided in bootstrap info.'); + // Bubble this error up to any listeners + this.reportStreamError({ + code: status.INTERNAL, + details: 'Failed to initialize xDS Client. No servers provided in bootstrap info.', + metadata: new Metadata(), + }); + return; + } if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('xds_v3') >= 0) { this.apiVersion = XdsApiVersion.V3; } else { @@ -425,8 +435,7 @@ export class XdsClient { {channelOverride: channel} ); this.maybeStartLrsStream(); - }, - (error) => { + }).catch((error) => { trace('Failed to initialize xDS Client. ' + error.message); // Bubble this error up to any listeners this.reportStreamError({ diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 42b79a3cf..ed4b397d0 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -309,6 +309,7 @@ export class Server { const http2Server = setupServer(); return new Promise((resolve, reject) => { function onError(err: Error): void { + trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); resolve(err); } @@ -356,6 +357,7 @@ export class Server { const http2Server = setupServer(); return new Promise((resolve, reject) => { function onError(err: Error): void { + trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); resolve(bindWildcardPort(addressList.slice(1))); } From 2756a594958f679e6179a6c74ee61e3541e871ea Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 27 Sep 2021 16:12:23 -0700 Subject: [PATCH 086/694] Add a helper for creating ChannelControlHelper children --- packages/grpc-js-xds/src/load-balancer-eds.ts | 11 ++--------- packages/grpc-js-xds/src/load-balancer-lrs.ts | 10 ++-------- .../grpc-js-xds/src/load-balancer-priority.ts | 16 ++-------------- .../src/load-balancer-weighted-target.ts | 12 +++--------- .../src/load-balancer-xds-cluster-manager.ts | 12 +++--------- packages/grpc-js/src/experimental.ts | 1 + packages/grpc-js/src/load-balancer.ts | 18 ++++++++++++++++++ packages/grpc-tools/deps/protobuf | 2 +- 8 files changed, 32 insertions(+), 50 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index efb05eb78..55ad714a9 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -185,14 +185,7 @@ export class EdsLoadBalancer implements LoadBalancer { private concurrentRequests: number = 0; constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.childBalancer = new ChildLoadBalancerHandler({ - createSubchannel: (subchannelAddress, subchannelArgs) => - this.channelControlHelper.createSubchannel( - subchannelAddress, - subchannelArgs - ), - requestReresolution: () => - this.channelControlHelper.requestReresolution(), + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.channelControlHelper, { updateState: (connectivityState, originalPicker) => { if (this.latestEdsUpdate === null) { return; @@ -243,7 +236,7 @@ export class EdsLoadBalancer implements LoadBalancer { }; this.channelControlHelper.updateState(connectivityState, edsPicker); }, - }); + })); this.watcher = { onValidUpdate: (update) => { trace('Received EDS update for ' + this.edsServiceName + ': ' + JSON.stringify(update, undefined, 2)); diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 566aa04c5..145501fe9 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -174,20 +174,14 @@ export class LrsLoadBalancer implements LoadBalancer { private localityStatsReporter: XdsClusterLocalityStats | null = null; constructor(private channelControlHelper: ChannelControlHelper) { - this.childBalancer = new ChildLoadBalancerHandler({ - createSubchannel: (subchannelAddress, subchannelArgs) => - channelControlHelper.createSubchannel( - subchannelAddress, - subchannelArgs - ), - requestReresolution: () => channelControlHelper.requestReresolution(), + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(channelControlHelper, { updateState: (connectivityState: ConnectivityState, picker: Picker) => { if (this.localityStatsReporter !== null) { picker = new LoadReportingPicker(picker, this.localityStatsReporter); } channelControlHelper.updateState(connectivityState, picker); }, - }); + })); } updateAddressList( diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index 872ed7d1b..b05e459a7 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -139,23 +139,11 @@ export class PriorityLoadBalancer implements LoadBalancer { private failoverTimer: NodeJS.Timer | null = null; private deactivationTimer: NodeJS.Timer | null = null; constructor(private parent: PriorityLoadBalancer, private name: string) { - this.childBalancer = new ChildLoadBalancerHandler({ - createSubchannel: ( - subchannelAddress: SubchannelAddress, - subchannelArgs: ChannelOptions - ) => { - return this.parent.channelControlHelper.createSubchannel( - subchannelAddress, - subchannelArgs - ); - }, + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.updateState(connectivityState, picker); }, - requestReresolution: () => { - this.parent.channelControlHelper.requestReresolution(); - }, - }); + })); this.picker = new QueuePicker(this.childBalancer); } diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts index 44a6acf11..a7a28c663 100644 --- a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -168,17 +168,11 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { private weight: number = 0; constructor(private parent: WeightedTargetLoadBalancer, private name: string) { - this.childBalancer = new ChildLoadBalancerHandler({ - createSubchannel: (subchannelAddress, subchannelOptions) => { - return this.parent.channelControlHelper.createSubchannel(subchannelAddress, subchannelOptions); - }, - updateState: (connectivityState, picker) => { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { + updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.updateState(connectivityState, picker); }, - requestReresolution: () => { - this.parent.channelControlHelper.requestReresolution(); - } - }); + })); this.picker = new QueuePicker(this.childBalancer); } diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index 7df79e26b..6210ea84a 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -131,17 +131,11 @@ class XdsClusterManager implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; constructor(private parent: XdsClusterManager, private name: string) { - this.childBalancer = new ChildLoadBalancerHandler({ - createSubchannel: (subchannelAddress, subchannelOptions) => { - return this.parent.channelControlHelper.createSubchannel(subchannelAddress, subchannelOptions); - }, - updateState: (connectivityState, picker) => { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { + updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.updateState(connectivityState, picker); }, - requestReresolution: () => { - this.parent.channelControlHelper.requestReresolution(); - } - }); + })); this.picker = new QueuePicker(this.childBalancer); } diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 4d158a53f..5353f7f59 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -12,6 +12,7 @@ export { LoadBalancer, LoadBalancingConfig, ChannelControlHelper, + createChildChannelControlHelper, registerLoadBalancerType, getFirstUsableConfig, validateLoadBalancingConfig, diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index 870fdb30b..19b79764c 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -52,6 +52,24 @@ export interface ChannelControlHelper { removeChannelzChild(child: ChannelRef | SubchannelRef): void; } +/** + * Create a child ChannelControlHelper that overrides some methods of the + * parent while letting others pass through to the parent unmodified. This + * allows other code to create these children without needing to know about + * all of the methods to be passed through. + * @param parent + * @param overrides + */ +export function createChildChannelControlHelper(parent: ChannelControlHelper, overrides: Partial): ChannelControlHelper { + return { + createSubchannel: overrides.createSubchannel?.bind(overrides) ?? parent.createSubchannel.bind(parent), + updateState: overrides.updateState?.bind(overrides) ?? parent.updateState.bind(parent), + requestReresolution: overrides.requestReresolution?.bind(overrides) ?? parent.requestReresolution.bind(parent), + addChannelzChild: overrides.addChannelzChild?.bind(overrides) ?? parent.addChannelzChild.bind(parent), + removeChannelzChild: overrides.removeChannelzChild?.bind(overrides) ?? parent.removeChannelzChild.bind(parent) + }; +} + /** * Tracks one or more connected subchannels and determines which subchannel * each request should use. diff --git a/packages/grpc-tools/deps/protobuf b/packages/grpc-tools/deps/protobuf index a6e1cc7e3..6aa539bf0 160000 --- a/packages/grpc-tools/deps/protobuf +++ b/packages/grpc-tools/deps/protobuf @@ -1 +1 @@ -Subproject commit a6e1cc7e328c45a0cb9856c530c8f6cd23314163 +Subproject commit 6aa539bf0195f188ff86efe6fb8bfa2b676cdd46 From 1eea4b75bd8744757ac70ab87cae360733fe7f95 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 28 Sep 2021 13:14:14 -0700 Subject: [PATCH 087/694] In server.bindAsync, call callback in process.nextTick --- packages/grpc-js/src/server.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index ed4b397d0..87788158c 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -268,6 +268,10 @@ export class Server { }; } + const deferredCallback = (error: Error | null, port: number) => { + process.nextTick(() => callback(error, port)); + } + const setupServer = (): http2.Http2Server | http2.Http2SecureServer => { let http2Server: http2.Http2Server | http2.Http2SecureServer; if (creds._isSecure()) { @@ -386,7 +390,7 @@ export class Server { // We only want one resolution result. Discard all future results resolverListener.onSuccessfulResolution = () => {}; if (addressList.length === 0) { - callback(new Error(`No addresses resolved for port ${port}`), 0); + deferredCallback(new Error(`No addresses resolved for port ${port}`), 0); return; } let bindResultPromise: Promise; @@ -409,7 +413,7 @@ export class Server { if (bindResult.count === 0) { const errorString = `No address added out of total ${addressList.length} resolved`; logging.log(LogVerbosity.ERROR, errorString); - callback(new Error(errorString), 0); + deferredCallback(new Error(errorString), 0); } else { if (bindResult.count < addressList.length) { logging.log( @@ -417,18 +421,18 @@ export class Server { `WARNING Only ${bindResult.count} addresses added out of total ${addressList.length} resolved` ); } - callback(null, bindResult.port); + deferredCallback(null, bindResult.port); } }, (error) => { const errorString = `No address added out of total ${addressList.length} resolved`; logging.log(LogVerbosity.ERROR, errorString); - callback(new Error(errorString), 0); + deferredCallback(new Error(errorString), 0); } ); }, onError: (error) => { - callback(new Error(error.details), 0); + deferredCallback(new Error(error.details), 0); }, }; From cb6abd7e38bcc29c1731208afed28dc389edc719 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 30 Sep 2021 08:52:04 -0700 Subject: [PATCH 088/694] grpc-js-xds: Handle all ways control-plane streams can end --- packages/grpc-js-xds/src/xds-client.ts | 32 +++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index cfb8b66ac..2b8fae44c 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -516,13 +516,15 @@ export class XdsClient { } } - private handleAdsCallError(error: ServiceError) { + private handleAdsCallStatus(streamStatus: StatusObject) { trace( - 'ADS stream ended. code=' + error.code + ' details= ' + error.details + 'ADS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details ); this.adsCallV2 = null; this.adsCallV3 = null; - this.reportStreamError(error); + if (streamStatus.code !== status.OK) { + this.reportStreamError(streamStatus); + } /* If the backoff timer is no longer running, we do not need to wait any * more to start the new call. */ if (!this.adsBackoff.isRunning()) { @@ -544,9 +546,10 @@ export class XdsClient { this.adsCallV2.on('data', (message: DiscoveryResponse__Output) => { this.handleAdsResponse(message); }); - this.adsCallV2.on('error', (error: ServiceError) => { - this.handleAdsCallError(error); + this.adsCallV2.on('status', (status: StatusObject) => { + this.handleAdsCallStatus(status); }); + this.adsCallV2.on('error', () => {}); return true; } @@ -564,9 +567,10 @@ export class XdsClient { this.adsCallV3.on('data', (message: DiscoveryResponse__Output) => { this.handleAdsResponse(message); }); - this.adsCallV3.on('error', (error: ServiceError) => { - this.handleAdsCallError(error); + this.adsCallV3.on('status', (status: StatusObject) => { + this.handleAdsCallStatus(status); }); + this.adsCallV3.on('error', () => {}); return true; } @@ -772,9 +776,9 @@ export class XdsClient { this.receivedLrsSettingsForCurrentStream = true; } - private handleLrsCallError(error: ServiceError) { + private handleLrsCallStatus(streamStatus: StatusObject) { trace( - 'LRS stream ended. code=' + error.code + ' details= ' + error.details + 'LRS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details ); this.lrsCallV2 = null; this.lrsCallV3 = null; @@ -798,9 +802,10 @@ export class XdsClient { this.lrsCallV2.on('data', (message: LoadStatsResponse__Output) => { this.handleLrsResponse(message); }); - this.lrsCallV2.on('error', (error: ServiceError) => { - this.handleLrsCallError(error); + this.lrsCallV2.on('status', (status: StatusObject) => { + this.handleLrsCallStatus(status); }); + this.lrsCallV2.on('error', () => {}); return true; } @@ -816,9 +821,10 @@ export class XdsClient { this.lrsCallV3.on('data', (message: LoadStatsResponse__Output) => { this.handleLrsResponse(message); }); - this.lrsCallV3.on('error', (error: ServiceError) => { - this.handleLrsCallError(error); + this.lrsCallV3.on('status', (status: StatusObject) => { + this.handleLrsCallStatus(status); }); + this.lrsCallV3.on('error', () => {}); return true; } From c7d9598067918839c35d43594db6b4d72aa51596 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 5 Oct 2021 14:52:26 -0700 Subject: [PATCH 089/694] grpc-js-xds: Fix RDS and EDS missing resource handling --- packages/grpc-js-xds/src/xds-stream-state/eds-state.ts | 1 - packages/grpc-js-xds/src/xds-stream-state/lds-state.ts | 6 ++++++ packages/grpc-js-xds/src/xds-stream-state/rds-state.ts | 3 +-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index dbf18ef81..3861f4d2a 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -163,7 +163,6 @@ export class EdsState implements XdsStreamState { } } trace('Received EDS updates for cluster names ' + Array.from(allClusterNames)); - this.handleMissingNames(allClusterNames); return null; } diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 7e8ec0456..10e71babf 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -163,8 +163,13 @@ export class LdsState implements XdsStreamState { this.latestResponses = responses; this.latestIsV2 = isV2; const allTargetNames = new Set(); + const allRouteConfigNames = new Set(); for (const message of responses) { allTargetNames.add(message.name); + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener!.value); + if (httpConnectionManager.rds) { + allRouteConfigNames.add(httpConnectionManager.rds.route_config_name); + } const watchers = this.watchers.get(message.name) ?? []; for (const watcher of watchers) { watcher.onValidUpdate(message, isV2); @@ -172,6 +177,7 @@ export class LdsState implements XdsStreamState { } trace('Received RDS response with route config names ' + Array.from(allTargetNames)); this.handleMissingNames(allTargetNames); + this.rdsState.handleMissingNames(allRouteConfigNames); return null; } diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 9194529d8..ec7abe55a 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -172,7 +172,7 @@ export class RdsState implements XdsStreamState { return true; } - private handleMissingNames(allRouteConfigNames: Set) { + handleMissingNames(allRouteConfigNames: Set) { for (const [routeConfigName, watcherList] of this.watchers.entries()) { if (!allRouteConfigNames.has(routeConfigName)) { for (const watcher of watcherList) { @@ -200,7 +200,6 @@ export class RdsState implements XdsStreamState { } } trace('Received RDS response with route config names ' + Array.from(allRouteConfigNames)); - this.handleMissingNames(allRouteConfigNames); return null; } From 16303d9f0bb48e897af3ef79cbca1b50a05f4268 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 6 Oct 2021 14:56:53 -0700 Subject: [PATCH 090/694] grpc-js: Fix server trace function inconsistency --- packages/grpc-js/src/server.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index fb62163b3..aed04adec 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -391,8 +391,8 @@ export class Server { const http2Server = setupServer(); return new Promise((resolve, reject) => { - function onError(err: Error): void { - trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); + const onError = (err: Error) => { + this.trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); resolve(err); } @@ -467,8 +467,8 @@ export class Server { const address = addressList[0]; const http2Server = setupServer(); return new Promise((resolve, reject) => { - function onError(err: Error): void { - trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); + const onError = (err: Error) => { + this.trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); resolve(bindWildcardPort(addressList.slice(1))); } From 590e94e09dd9b0dc2af04f122d7ee2cd45acae96 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Oct 2021 14:23:05 -0700 Subject: [PATCH 091/694] grpc-js: Update package versions for 1.4.0 release --- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 6506112c4..fefc0f33b 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.3.1", + "version": "1.4.0", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -47,7 +47,7 @@ "re2-wasm": "^1.0.1" }, "peerDependencies": { - "@grpc/grpc-js": "~1.3.0" + "@grpc/grpc-js": "~1.4.0" }, "engines": { "node": ">=10.10.0" diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 63137c977..856b71471 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.3.7", + "version": "1.4.0", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 7564f60dcbbc51e0ed3ec8c099bc9c5d7e353b3d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 12 Oct 2021 12:25:47 -0700 Subject: [PATCH 092/694] grpc-js: Add tracing of channel options in channel and subchannel constructors --- packages/grpc-js/src/channel.ts | 2 +- packages/grpc-js/src/subchannel.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index d25abb05c..f998ecef4 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -311,7 +311,7 @@ export class ChannelImplementation implements Channel { new MaxMessageSizeFilterFactory(this.options), new CompressionFilterFactory(this), ]); - this.trace('Constructed channel'); + this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2)); } private getChannelzInfo(): ChannelInfo { diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index be58fe02b..a3b86b283 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -229,7 +229,7 @@ export class Subchannel { this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); this.channelzTrace = new ChannelzTrace(); this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); - this.trace('Subchannel constructed'); + this.trace('Subchannel constructed with options ' + JSON.stringify(options, undefined, 2)); } private getChannelzInfo(): SubchannelInfo { From 24df9a039b866c74d39cf6afd925859969e51fd8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Oct 2021 11:10:19 -0700 Subject: [PATCH 093/694] grpc-js: Publish missing channelz proto and type files --- packages/grpc-js/package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 856b71471..9705383ea 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.4.0", + "version": "1.4.1", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", @@ -48,11 +48,11 @@ "compile": "tsc -p .", "format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts", "lint": "npm run check", - "prepare": "npm run compile", + "prepare": "npm run generate-types && npm run compile", "test": "gulp test", "check": "gts check src/**/*.ts", "fix": "gts fix src/*.ts", - "pretest": "npm run compile", + "pretest": "npm run generate-types && npm run compile", "posttest": "npm run check && madge -c ./build/src", "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated/ --grpcLib ../index channelz.proto" }, @@ -62,7 +62,8 @@ }, "files": [ "src/**/*.ts", - "build/src/*.{js,d.ts,js.map}", + "build/src/**/*.{js,d.ts,js.map}", + "proto/*.proto", "LICENSE", "deps/envoy-api/envoy/api/v2/**/*.proto", "deps/envoy-api/envoy/config/**/*.proto", From 624a839db9560adeba62e5dea37dec78a7a298ed Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Oct 2021 11:34:32 -0700 Subject: [PATCH 094/694] Drop testing on Node 8, add 14 and 16 --- run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.sh b/run-tests.sh index 03fd970b5..e62cbd885 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -28,7 +28,7 @@ cd $ROOT git submodule update --init --recursive if [ ! -n "$node_versions" ] ; then - node_versions="8 10 12" + node_versions="10 12 14 16" fi set +ex From 144c41b366e86fed5cb333ab72014b0ad4ef2556 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 15 Oct 2021 09:48:42 -0700 Subject: [PATCH 095/694] proto-loader: Make serializers reject arrays --- packages/proto-loader/package.json | 2 +- packages/proto-loader/src/index.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 9418df982..d31220186 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.5", + "version": "0.6.6", "author": "Google Inc.", "contributors": [ { diff --git a/packages/proto-loader/src/index.ts b/packages/proto-loader/src/index.ts index 98ca97b51..24a249400 100644 --- a/packages/proto-loader/src/index.ts +++ b/packages/proto-loader/src/index.ts @@ -212,6 +212,9 @@ function createDeserializer( function createSerializer(cls: Protobuf.Type): Serialize { return function serialize(arg: object): Buffer { + if (Array.isArray(arg)) { + throw new Error(`Failed to serialize message: expected object with ${cls.name} structure, got array instead`); + } const message = cls.fromObject(arg); return cls.encode(message).finish() as Buffer; }; From 5139b107e0f4318b3ee39b2af1daf5446eb05517 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Oct 2021 09:29:43 -0700 Subject: [PATCH 096/694] grpc-js: Limit the number of retained channelz trace events --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/channelz.ts | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 9705383ea..4acf948df 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.4.1", + "version": "1.4.2", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 7efe54780..a1c85eb47 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -113,6 +113,14 @@ interface TraceEvent { childSubchannel?: SubchannelRef; } +/** + * The loose upper bound on the number of events that should be retained in a + * trace. This may be exceeded by up to a factor of 2. Arbitrarily chosen as a + * number that should be large enough to contain the recent relevant + * information, but small enough to not use excessive memory. + */ +const TARGET_RETAINED_TRACES = 32; + export class ChannelzTrace { events: TraceEvent[] = []; creationTimestamp: Date; @@ -131,6 +139,10 @@ export class ChannelzTrace { childChannel: child?.kind === 'channel' ? child : undefined, childSubchannel: child?.kind === 'subchannel' ? child : undefined }); + // Whenever the trace array gets too large, discard the first half + if (this.events.length >= TARGET_RETAINED_TRACES * 2) { + this.events = this.events.slice(TARGET_RETAINED_TRACES); + } this.eventsLogged += 1; } @@ -380,20 +392,6 @@ export function unregisterChannelzRef(ref: ChannelRef | SubchannelRef | ServerRe } } -export interface ChannelzClientView { - updateState(connectivityState: ConnectivityState): void; - addTrace(severity: TraceSeverity, description: string, child?: ChannelRef | SubchannelRef): void; - addCallStarted(): void; - addCallSucceeded(): void; - addCallFailed(): void; - addChild(child: ChannelRef | SubchannelRef): void; - removeChild(child: ChannelRef | SubchannelRef): void; -} - -export interface ChannelzSubchannelView extends ChannelzClientView { - getRef(): SubchannelRef; -} - /** * Converts an IPv4 or IPv6 address from string representation to binary * representation From 891a918c85d717c814d98ee0ce01363970b99007 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Oct 2021 13:31:08 -0700 Subject: [PATCH 097/694] grpc-js: Allow users to disable channelz --- packages/grpc-js/src/channel-options.ts | 2 + packages/grpc-js/src/channel.ts | 69 +++++++++++++----- packages/grpc-js/src/server.ts | 41 ++++++++--- packages/grpc-js/src/subchannel.ts | 94 ++++++++++++++++--------- 4 files changed, 147 insertions(+), 59 deletions(-) diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index 604fd8685..4e2ee6132 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -36,6 +36,7 @@ export interface ChannelOptions { 'grpc.enable_http_proxy'?: number; 'grpc.http_connect_target'?: string; 'grpc.http_connect_creds'?: string; + 'grpc.enable_channelz'?: number; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; @@ -61,6 +62,7 @@ export const recognizedOptions = { 'grpc.max_send_message_length': true, 'grpc.max_receive_message_length': true, 'grpc.enable_http_proxy': true, + 'grpc.enable_channelz': true, 'grpc-node.max_session_memory': true, }; diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index f998ecef4..e442e6e64 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -172,6 +172,7 @@ export class ChannelImplementation implements Channel { private configSelector: ConfigSelector | null = null; // Channelz info + private readonly channelzEnabled: boolean = true; private originalTarget: string; private channelzRef: ChannelRef; private channelzTrace: ChannelzTrace; @@ -213,9 +214,22 @@ export class ChannelImplementation implements Channel { this.callRefTimer = setInterval(() => {}, MAX_TIMEOUT_TIME); this.callRefTimer.unref?.(); - this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); + if (this.options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + this.channelzTrace = new ChannelzTrace(); - this.channelzTrace.addTrace('CT_INFO', 'Channel created'); + if (this.channelzEnabled) { + this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Channel created'); + } else { + // Dummy channelz ref that will never be used + this.channelzRef = { + kind: 'channel', + id: -1, + name: '' + }; + } if (this.options['grpc.default_authority']) { this.defaultAuthority = this.options['grpc.default_authority'] as string; @@ -242,7 +256,9 @@ export class ChannelImplementation implements Channel { Object.assign({}, this.options, subchannelArgs), this.credentials ); - this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); + } return subchannel; }, updateState: (connectivityState: ConnectivityState, picker: Picker) => { @@ -262,10 +278,14 @@ export class ChannelImplementation implements Channel { ); }, addChannelzChild: (child: ChannelRef | SubchannelRef) => { - this.childrenTracker.refChild(child); + if (this.channelzEnabled) { + this.childrenTracker.refChild(child); + } }, removeChannelzChild: (child: ChannelRef | SubchannelRef) => { - this.childrenTracker.unrefChild(child); + if (this.channelzEnabled) { + this.childrenTracker.unrefChild(child); + } } }; this.resolvingLoadBalancer = new ResolvingLoadBalancer( @@ -273,7 +293,9 @@ export class ChannelImplementation implements Channel { channelControlHelper, options, (configSelector) => { - this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); + } this.configSelector = configSelector; /* We process the queue asynchronously to ensure that the corresponding * load balancer update has completed. */ @@ -288,7 +310,9 @@ export class ChannelImplementation implements Channel { }); }, (status) => { - this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); + } if (this.configSelectionQueue.length > 0) { this.trace('Name resolution failed with calls queued for config selection'); } @@ -553,7 +577,9 @@ export class ChannelImplementation implements Channel { ' -> ' + ConnectivityState[newState] ); - this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + } this.connectivityState = newState; const watchersCopy = this.connectivityStateWatchers.slice(); for (const watcherObject of watchersCopy) { @@ -638,7 +664,9 @@ export class ChannelImplementation implements Channel { this.resolvingLoadBalancer.destroy(); this.updateState(ConnectivityState.SHUTDOWN); clearInterval(this.callRefTimer); - unregisterChannelzRef(this.channelzRef); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } this.subchannelPool.unrefUnusedSubchannels(); } @@ -690,6 +718,11 @@ export class ChannelImplementation implements Channel { this.connectivityStateWatchers.push(watcherObject); } + /** + * Get the channelz reference object for this channel. The returned value is + * garbage if channelz is disabled for this channel. + * @returns + */ getChannelzRef() { return this.channelzRef; } @@ -735,14 +768,16 @@ export class ChannelImplementation implements Channel { this.credentials._getCallCredentials(), callNumber ); - this.callTracker.addCallStarted(); - stream.addStatusWatcher(status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + stream.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + } return stream; } } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index aed04adec..8bfc2a5bc 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -149,6 +149,7 @@ export class Server { private options: ChannelOptions; // Channelz Info + private readonly channelzEnabled: boolean = true; private channelzRef: ServerRef; private channelzTrace = new ChannelzTrace(); private callTracker = new ChannelzCallTracker(); @@ -157,9 +158,20 @@ export class Server { constructor(options?: ChannelOptions) { this.options = options ?? {}; - this.channelzRef = registerChannelzServer(() => this.getChannelzInfo()); - this.channelzTrace.addTrace('CT_INFO', 'Server created'); - this.trace('Server constructed'); + if (this.options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + if (this.channelzEnabled) { + this.channelzRef = registerChannelzServer(() => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Server created'); + this.trace('Server constructed'); + } else { + // Dummy channelz ref that will never be used + this.channelzRef = { + kind: 'server', + id: -1 + }; + } } private getChannelzInfo(): ServerInfo { @@ -638,7 +650,9 @@ export class Server { if (this.started === true) { throw new Error('server is already started'); } - this.channelzTrace.addTrace('CT_INFO', 'Starting'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Starting'); + } this.started = true; } @@ -686,6 +700,11 @@ export class Server { throw new Error('Not yet implemented'); } + /** + * Get the channelz reference object for this server. The returned value is + * garbage if channelz is disabled for this server. + * @returns + */ getChannelzRef() { return this.channelzRef; } @@ -841,12 +860,16 @@ export class Server { this.sessions.set(session, channelzSessionInfo); const clientAddress = session.socket.remoteAddress; - this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); - this.sessionChildrenTracker.refChild(channelzRef); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); + this.sessionChildrenTracker.refChild(channelzRef); + } session.on('close', () => { - this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); - this.sessionChildrenTracker.unrefChild(channelzRef); - unregisterChannelzRef(channelzRef); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); + this.sessionChildrenTracker.unrefChild(channelzRef); + unregisterChannelzRef(channelzRef); + } this.sessions.delete(session); }); }); diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index a3b86b283..edc843a8a 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -157,6 +157,7 @@ export class Subchannel { private subchannelAddressString: string; // Channelz info + private readonly channelzEnabled: boolean = true; private channelzRef: SubchannelRef; private channelzTrace: ChannelzTrace; private callTracker = new ChannelzCallTracker(); @@ -226,9 +227,21 @@ export class Subchannel { }, backoffOptions); this.subchannelAddressString = subchannelAddressToString(subchannelAddress); - this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); + if (options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } this.channelzTrace = new ChannelzTrace(); - this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); + if (this.channelzEnabled) { + this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); + } else { + // Dummy channelz ref that will never be used + this.channelzRef = { + kind: 'subchannel', + id: -1, + name: '' + }; + } this.trace('Subchannel constructed with options ' + JSON.stringify(options, undefined, 2)); } @@ -286,6 +299,9 @@ export class Subchannel { } private resetChannelzSocketInfo() { + if (!this.channelzEnabled) { + return; + } if (this.channelzSocketRef) { unregisterChannelzRef(this.channelzSocketRef); this.childrenTracker.unrefChild(this.channelzSocketRef); @@ -335,7 +351,9 @@ export class Subchannel { } private sendPing() { - this.keepalivesSent += 1; + if (this.channelzEnabled) { + this.keepalivesSent += 1; + } logging.trace( LogVerbosity.DEBUG, 'keepalive', @@ -462,8 +480,10 @@ export class Subchannel { connectionOptions ); this.session = session; - this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); - this.childrenTracker.refChild(this.channelzSocketRef); + if (this.channelzEnabled) { + this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); + this.childrenTracker.refChild(this.channelzSocketRef); + } session.unref(); /* For all of these events, check if the session at the time of the event * is the same one currently attached to this subchannel, to ensure that @@ -615,7 +635,9 @@ export class Subchannel { ' -> ' + ConnectivityState[newState] ); - this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + } const previousState = this.connectivityState; this.connectivityState = newState; switch (newState) { @@ -678,12 +700,16 @@ export class Subchannel { /* If no calls, channels, or subchannel pools have any more references to * this subchannel, we can be sure it will never be used again. */ if (this.callRefcount === 0 && this.refcount === 0) { - this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); + } this.transitionToState( [ConnectivityState.CONNECTING, ConnectivityState.READY], ConnectivityState.TRANSIENT_FAILURE ); - unregisterChannelzRef(this.channelzRef); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } } } @@ -805,34 +831,36 @@ export class Subchannel { ' with headers\n' + headersString ); - this.callTracker.addCallStarted(); - callStream.addStatusWatcher(status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); const streamSession = this.session; - this.streamTracker.addCallStarted(); - callStream.addStreamEndWatcher(success => { - if (streamSession === this.session) { - if (success) { - this.streamTracker.addCallSucceeded(); + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + callStream.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); } else { - this.streamTracker.addCallFailed(); + this.callTracker.addCallFailed(); } - } - }); - callStream.attachHttp2Stream(http2Stream, this, extraFilters, { - addMessageSent: () => { - this.messagesSent += 1; - this.lastMessageSentTimestamp = new Date(); - }, - addMessageReceived: () => { - this.messagesReceived += 1; - } - }); + }); + this.streamTracker.addCallStarted(); + callStream.addStreamEndWatcher(success => { + if (streamSession === this.session) { + if (success) { + this.streamTracker.addCallSucceeded(); + } else { + this.streamTracker.addCallFailed(); + } + } + }); + callStream.attachHttp2Stream(http2Stream, this, extraFilters, { + addMessageSent: () => { + this.messagesSent += 1; + this.lastMessageSentTimestamp = new Date(); + }, + addMessageReceived: () => { + this.messagesReceived += 1; + } + }); + } } /** From 9d92bf164fa2ce0445d5acedfcf04034db587a44 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Oct 2021 16:20:39 -0700 Subject: [PATCH 098/694] grpc-js-xds: Improve received resource list logging --- packages/grpc-js-xds/src/xds-stream-state/cds-state.ts | 2 +- packages/grpc-js-xds/src/xds-stream-state/eds-state.ts | 2 +- packages/grpc-js-xds/src/xds-stream-state/lds-state.ts | 2 +- packages/grpc-js-xds/src/xds-stream-state/rds-state.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 7720c5673..c3c73e9fb 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -158,7 +158,7 @@ export class CdsState implements XdsStreamState { watcher.onValidUpdate(message, isV2); } } - trace('Received CDS updates for cluster names ' + Array.from(allClusterNames)); + trace('Received CDS updates for cluster names [' + Array.from(allClusterNames) + ']'); this.handleMissingNames(allClusterNames); this.edsState.handleMissingNames(allEdsServiceNames); return null; diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index 3861f4d2a..f816ceea2 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -162,7 +162,7 @@ export class EdsState implements XdsStreamState { watcher.onValidUpdate(message, isV2); } } - trace('Received EDS updates for cluster names ' + Array.from(allClusterNames)); + trace('Received EDS updates for cluster names [' + Array.from(allClusterNames) + ']'); return null; } diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 10e71babf..7c9105be4 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -175,7 +175,7 @@ export class LdsState implements XdsStreamState { watcher.onValidUpdate(message, isV2); } } - trace('Received RDS response with route config names ' + Array.from(allTargetNames)); + trace('Received LDS response with listener names [' + Array.from(allTargetNames) + ']'); this.handleMissingNames(allTargetNames); this.rdsState.handleMissingNames(allRouteConfigNames); return null; diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index ec7abe55a..04d2bce0d 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -199,7 +199,7 @@ export class RdsState implements XdsStreamState { watcher.onValidUpdate(message, isV2); } } - trace('Received RDS response with route config names ' + Array.from(allRouteConfigNames)); + trace('Received RDS response with route config names [' + Array.from(allRouteConfigNames) + ']'); return null; } From 647654c7c17cd79dd1f06085d5b8d2c4e40b7e3a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 19 Oct 2021 08:33:25 -0700 Subject: [PATCH 099/694] grpc-js: Include other causes in ENHANCE_YOUR_CALM translation --- packages/grpc-js/src/call-stream.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 70a9ac6ee..f705f6caf 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -600,7 +600,7 @@ export class Http2CallStream implements Call { break; case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM: code = Status.RESOURCE_EXHAUSTED; - details = 'Bandwidth exhausted'; + details = 'Bandwidth exhausted or memory limit exceeded'; break; case http2.constants.NGHTTP2_INADEQUATE_SECURITY: code = Status.PERMISSION_DENIED; From 2a45b343d51ae8d3333914659dabdbb1d340334c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 19 Oct 2021 14:20:37 -0700 Subject: [PATCH 100/694] grpc-js-xds: Use valid resources when NACKing messages --- .../grpc-js-xds/src/xds-stream-state/cds-state.ts | 14 +++++++++----- .../grpc-js-xds/src/xds-stream-state/eds-state.ts | 14 +++++++++----- .../grpc-js-xds/src/xds-stream-state/lds-state.ts | 14 +++++++++----- .../grpc-js-xds/src/xds-stream-state/rds-state.ts | 14 +++++++++----- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 7720c5673..5dae09fb4 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -137,17 +137,21 @@ export class CdsState implements XdsStreamState { } handleResponses(responses: Cluster__Output[], isV2: boolean): string | null { + const validResponses: Cluster__Output[] = []; + let errorMessage: string | null = null; for (const message of responses) { - if (!this.validateResponse(message)) { + if (this.validateResponse(message)) { + validResponses.push(message); + } else { trace('CDS validation failed for message ' + JSON.stringify(message)); - return 'CDS Error: Cluster validation failed'; + errorMessage = 'CDS Error: Cluster validation failed'; } } - this.latestResponses = responses; + this.latestResponses = validResponses; this.latestIsV2 = isV2; const allEdsServiceNames: Set = new Set(); const allClusterNames: Set = new Set(); - for (const message of responses) { + for (const message of validResponses) { allClusterNames.add(message.name); const edsServiceName = message.eds_cluster_config?.service_name ?? ''; allEdsServiceNames.add( @@ -161,7 +165,7 @@ export class CdsState implements XdsStreamState { trace('Received CDS updates for cluster names ' + Array.from(allClusterNames)); this.handleMissingNames(allClusterNames); this.edsState.handleMissingNames(allEdsServiceNames); - return null; + return errorMessage; } reportStreamError(status: StatusObject): void { diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index 3861f4d2a..7d28ed5ff 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -146,16 +146,20 @@ export class EdsState implements XdsStreamState { } handleResponses(responses: ClusterLoadAssignment__Output[], isV2: boolean) { + const validResponses: ClusterLoadAssignment__Output[] = []; + let errorMessage: string | null = null; for (const message of responses) { - if (!this.validateResponse(message)) { + if (this.validateResponse(message)) { + validResponses.push(message); + } else { trace('EDS validation failed for message ' + JSON.stringify(message)); - return 'EDS Error: ClusterLoadAssignment validation failed'; + errorMessage = 'EDS Error: ClusterLoadAssignment validation failed'; } } - this.latestResponses = responses; + this.latestResponses = validResponses; this.latestIsV2 = isV2; const allClusterNames: Set = new Set(); - for (const message of responses) { + for (const message of validResponses) { allClusterNames.add(message.cluster_name); const watchers = this.watchers.get(message.cluster_name) ?? []; for (const watcher of watchers) { @@ -163,7 +167,7 @@ export class EdsState implements XdsStreamState { } } trace('Received EDS updates for cluster names ' + Array.from(allClusterNames)); - return null; + return errorMessage; } reportStreamError(status: StatusObject): void { diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 10e71babf..5706e3766 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -154,17 +154,21 @@ export class LdsState implements XdsStreamState { } handleResponses(responses: Listener__Output[], isV2: boolean): string | null { + const validResponses: Listener__Output[] = []; + let errorMessage: string | null = null; for (const message of responses) { - if (!this.validateResponse(message, isV2)) { + if (this.validateResponse(message, isV2)) { + validResponses.push(message); + } else { trace('LDS validation failed for message ' + JSON.stringify(message)); - return 'LDS Error: Route validation failed'; + errorMessage = 'LDS Error: Route validation failed'; } } - this.latestResponses = responses; + this.latestResponses = validResponses; this.latestIsV2 = isV2; const allTargetNames = new Set(); const allRouteConfigNames = new Set(); - for (const message of responses) { + for (const message of validResponses) { allTargetNames.add(message.name); const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener!.value); if (httpConnectionManager.rds) { @@ -178,7 +182,7 @@ export class LdsState implements XdsStreamState { trace('Received RDS response with route config names ' + Array.from(allTargetNames)); this.handleMissingNames(allTargetNames); this.rdsState.handleMissingNames(allRouteConfigNames); - return null; + return errorMessage; } reportStreamError(status: StatusObject): void { diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index ec7abe55a..5a3854323 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -183,16 +183,20 @@ export class RdsState implements XdsStreamState { } handleResponses(responses: RouteConfiguration__Output[], isV2: boolean): string | null { + const validResponses: RouteConfiguration__Output[] = []; + let errorMessage: string | null = null; for (const message of responses) { - if (!this.validateResponse(message, isV2)) { + if (this.validateResponse(message, isV2)) { + validResponses.push(message); + } else { trace('RDS validation failed for message ' + JSON.stringify(message)); - return 'RDS Error: Route validation failed'; + errorMessage = 'RDS Error: Route validation failed'; } } - this.latestResponses = responses; + this.latestResponses = validResponses; this.latestIsV2 = isV2; const allRouteConfigNames = new Set(); - for (const message of responses) { + for (const message of validResponses) { allRouteConfigNames.add(message.name); const watchers = this.watchers.get(message.name) ?? []; for (const watcher of watchers) { @@ -200,7 +204,7 @@ export class RdsState implements XdsStreamState { } } trace('Received RDS response with route config names ' + Array.from(allRouteConfigNames)); - return null; + return errorMessage; } reportStreamError(status: StatusObject): void { From 25e21d1fba1b7f6aa41522309cb1dc773324d5ed Mon Sep 17 00:00:00 2001 From: howyi Date: Sun, 24 Oct 2021 21:14:16 +0900 Subject: [PATCH 101/694] feat(client): export ServiceClientConstructor,ProtobufTypeDefinition type --- packages/grpc-js/src/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 4b7967be9..f3d018953 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -42,7 +42,9 @@ import { loadPackageDefinition, makeClientConstructor, MethodDefinition, + ProtobufTypeDefinition, Serialize, + ServiceClientConstructor, ServiceDefinition, } from './make-client'; import { Metadata, MetadataValue } from './metadata'; @@ -245,7 +247,11 @@ export { InterceptorConfigurationError, } from './client-interceptors'; -export { GrpcObject } from './make-client'; +export { + GrpcObject, + ServiceClientConstructor, + ProtobufTypeDefinition +} from './make-client'; export { ChannelOptions } from './channel-options'; From 7aa5b6200814afc3acf5739d8a758fbaf97dca8b Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 24 Oct 2021 21:46:23 -0700 Subject: [PATCH 102/694] grpc-js: Allow per-channel request compression from the client and decompression from the server --- packages/grpc-js/package.json | 5 +- packages/grpc-js/proto/test_service.proto | 42 +++ packages/grpc-js/src/channel-options.ts | 1 + packages/grpc-js/src/channel.ts | 3 +- packages/grpc-js/src/compression-filter.ts | 52 +++- packages/grpc-js/src/generated/Request.ts | 14 + packages/grpc-js/src/generated/Response.ts | 10 + packages/grpc-js/src/generated/TestService.ts | 55 ++++ .../grpc-js/src/generated/test_service.ts | 15 + packages/grpc-js/src/server-call.ts | 84 ++++- packages/grpc-js/src/server.ts | 39 ++- packages/grpc-js/test/test-server.ts | 286 ++++++++++++++++++ 12 files changed, 570 insertions(+), 36 deletions(-) create mode 100644 packages/grpc-js/proto/test_service.proto create mode 100644 packages/grpc-js/src/generated/Request.ts create mode 100644 packages/grpc-js/src/generated/Response.ts create mode 100644 packages/grpc-js/src/generated/TestService.ts create mode 100644 packages/grpc-js/src/generated/test_service.ts diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 9705383ea..f430c09d2 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -54,11 +54,12 @@ "fix": "gts fix src/*.ts", "pretest": "npm run generate-types && npm run compile", "posttest": "npm run check && madge -c ./build/src", - "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated/ --grpcLib ../index channelz.proto" + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated/ --grpcLib ../index channelz.proto test_service.proto" }, "dependencies": { "@grpc/proto-loader": "^0.6.4", - "@types/node": ">=12.12.47" + "@types/node": "16.10.0", + "@types/semver": "^7.3.9" }, "files": [ "src/**/*.ts", diff --git a/packages/grpc-js/proto/test_service.proto b/packages/grpc-js/proto/test_service.proto new file mode 100644 index 000000000..f99393d14 --- /dev/null +++ b/packages/grpc-js/proto/test_service.proto @@ -0,0 +1,42 @@ +/* + * 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. + * + */ + +syntax = "proto3"; + +message Request { + bool error = 1; + string message = 2; + int32 errorAfter = 3; +} + +message Response { + int32 count = 1; +} + +service TestService { + rpc Unary (Request) returns (Response) { + } + + rpc ClientStream (stream Request) returns (Response) { + } + + rpc ServerStream (Request) returns (stream Response) { + } + + rpc BidiStream (stream Request) returns (stream Response) { + } +} diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index 604fd8685..f0b37d7a4 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -36,6 +36,7 @@ export interface ChannelOptions { 'grpc.enable_http_proxy'?: number; 'grpc.http_connect_target'?: string; 'grpc.http_connect_creds'?: string; + 'grpc.default_compression_algorithm'?: number; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index f998ecef4..8a2f93a54 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -45,7 +45,6 @@ import { MaxMessageSizeFilterFactory } from './max-message-size-filter'; import { mapProxyName } from './http_proxy'; import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; -import { SurfaceCall } from './call'; import { Filter } from './filter'; import { ConnectivityState } from './connectivity-state'; @@ -309,7 +308,7 @@ export class ChannelImplementation implements Channel { new CallCredentialsFilterFactory(this), new DeadlineFilterFactory(this), new MaxMessageSizeFilterFactory(this.options), - new CompressionFilterFactory(this), + new CompressionFilterFactory(this, this.options), ]); this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2)); } diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 330eb675a..4ecdbb6a3 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -17,10 +17,27 @@ import * as zlib from 'zlib'; -import { Call, WriteFlags, WriteObject } from './call-stream'; +import { Call, WriteObject } from './call-stream'; import { Channel } from './channel'; import { BaseFilter, Filter, FilterFactory } from './filter'; import { Metadata, MetadataValue } from './metadata'; +import { ChannelOptions } from './channel-options'; + +const CompressionAlgorithms = { + '0': 'identity', + '1': 'deflate', + '2': 'gzip', + // Streaming compression is unimplemented + '3': 'stream-gzip' +} as const; + +const CompressionAlgorithKeys = new Set(Object.keys(CompressionAlgorithms)); + +const isCompressionAlgorithmKey = (key: string | undefined): key is keyof typeof CompressionAlgorithms => { + return typeof key === 'string' && CompressionAlgorithKeys.has(key); +} + +type CompressionAlgorithm = (typeof CompressionAlgorithms)[keyof typeof CompressionAlgorithms]; abstract class CompressionHandler { protected abstract compressMessage(message: Buffer): Promise; @@ -167,10 +184,29 @@ function getCompressionHandler(compressionName: string): CompressionHandler { export class CompressionFilter extends BaseFilter implements Filter { private sendCompression: CompressionHandler = new IdentityHandler(); private receiveCompression: CompressionHandler = new IdentityHandler(); + private defaultCompressionAlgorithm: CompressionAlgorithm | undefined; + + constructor(channelOptions: ChannelOptions) { + super(); + + const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']?.toString(); + if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { + this.defaultCompressionAlgorithm = CompressionAlgorithms[compressionAlgorithmKey]; + this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); + } + } + async sendMetadata(metadata: Promise): Promise { const headers: Metadata = await metadata; headers.set('grpc-accept-encoding', 'identity,deflate,gzip'); - headers.set('accept-encoding', 'identity'); + headers.set('accept-encoding', 'identity,deflate,gzip'); + + if (this.defaultCompressionAlgorithm && ['deflate', 'gzip'].includes(this.defaultCompressionAlgorithm)) { + headers.set('grpc-encoding', this.defaultCompressionAlgorithm); + } else { + headers.remove('grpc-encoding'); + } + return headers; } @@ -192,10 +228,10 @@ export class CompressionFilter extends BaseFilter implements Filter { * and the output is a framed and possibly compressed message. For this * reason, this filter should be at the bottom of the filter stack */ const resolvedMessage: WriteObject = await message; - const compress = - resolvedMessage.flags === undefined - ? false - : (resolvedMessage.flags & WriteFlags.NoCompress) === 0; + const compress = !(this.sendCompression instanceof IdentityHandler); + // resolvedMessage.flags === undefined + // ? false + // : (resolvedMessage.flags & WriteFlags.NoCompress) === 0; return { message: await this.sendCompression.writeMessage( resolvedMessage.message, @@ -216,8 +252,8 @@ export class CompressionFilter extends BaseFilter implements Filter { export class CompressionFilterFactory implements FilterFactory { - constructor(private readonly channel: Channel) {} + constructor(private readonly channel: Channel, private readonly options: ChannelOptions) {} createFilter(callStream: Call): CompressionFilter { - return new CompressionFilter(); + return new CompressionFilter(this.options); } } diff --git a/packages/grpc-js/src/generated/Request.ts b/packages/grpc-js/src/generated/Request.ts new file mode 100644 index 000000000..93898a701 --- /dev/null +++ b/packages/grpc-js/src/generated/Request.ts @@ -0,0 +1,14 @@ +// Original file: proto/test_service.proto + + +export interface Request { + 'error'?: (boolean); + 'message'?: (string); + 'errorAfter'?: (number); +} + +export interface Request__Output { + 'error': (boolean); + 'message': (string); + 'errorAfter': (number); +} diff --git a/packages/grpc-js/src/generated/Response.ts b/packages/grpc-js/src/generated/Response.ts new file mode 100644 index 000000000..980bfdd18 --- /dev/null +++ b/packages/grpc-js/src/generated/Response.ts @@ -0,0 +1,10 @@ +// Original file: proto/test_service.proto + + +export interface Response { + 'count'?: (number); +} + +export interface Response__Output { + 'count': (number); +} diff --git a/packages/grpc-js/src/generated/TestService.ts b/packages/grpc-js/src/generated/TestService.ts new file mode 100644 index 000000000..dd1d22ab2 --- /dev/null +++ b/packages/grpc-js/src/generated/TestService.ts @@ -0,0 +1,55 @@ +// Original file: proto/test_service.proto + +import type * as grpc from './../index' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { Request as _Request, Request__Output as _Request__Output } from './Request'; +import type { Response as _Response, Response__Output as _Response__Output } from './Response'; + +export interface TestServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; + + ClientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + ClientStream(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + ClientStream(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + ClientStream(callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + clientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + clientStream(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + clientStream(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + clientStream(callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + + ServerStream(argument: _Request, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + ServerStream(argument: _Request, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + serverStream(argument: _Request, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + serverStream(argument: _Request, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; + + Unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + Unary(argument: _Request, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + Unary(argument: _Request, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + Unary(argument: _Request, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + unary(argument: _Request, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + unary(argument: _Request, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + unary(argument: _Request, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + +} + +export interface TestServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_Request__Output, _Response>; + + ClientStream: grpc.handleClientStreamingCall<_Request__Output, _Response>; + + ServerStream: grpc.handleServerStreamingCall<_Request__Output, _Response>; + + Unary: grpc.handleUnaryCall<_Request__Output, _Response>; + +} + +export interface TestServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> + ClientStream: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> + ServerStream: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> + Unary: MethodDefinition<_Request, _Response, _Request__Output, _Response__Output> +} diff --git a/packages/grpc-js/src/generated/test_service.ts b/packages/grpc-js/src/generated/test_service.ts new file mode 100644 index 000000000..2beb88680 --- /dev/null +++ b/packages/grpc-js/src/generated/test_service.ts @@ -0,0 +1,15 @@ +import type * as grpc from '../index'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { TestServiceClient as _TestServiceClient, TestServiceDefinition as _TestServiceDefinition } from './TestService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + Request: MessageTypeDefinition + Response: MessageTypeDefinition + TestService: SubtypeConstructor & { service: _TestServiceDefinition } +} + diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index be4429b0f..8ea488a4d 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -18,6 +18,7 @@ import { EventEmitter } from 'events'; import * as http2 from 'http2'; import { Duplex, Readable, Writable } from 'stream'; +import * as zlib from 'zlib'; import { Deadline, StatusObject } from './call-stream'; import { @@ -32,6 +33,7 @@ import { StreamDecoder } from './stream-decoder'; import { ObjectReadable, ObjectWritable } from './object-stream'; import { ChannelOptions } from './channel-options'; import * as logging from './logging'; +import { MetadataValue } from '.'; const TRACER_NAME = 'server_call'; @@ -60,7 +62,7 @@ const deadlineUnitsToMs: DeadlineUnitIndexSignature = { const defaultResponseHeaders = { // TODO(cjihrig): Remove these encoding headers from the default response // once compression is integrated. - [GRPC_ACCEPT_ENCODING_HEADER]: 'identity', + [GRPC_ACCEPT_ENCODING_HEADER]: 'identity,deflate,gzip', [GRPC_ENCODING_HEADER]: 'identity', [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', @@ -136,12 +138,13 @@ export class ServerReadableStreamImpl constructor( private call: Http2ServerCallStream, public metadata: Metadata, - public deserialize: Deserialize + public deserialize: Deserialize, + encoding?: MetadataValue ) { super({ objectMode: true }); this.cancelled = false; this.call.setupSurfaceCall(this); - this.call.setupReadable(this); + this.call.setupReadable(this, encoding); } _read(size: number) { @@ -250,13 +253,14 @@ export class ServerDuplexStreamImpl private call: Http2ServerCallStream, public metadata: Metadata, public serialize: Serialize, - public deserialize: Deserialize + public deserialize: Deserialize, + encoding?: MetadataValue ) { super({ objectMode: true }); this.cancelled = false; this.trailingMetadata = new Metadata(); this.call.setupSurfaceCall(this); - this.call.setupReadable(this); + this.call.setupReadable(this, encoding); this.on('error', (err) => { this.call.sendError(err); @@ -439,6 +443,47 @@ export class Http2ServerCallStream< return this.cancelled; } + private getDecompressedMessage(message: Buffer, encoding?: MetadataValue) { + switch (encoding) { + case 'deflate': { + return new Promise((resolve, reject) => { + zlib.inflate(message.slice(5), (err, output) => { + if (err) { + this.sendError({ + code: Status.INTERNAL, + details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, + }); + resolve(); + } else { + const joined = Buffer.concat([message.slice(0, 5), output]); + resolve(joined); + } + }); + }); + } + + case 'gzip': { + return new Promise((resolve, reject) => { + zlib.unzip(message.slice(5), (err, output) => { + if (err) { + this.sendError({ + code: Status.INTERNAL, + details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, + }); + resolve(); + } else { + const joined = Buffer.concat([message.slice(0, 5), output]); + resolve(joined); + } + }); + }); + } + + default: + return Promise.resolve(message); + } + } + sendMetadata(customMetadata?: Metadata) { if (this.checkCancelled()) { return; @@ -469,7 +514,7 @@ export class Http2ServerCallStream< const err = new Error('Invalid deadline') as ServerErrorResponse; err.code = Status.OUT_OF_RANGE; this.sendError(err); - return; + return metadata; } const timeout = (+match[1] * deadlineUnitsToMs[match[2]]) | 0; @@ -484,13 +529,12 @@ export class Http2ServerCallStream< metadata.remove(http2.constants.HTTP2_HEADER_ACCEPT_ENCODING); metadata.remove(http2.constants.HTTP2_HEADER_TE); metadata.remove(http2.constants.HTTP2_HEADER_CONTENT_TYPE); - metadata.remove('grpc-encoding'); metadata.remove('grpc-accept-encoding'); return metadata; } - receiveUnaryMessage(): Promise { + receiveUnaryMessage(encoding?: MetadataValue): Promise { return new Promise((resolve, reject) => { const stream = this.stream; const chunks: Buffer[] = []; @@ -516,7 +560,17 @@ export class Http2ServerCallStream< } this.emit('receiveMessage'); - resolve(this.deserializeMessage(requestBytes)); + + const decompressedMessage = await this.getDecompressedMessage(requestBytes, encoding); + + // Encountered an error with decompression; it'll already have been propogated back + // Just return early + if (!decompressedMessage) { + resolve(); + } + else { + resolve(this.deserializeMessage(decompressedMessage)); + } } catch (err) { err.code = Status.INTERNAL; this.sendError(err); @@ -673,7 +727,8 @@ export class Http2ServerCallStream< setupReadable( readable: | ServerReadableStream - | ServerDuplexStream + | ServerDuplexStream, + encoding?: MetadataValue ) { const decoder = new StreamDecoder(); @@ -692,7 +747,14 @@ export class Http2ServerCallStream< return; } this.emit('receiveMessage'); - this.pushOrBufferMessage(readable, message); + + const decompressedMessage = await this.getDecompressedMessage(message, encoding); + + // Encountered an error with decompression; it'll already have been propogated back + // Just return early + if (!decompressedMessage) return; + + this.pushOrBufferMessage(readable, decompressedMessage); } }); diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index aed04adec..552f31b02 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -61,6 +61,7 @@ import { import { parseUri } from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; +import { MetadataValue } from '.'; const TRACER_NAME = 'server'; @@ -777,30 +778,36 @@ export class Server { channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); }); } - const metadata: Metadata = call.receiveMetadata(headers) as Metadata; + const metadata = call.receiveMetadata(headers); + const encoding: MetadataValue | undefined = metadata.get('grpc-encoding')[0]; + metadata.remove('grpc-encoding'); + switch (handler.type) { case 'unary': - handleUnary(call, handler as UntypedUnaryHandler, metadata); + handleUnary(call, handler as UntypedUnaryHandler, metadata, encoding); break; case 'clientStream': handleClientStreaming( call, handler as UntypedClientStreamingHandler, - metadata + metadata, + encoding ); break; case 'serverStream': handleServerStreaming( call, handler as UntypedServerStreamingHandler, - metadata + metadata, + encoding ); break; case 'bidi': handleBidiStreaming( call, handler as UntypedBidiStreamingHandler, - metadata + metadata, + encoding ); break; default: @@ -856,9 +863,10 @@ export class Server { async function handleUnary( call: Http2ServerCallStream, handler: UnaryHandler, - metadata: Metadata + metadata: Metadata, + encoding?: MetadataValue ): Promise { - const request = await call.receiveUnaryMessage(); + const request = await call.receiveUnaryMessage(encoding); if (request === undefined || call.cancelled) { return; @@ -886,12 +894,14 @@ async function handleUnary( function handleClientStreaming( call: Http2ServerCallStream, handler: ClientStreamingHandler, - metadata: Metadata + metadata: Metadata, + encoding?: MetadataValue ): void { const stream = new ServerReadableStreamImpl( call, metadata, - handler.deserialize + handler.deserialize, + encoding ); function respond( @@ -915,9 +925,10 @@ function handleClientStreaming( async function handleServerStreaming( call: Http2ServerCallStream, handler: ServerStreamingHandler, - metadata: Metadata + metadata: Metadata, + encoding?: MetadataValue ): Promise { - const request = await call.receiveUnaryMessage(); + const request = await call.receiveUnaryMessage(encoding); if (request === undefined || call.cancelled) { return; @@ -936,13 +947,15 @@ async function handleServerStreaming( function handleBidiStreaming( call: Http2ServerCallStream, handler: BidiStreamingHandler, - metadata: Metadata + metadata: Metadata, + encoding?: MetadataValue ): void { const stream = new ServerDuplexStreamImpl( call, metadata, handler.serialize, - handler.deserialize + handler.deserialize, + encoding ); if (call.cancelled) { diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 8b2815745..676a2fda2 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -21,6 +21,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as http2 from 'http2'; import * as path from 'path'; +import * as protoLoader from '@grpc/proto-loader'; import * as grpc from '../src'; import { Server, ServerCredentials } from '../src'; @@ -29,6 +30,36 @@ import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; import { sendUnaryData, ServerUnaryCall } from '../src/server-call'; import { loadProtoFile } from './common'; +import { TestServiceClient, TestServiceHandlers } from '../src/generated/TestService'; +import { ProtoGrpcType as ChannelzGrpcType } from '../src/generated/channelz'; +import { ProtoGrpcType as TestServiceGrpcType } from '../src/generated/test_service'; +import { Request__Output } from '../src/generated/Request'; + +const loadedChannelzProto = protoLoader.loadSync('channelz.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + `${__dirname}/../../proto` + ] +}); + +const channelzGrpcObject = grpc.loadPackageDefinition(loadedChannelzProto) as unknown as ChannelzGrpcType; + +const loadedTestServiceProto = protoLoader.loadSync('test_service.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + `${__dirname}/../../proto` + ] +}); + +const testServiceGrpcObject = grpc.loadPackageDefinition(loadedTestServiceProto) as unknown as TestServiceGrpcType; const ca = fs.readFileSync(path.join(__dirname, 'fixtures', 'ca.pem')); const key = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.key')); @@ -604,3 +635,258 @@ describe('Generic client and server', () => { }); }); }); + +describe('Compressed requests', () => { + const testServiceHandlers: TestServiceHandlers = { + Unary(call, callback) { + const { metadata, request } = call; + console.log({ metadata, request }); + + callback(null, { count: 500000 }); + }, + + ClientStream(call, callback) { + let timesCalled = 0; + + call.on('data', () => { + timesCalled += 1; + console.log('clientStream received another call', { timesCalled }); + }); + + call.on('end', () => { + console.log('Returning from clientStream: ', { timesCalled }); + callback(null, { count: timesCalled }); + }); + }, + + ServerStream(call) { + const { metadata, request } = call; + console.log({ metadata, request }); + + for (let i = 0; i < 5; i++) { + call.write({ count: request.message.length }); + } + + call.end(); + }, + + BidiStream(call) { + call.on('data', (data: Request__Output) => { + console.log('Bidi stream data received, writing response', { data }); + call.write({ count: data.message.length }); + }); + + call.on('end', () => { + console.log('Server received end event for bidi stream, ending server-side call'); + call.end(); + }); + } + }; + + describe('Test service client and server with deflate', () => { + let client: TestServiceClient; + let server: Server; + let assignedPort: number; + + before(done => { + server = new Server(); + server.addService( + testServiceGrpcObject.TestService.service, + testServiceHandlers + ); + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + server.start(); + assignedPort = port; + client = new testServiceGrpcObject.TestService( + `localhost:${assignedPort}`, + grpc.credentials.createInsecure(), + { + 'grpc.default_compression_algorithm': 1 + } + ); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('Should compress and decompress when performing unary call', done => { + client.unary({ message: 'foo' }, (err, response) => { + assert.ifError(err); + done(); + }); + }); + + it('Should compress and decompress when performing client stream', done => { + const clientStream = client.clientStream((err, res) => { + assert.ifError(err); + assert.equal(res?.count, 3); + done(); + }); + + clientStream.write({ message: 'foo' }, () => { + clientStream.write({ message: 'bar' }, () => { + clientStream.write({ message: 'baz' }, () => { + setTimeout(() => clientStream.end(), 10); + }); + }); + }); + }); + + it('Should compress and decompress when performing server stream', done => { + const serverStream = client.serverStream({ message: 'foobar' }); + let timesResponded = 0; + + serverStream.on('data', () => { + timesResponded += 1; + }); + + serverStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + serverStream.on('end', () => { + assert.equal(timesResponded, 5); + done(); + }); + }); + + it('Should compress and decompress when performing bidi stream', done => { + const bidiStream = client.bidiStream(); + let timesRequested = 0; + let timesResponded = 0; + + bidiStream.on('data', () => { + timesResponded += 1; + }); + + bidiStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + bidiStream.on('end', () => { + console.log('Client received end event for bidi stream', { timesResponded, timesRequested }); + assert.equal(timesResponded, timesRequested); + done(); + }); + + bidiStream.write({ message: 'foo' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'bar' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'baz' }, () => { + timesRequested += 1; + setTimeout(() => bidiStream.end(), 10); + }) + }) + }); + }); + + it('Should compress and decompress with gzip', done => { + client = new testServiceGrpcObject.TestService( + `localhost:${assignedPort}`, + grpc.credentials.createInsecure(), + { + 'grpc.default_compression_algorithm': 2 + } + ); + + client.unary({ message: 'foo' }, (err, response) => { + assert.ifError(err); + done(); + }); + }); + + it('Should compress and decompress when performing client stream', done => { + const clientStream = client.clientStream((err, res) => { + assert.ifError(err); + assert.equal(res?.count, 3); + done(); + }); + + clientStream.write({ message: 'foo' }, () => { + clientStream.write({ message: 'bar' }, () => { + clientStream.write({ message: 'baz' }, () => { + setTimeout(() => clientStream.end(), 10); + }); + }); + }); + }); + + it('Should compress and decompress when performing server stream', done => { + const serverStream = client.serverStream({ message: 'foobar' }); + let timesResponded = 0; + + serverStream.on('data', () => { + timesResponded += 1; + }); + + serverStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + serverStream.on('end', () => { + assert.equal(timesResponded, 5); + done(); + }); + }); + + it('Should compress and decompress when performing bidi stream', done => { + const bidiStream = client.bidiStream(); + let timesRequested = 0; + let timesResponded = 0; + + bidiStream.on('data', () => { + timesResponded += 1; + }); + + bidiStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + bidiStream.on('end', () => { + console.log('Client received end event for bidi stream', { timesResponded, timesRequested }); + assert.equal(timesResponded, timesRequested); + done(); + }); + + bidiStream.write({ message: 'foo' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'bar' }, () => { + timesRequested += 1; + bidiStream.write({ message: 'baz' }, () => { + timesRequested += 1; + setTimeout(() => bidiStream.end(), 10); + }) + }) + }); + }); + + it('Should fail when attempting to use an unsupported compression method', done => { + client = new testServiceGrpcObject.TestService( + `localhost:${assignedPort}`, + grpc.credentials.createInsecure(), + { + 'grpc.default_compression_algorithm': 3 + } + ); + + client.unary({ message: 'foo' }, (err, response) => { + assert.ok(err); + done(); + }); + }); + }); +}); From cec7e64a2be1ba7c2a6bd19d7d234fd0bcff5c29 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 24 Oct 2021 22:21:24 -0700 Subject: [PATCH 103/694] cleanup test file --- packages/grpc-js/test/test-server.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 676a2fda2..f6a68a02a 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -31,23 +31,9 @@ import { sendUnaryData, ServerUnaryCall } from '../src/server-call'; import { loadProtoFile } from './common'; import { TestServiceClient, TestServiceHandlers } from '../src/generated/TestService'; -import { ProtoGrpcType as ChannelzGrpcType } from '../src/generated/channelz'; import { ProtoGrpcType as TestServiceGrpcType } from '../src/generated/test_service'; import { Request__Output } from '../src/generated/Request'; -const loadedChannelzProto = protoLoader.loadSync('channelz.proto', { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - includeDirs: [ - `${__dirname}/../../proto` - ] -}); - -const channelzGrpcObject = grpc.loadPackageDefinition(loadedChannelzProto) as unknown as ChannelzGrpcType; - const loadedTestServiceProto = protoLoader.loadSync('test_service.proto', { keepCase: true, longs: String, @@ -639,9 +625,6 @@ describe('Generic client and server', () => { describe('Compressed requests', () => { const testServiceHandlers: TestServiceHandlers = { Unary(call, callback) { - const { metadata, request } = call; - console.log({ metadata, request }); - callback(null, { count: 500000 }); }, @@ -650,18 +633,15 @@ describe('Compressed requests', () => { call.on('data', () => { timesCalled += 1; - console.log('clientStream received another call', { timesCalled }); }); call.on('end', () => { - console.log('Returning from clientStream: ', { timesCalled }); callback(null, { count: timesCalled }); }); }, ServerStream(call) { const { metadata, request } = call; - console.log({ metadata, request }); for (let i = 0; i < 5; i++) { call.write({ count: request.message.length }); @@ -672,12 +652,10 @@ describe('Compressed requests', () => { BidiStream(call) { call.on('data', (data: Request__Output) => { - console.log('Bidi stream data received, writing response', { data }); call.write({ count: data.message.length }); }); call.on('end', () => { - console.log('Server received end event for bidi stream, ending server-side call'); call.end(); }); } @@ -775,7 +753,6 @@ describe('Compressed requests', () => { }); bidiStream.on('end', () => { - console.log('Client received end event for bidi stream', { timesResponded, timesRequested }); assert.equal(timesResponded, timesRequested); done(); }); @@ -857,7 +834,6 @@ describe('Compressed requests', () => { }); bidiStream.on('end', () => { - console.log('Client received end event for bidi stream', { timesResponded, timesRequested }); assert.equal(timesResponded, timesRequested); done(); }); From 8dbeeb1d18cb254bf055c8ebbc966657fd94ed4f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 25 Oct 2021 13:29:06 -0700 Subject: [PATCH 104/694] grpc-js: Handle undefined socket.localAddress --- packages/grpc-js/src/channelz.ts | 4 ++-- packages/grpc-js/src/server.ts | 2 +- packages/grpc-js/src/subchannel.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 7efe54780..56a015642 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -286,7 +286,7 @@ export interface TlsInfo { } export interface SocketInfo { - localAddress: SubchannelAddress; + localAddress: SubchannelAddress | null; remoteAddress: SubchannelAddress | null; security: TlsInfo | null; remoteName: string | null; @@ -631,7 +631,7 @@ function GetSocket(call: ServerUnaryCall Date: Mon, 25 Oct 2021 18:46:14 -0700 Subject: [PATCH 105/694] revert mistaken accept-encoding client header change --- packages/grpc-js/src/compression-filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 4ecdbb6a3..4b06fafe6 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -199,7 +199,7 @@ export class CompressionFilter extends BaseFilter implements Filter { async sendMetadata(metadata: Promise): Promise { const headers: Metadata = await metadata; headers.set('grpc-accept-encoding', 'identity,deflate,gzip'); - headers.set('accept-encoding', 'identity,deflate,gzip'); + headers.set('accept-encoding', 'identity'); if (this.defaultCompressionAlgorithm && ['deflate', 'gzip'].includes(this.defaultCompressionAlgorithm)) { headers.set('grpc-encoding', this.defaultCompressionAlgorithm); From 16f13560506235374775f4fac3005cfdfe9c4559 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 25 Oct 2021 18:54:16 -0700 Subject: [PATCH 106/694] remove unused compression algorithm key value --- packages/grpc-js/src/compression-filter.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 4b06fafe6..6c613612d 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -26,9 +26,7 @@ import { ChannelOptions } from './channel-options'; const CompressionAlgorithms = { '0': 'identity', '1': 'deflate', - '2': 'gzip', - // Streaming compression is unimplemented - '3': 'stream-gzip' + '2': 'gzip' } as const; const CompressionAlgorithKeys = new Set(Object.keys(CompressionAlgorithms)); From 9d70f39b7e53bb10a578ffd5846e14dd788cc63b Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 25 Oct 2021 19:00:59 -0700 Subject: [PATCH 107/694] remove copied test proto file and modify npm command --- packages/grpc-js/package.json | 2 +- packages/grpc-js/proto/test_service.proto | 42 ------------------- packages/grpc-js/src/generated/Request.ts | 2 +- packages/grpc-js/src/generated/Response.ts | 2 +- packages/grpc-js/src/generated/TestService.ts | 2 +- 5 files changed, 4 insertions(+), 46 deletions(-) delete mode 100644 packages/grpc-js/proto/test_service.proto diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index f430c09d2..76a0902be 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -54,7 +54,7 @@ "fix": "gts fix src/*.ts", "pretest": "npm run generate-types && npm run compile", "posttest": "npm run check && madge -c ./build/src", - "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated/ --grpcLib ../index channelz.proto test_service.proto" + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ --include-dirs test/fixtures/ -O src/generated/ --grpcLib ../index channelz.proto test_service.proto" }, "dependencies": { "@grpc/proto-loader": "^0.6.4", diff --git a/packages/grpc-js/proto/test_service.proto b/packages/grpc-js/proto/test_service.proto deleted file mode 100644 index f99393d14..000000000 --- a/packages/grpc-js/proto/test_service.proto +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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. - * - */ - -syntax = "proto3"; - -message Request { - bool error = 1; - string message = 2; - int32 errorAfter = 3; -} - -message Response { - int32 count = 1; -} - -service TestService { - rpc Unary (Request) returns (Response) { - } - - rpc ClientStream (stream Request) returns (Response) { - } - - rpc ServerStream (Request) returns (stream Response) { - } - - rpc BidiStream (stream Request) returns (stream Response) { - } -} diff --git a/packages/grpc-js/src/generated/Request.ts b/packages/grpc-js/src/generated/Request.ts index 93898a701..d64ebb6ea 100644 --- a/packages/grpc-js/src/generated/Request.ts +++ b/packages/grpc-js/src/generated/Request.ts @@ -1,4 +1,4 @@ -// Original file: proto/test_service.proto +// Original file: test/fixtures/test_service.proto export interface Request { diff --git a/packages/grpc-js/src/generated/Response.ts b/packages/grpc-js/src/generated/Response.ts index 980bfdd18..217fc75e8 100644 --- a/packages/grpc-js/src/generated/Response.ts +++ b/packages/grpc-js/src/generated/Response.ts @@ -1,4 +1,4 @@ -// Original file: proto/test_service.proto +// Original file: test/fixtures/test_service.proto export interface Response { diff --git a/packages/grpc-js/src/generated/TestService.ts b/packages/grpc-js/src/generated/TestService.ts index dd1d22ab2..f890c520f 100644 --- a/packages/grpc-js/src/generated/TestService.ts +++ b/packages/grpc-js/src/generated/TestService.ts @@ -1,4 +1,4 @@ -// Original file: proto/test_service.proto +// Original file: test/fixtures/test_service.proto import type * as grpc from './../index' import type { MethodDefinition } from '@grpc/proto-loader' From 959503ec94aa08a6942780d7b7cd80847dbd4be6 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 25 Oct 2021 19:14:01 -0700 Subject: [PATCH 108/694] rewrite CompressionAlgorithms to use numeric keys and export it --- packages/grpc-js/src/compression-filter.ts | 14 +++++++------- packages/grpc-js/src/index.ts | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 6c613612d..eabb7a851 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -23,16 +23,16 @@ import { BaseFilter, Filter, FilterFactory } from './filter'; import { Metadata, MetadataValue } from './metadata'; import { ChannelOptions } from './channel-options'; -const CompressionAlgorithms = { - '0': 'identity', - '1': 'deflate', - '2': 'gzip' +export const CompressionAlgorithms = { + 0: 'identity', + 1: 'deflate', + 2: 'gzip' } as const; const CompressionAlgorithKeys = new Set(Object.keys(CompressionAlgorithms)); -const isCompressionAlgorithmKey = (key: string | undefined): key is keyof typeof CompressionAlgorithms => { - return typeof key === 'string' && CompressionAlgorithKeys.has(key); +const isCompressionAlgorithmKey = (key: number | undefined): key is keyof typeof CompressionAlgorithms => { + return typeof key === 'number' && CompressionAlgorithKeys.has(key.toString()); } type CompressionAlgorithm = (typeof CompressionAlgorithms)[keyof typeof CompressionAlgorithms]; @@ -187,7 +187,7 @@ export class CompressionFilter extends BaseFilter implements Filter { constructor(channelOptions: ChannelOptions) { super(); - const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']?.toString(); + const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']; if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { this.defaultCompressionAlgorithm = CompressionAlgorithms[compressionAlgorithmKey]; this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 4b7967be9..7e1ed7a50 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -25,6 +25,7 @@ import { import { CallCredentials, OAuth2Client } from './call-credentials'; import { Deadline, StatusObject } from './call-stream'; import { Channel, ChannelImplementation } from './channel'; +import { CompressionAlgorithms } from './compression-filter'; import { ConnectivityState } from './connectivity-state'; import { ChannelCredentials } from './channel-credentials'; import { @@ -124,6 +125,7 @@ export { Status as status, ConnectivityState as connectivityState, Propagate as propagate, + CompressionAlgorithms as compressionAlgorithms // TODO: Other constants as well }; From af010071fe2f4eeab2ebc80b99039942f935423f Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 25 Oct 2021 19:43:40 -0700 Subject: [PATCH 109/694] have client restore default sendCompression if server doesnt support compression, and fix test file generation --- packages/grpc-js/package.json | 5 ++-- packages/grpc-js/src/compression-filter.ts | 12 ++++++++++ .../{src => test}/generated/Request.ts | 0 .../{src => test}/generated/Response.ts | 0 .../{src => test}/generated/TestService.ts | 2 +- .../{src => test}/generated/test_service.ts | 2 +- packages/grpc-js/test/test-server.ts | 23 ++++--------------- 7 files changed, 21 insertions(+), 23 deletions(-) rename packages/grpc-js/{src => test}/generated/Request.ts (100%) rename packages/grpc-js/{src => test}/generated/Response.ts (100%) rename packages/grpc-js/{src => test}/generated/TestService.ts (98%) rename packages/grpc-js/{src => test}/generated/test_service.ts (92%) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 76a0902be..9131d470f 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -52,9 +52,10 @@ "test": "gulp test", "check": "gts check src/**/*.ts", "fix": "gts fix src/*.ts", - "pretest": "npm run generate-types && npm run compile", + "pretest": "npm run generate-types && npm run generate-test-types && npm run compile", "posttest": "npm run check && madge -c ./build/src", - "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ --include-dirs test/fixtures/ -O src/generated/ --grpcLib ../index channelz.proto test_service.proto" + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ --include-dirs test/fixtures/ -O src/generated/ --grpcLib ../index channelz.proto", + "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto" }, "dependencies": { "@grpc/proto-loader": "^0.6.4", diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index eabb7a851..b7e5e5d29 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -217,6 +217,18 @@ export class CompressionFilter extends BaseFilter implements Filter { } } metadata.remove('grpc-encoding'); + + /* Check to see if the compression we're using to send messages is supported by the server + * If not, reset the sendCompression filter and have it use the default IdentityHandler */ + const serverSupportedEncodingsHeader = metadata.get('grpc-accept-encoding')[0] as string | undefined; + if (serverSupportedEncodingsHeader) { + const serverSupportedEncodings = serverSupportedEncodingsHeader.split(','); + + if ((this.sendCompression instanceof DeflateHandler && !serverSupportedEncodings.includes('deflate')) + || (this.sendCompression instanceof GzipHandler && !serverSupportedEncodings.includes('gzip'))) { + this.sendCompression = new IdentityHandler(); + } + } metadata.remove('grpc-accept-encoding'); return metadata; } diff --git a/packages/grpc-js/src/generated/Request.ts b/packages/grpc-js/test/generated/Request.ts similarity index 100% rename from packages/grpc-js/src/generated/Request.ts rename to packages/grpc-js/test/generated/Request.ts diff --git a/packages/grpc-js/src/generated/Response.ts b/packages/grpc-js/test/generated/Response.ts similarity index 100% rename from packages/grpc-js/src/generated/Response.ts rename to packages/grpc-js/test/generated/Response.ts diff --git a/packages/grpc-js/src/generated/TestService.ts b/packages/grpc-js/test/generated/TestService.ts similarity index 98% rename from packages/grpc-js/src/generated/TestService.ts rename to packages/grpc-js/test/generated/TestService.ts index f890c520f..75bff33e4 100644 --- a/packages/grpc-js/src/generated/TestService.ts +++ b/packages/grpc-js/test/generated/TestService.ts @@ -1,6 +1,6 @@ // Original file: test/fixtures/test_service.proto -import type * as grpc from './../index' +import type * as grpc from './../../src/index' import type { MethodDefinition } from '@grpc/proto-loader' import type { Request as _Request, Request__Output as _Request__Output } from './Request'; import type { Response as _Response, Response__Output as _Response__Output } from './Response'; diff --git a/packages/grpc-js/src/generated/test_service.ts b/packages/grpc-js/test/generated/test_service.ts similarity index 92% rename from packages/grpc-js/src/generated/test_service.ts rename to packages/grpc-js/test/generated/test_service.ts index 2beb88680..364acddeb 100644 --- a/packages/grpc-js/src/generated/test_service.ts +++ b/packages/grpc-js/test/generated/test_service.ts @@ -1,4 +1,4 @@ -import type * as grpc from '../index'; +import type * as grpc from '../../src/index'; import type { MessageTypeDefinition } from '@grpc/proto-loader'; import type { TestServiceClient as _TestServiceClient, TestServiceDefinition as _TestServiceDefinition } from './TestService'; diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index f6a68a02a..c8171e796 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -30,11 +30,11 @@ import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; import { sendUnaryData, ServerUnaryCall } from '../src/server-call'; import { loadProtoFile } from './common'; -import { TestServiceClient, TestServiceHandlers } from '../src/generated/TestService'; -import { ProtoGrpcType as TestServiceGrpcType } from '../src/generated/test_service'; -import { Request__Output } from '../src/generated/Request'; +import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; +import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; +import { Request__Output } from './generated/Request'; -const loadedTestServiceProto = protoLoader.loadSync('test_service.proto', { +const loadedTestServiceProto = protoLoader.loadSync('test/fixtures/test_service.proto', { keepCase: true, longs: String, enums: String, @@ -849,20 +849,5 @@ describe('Compressed requests', () => { }) }); }); - - it('Should fail when attempting to use an unsupported compression method', done => { - client = new testServiceGrpcObject.TestService( - `localhost:${assignedPort}`, - grpc.credentials.createInsecure(), - { - 'grpc.default_compression_algorithm': 3 - } - ); - - client.unary({ message: 'foo' }, (err, response) => { - assert.ok(err); - done(); - }); - }); }); }); From d68d94a5f47582419390e2e8f08a10339a866026 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 25 Oct 2021 20:21:48 -0700 Subject: [PATCH 110/694] re-enable NoCompress flag behavior and check Compressed Flag byte on server --- packages/grpc-js/src/compression-filter.ts | 10 ++++----- packages/grpc-js/src/server-call.ts | 8 +++++-- packages/grpc-js/test/test-server.ts | 26 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index b7e5e5d29..ae507d333 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -17,7 +17,7 @@ import * as zlib from 'zlib'; -import { Call, WriteObject } from './call-stream'; +import { Call, WriteObject, WriteFlags } from './call-stream'; import { Channel } from './channel'; import { BaseFilter, Filter, FilterFactory } from './filter'; import { Metadata, MetadataValue } from './metadata'; @@ -238,10 +238,10 @@ export class CompressionFilter extends BaseFilter implements Filter { * and the output is a framed and possibly compressed message. For this * reason, this filter should be at the bottom of the filter stack */ const resolvedMessage: WriteObject = await message; - const compress = !(this.sendCompression instanceof IdentityHandler); - // resolvedMessage.flags === undefined - // ? false - // : (resolvedMessage.flags & WriteFlags.NoCompress) === 0; + const compress = + resolvedMessage.flags === undefined + ? !(this.sendCompression instanceof IdentityHandler) + : (resolvedMessage.flags & WriteFlags.NoCompress) === 0; return { message: await this.sendCompression.writeMessage( resolvedMessage.message, diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 8ea488a4d..f1d3a8abd 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -561,7 +561,9 @@ export class Http2ServerCallStream< this.emit('receiveMessage'); - const decompressedMessage = await this.getDecompressedMessage(requestBytes, encoding); + const compressed = requestBytes.readUInt8(0) === 1; + const compressedMessageEncoding = compressed ? encoding : undefined; + const decompressedMessage = await this.getDecompressedMessage(requestBytes, compressedMessageEncoding); // Encountered an error with decompression; it'll already have been propogated back // Just return early @@ -748,7 +750,9 @@ export class Http2ServerCallStream< } this.emit('receiveMessage'); - const decompressedMessage = await this.getDecompressedMessage(message, encoding); + const compressed = message.readUInt8(0) === 1; + const compressedMessageEncoding = compressed ? encoding : undefined; + const decompressedMessage = await this.getDecompressedMessage(message, compressedMessageEncoding); // Encountered an error with decompression; it'll already have been propogated back // Just return early diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index c8171e796..b6cffd81c 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -849,5 +849,31 @@ describe('Compressed requests', () => { }) }); }); + + it('Should not compress requests when the NoCompress write flag is used', done => { + const bidiStream = client.bidiStream(); + let timesRequested = 0; + let timesResponded = 0; + + bidiStream.on('data', () => { + timesResponded += 1; + }); + + bidiStream.on('error', (err) => { + assert.ifError(err); + done(); + }); + + bidiStream.on('end', () => { + assert.equal(timesResponded, timesRequested); + done(); + }); + + bidiStream._write({ message: 'foo' }, '2', (err: any) => { + assert.ifError(err); + timesRequested += 1; + setTimeout(() => bidiStream.end(), 10); + }); + }); }); }); From 91ae2b44b165f4d77ed76deab5275df98230776b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 25 Oct 2021 13:29:06 -0700 Subject: [PATCH 111/694] grpc-js: Handle undefined socket.localAddress --- packages/grpc-js/src/channelz.ts | 4 ++-- packages/grpc-js/src/server.ts | 2 +- packages/grpc-js/src/subchannel.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index a1c85eb47..768efaa58 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -298,7 +298,7 @@ export interface TlsInfo { } export interface SocketInfo { - localAddress: SubchannelAddress; + localAddress: SubchannelAddress | null; remoteAddress: SubchannelAddress | null; security: TlsInfo | null; remoteName: string | null; @@ -629,7 +629,7 @@ function GetSocket(call: ServerUnaryCall Date: Tue, 26 Oct 2021 13:44:03 -0700 Subject: [PATCH 112/694] change encoding type from MetadataValue to string --- packages/grpc-js/src/server-call.ts | 11 +++++------ packages/grpc-js/src/server.ts | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index f1d3a8abd..d3c622b5c 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -33,7 +33,6 @@ import { StreamDecoder } from './stream-decoder'; import { ObjectReadable, ObjectWritable } from './object-stream'; import { ChannelOptions } from './channel-options'; import * as logging from './logging'; -import { MetadataValue } from '.'; const TRACER_NAME = 'server_call'; @@ -139,7 +138,7 @@ export class ServerReadableStreamImpl private call: Http2ServerCallStream, public metadata: Metadata, public deserialize: Deserialize, - encoding?: MetadataValue + encoding?: string ) { super({ objectMode: true }); this.cancelled = false; @@ -254,7 +253,7 @@ export class ServerDuplexStreamImpl public metadata: Metadata, public serialize: Serialize, public deserialize: Deserialize, - encoding?: MetadataValue + encoding?: string ) { super({ objectMode: true }); this.cancelled = false; @@ -443,7 +442,7 @@ export class Http2ServerCallStream< return this.cancelled; } - private getDecompressedMessage(message: Buffer, encoding?: MetadataValue) { + private getDecompressedMessage(message: Buffer, encoding?: string) { switch (encoding) { case 'deflate': { return new Promise((resolve, reject) => { @@ -534,7 +533,7 @@ export class Http2ServerCallStream< return metadata; } - receiveUnaryMessage(encoding?: MetadataValue): Promise { + receiveUnaryMessage(encoding?: string): Promise { return new Promise((resolve, reject) => { const stream = this.stream; const chunks: Buffer[] = []; @@ -730,7 +729,7 @@ export class Http2ServerCallStream< readable: | ServerReadableStream | ServerDuplexStream, - encoding?: MetadataValue + encoding?: string ) { const decoder = new StreamDecoder(); diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 552f31b02..b69a795e9 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -61,7 +61,6 @@ import { import { parseUri } from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; -import { MetadataValue } from '.'; const TRACER_NAME = 'server'; @@ -779,7 +778,7 @@ export class Server { }); } const metadata = call.receiveMetadata(headers); - const encoding: MetadataValue | undefined = metadata.get('grpc-encoding')[0]; + const encoding = metadata.get('grpc-encoding')[0] as string | undefined; metadata.remove('grpc-encoding'); switch (handler.type) { @@ -864,7 +863,7 @@ async function handleUnary( call: Http2ServerCallStream, handler: UnaryHandler, metadata: Metadata, - encoding?: MetadataValue + encoding?: string ): Promise { const request = await call.receiveUnaryMessage(encoding); @@ -895,7 +894,7 @@ function handleClientStreaming( call: Http2ServerCallStream, handler: ClientStreamingHandler, metadata: Metadata, - encoding?: MetadataValue + encoding?: string ): void { const stream = new ServerReadableStreamImpl( call, @@ -926,7 +925,7 @@ async function handleServerStreaming( call: Http2ServerCallStream, handler: ServerStreamingHandler, metadata: Metadata, - encoding?: MetadataValue + encoding?: string ): Promise { const request = await call.receiveUnaryMessage(encoding); @@ -948,7 +947,7 @@ function handleBidiStreaming( call: Http2ServerCallStream, handler: BidiStreamingHandler, metadata: Metadata, - encoding?: MetadataValue + encoding?: string ): void { const stream = new ServerDuplexStreamImpl( call, From 21b09e25ba9c430389d8c61e674c531757e6f2ae Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 26 Oct 2021 13:51:42 -0700 Subject: [PATCH 113/694] separate compression algorithms to avoid circular dependency --- packages/grpc-js/src/channel-options.ts | 4 +++- .../grpc-js/src/compression-algorithms.ts | 22 +++++++++++++++++++ packages/grpc-js/src/compression-filter.ts | 7 +----- packages/grpc-js/src/index.ts | 2 +- 4 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 packages/grpc-js/src/compression-algorithms.ts diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index f0b37d7a4..b54ae4eda 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -15,6 +15,8 @@ * */ +import { CompressionAlgorithms } from './compression-algorithms'; + /** * An interface that contains options used when initializing a Channel instance. */ @@ -36,7 +38,7 @@ export interface ChannelOptions { 'grpc.enable_http_proxy'?: number; 'grpc.http_connect_target'?: string; 'grpc.http_connect_creds'?: string; - 'grpc.default_compression_algorithm'?: number; + 'grpc.default_compression_algorithm'?: keyof typeof CompressionAlgorithms; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; diff --git a/packages/grpc-js/src/compression-algorithms.ts b/packages/grpc-js/src/compression-algorithms.ts new file mode 100644 index 000000000..8ec283cf6 --- /dev/null +++ b/packages/grpc-js/src/compression-algorithms.ts @@ -0,0 +1,22 @@ +/* + * 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. + * + */ + +export const CompressionAlgorithms = { + 0: 'identity', + 1: 'deflate', + 2: 'gzip' +} as const; diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index ae507d333..69fcf48db 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -19,16 +19,11 @@ import * as zlib from 'zlib'; import { Call, WriteObject, WriteFlags } from './call-stream'; import { Channel } from './channel'; +import { CompressionAlgorithms } from './compression-algorithms'; import { BaseFilter, Filter, FilterFactory } from './filter'; import { Metadata, MetadataValue } from './metadata'; import { ChannelOptions } from './channel-options'; -export const CompressionAlgorithms = { - 0: 'identity', - 1: 'deflate', - 2: 'gzip' -} as const; - const CompressionAlgorithKeys = new Set(Object.keys(CompressionAlgorithms)); const isCompressionAlgorithmKey = (key: number | undefined): key is keyof typeof CompressionAlgorithms => { diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 7e1ed7a50..f34c19a1d 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -25,7 +25,7 @@ import { import { CallCredentials, OAuth2Client } from './call-credentials'; import { Deadline, StatusObject } from './call-stream'; import { Channel, ChannelImplementation } from './channel'; -import { CompressionAlgorithms } from './compression-filter'; +import { CompressionAlgorithms } from './compression-algorithms'; import { ConnectivityState } from './connectivity-state'; import { ChannelCredentials } from './channel-credentials'; import { From e03c1159ea650cc4bb931ae7be2ef22188bd635e Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 26 Oct 2021 14:01:37 -0700 Subject: [PATCH 114/694] log a warning when invalid value for grpc.default_compression_algorithm option is provided --- packages/grpc-js/src/compression-filter.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 69fcf48db..26b2836b6 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -19,10 +19,12 @@ import * as zlib from 'zlib'; import { Call, WriteObject, WriteFlags } from './call-stream'; import { Channel } from './channel'; +import { ChannelOptions } from './channel-options'; import { CompressionAlgorithms } from './compression-algorithms'; +import { LogVerbosity } from './constants'; import { BaseFilter, Filter, FilterFactory } from './filter'; +import * as logging from './logging'; import { Metadata, MetadataValue } from './metadata'; -import { ChannelOptions } from './channel-options'; const CompressionAlgorithKeys = new Set(Object.keys(CompressionAlgorithms)); @@ -186,6 +188,8 @@ export class CompressionFilter extends BaseFilter implements Filter { if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { this.defaultCompressionAlgorithm = CompressionAlgorithms[compressionAlgorithmKey]; this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); + } else { + logging.log(LogVerbosity.ERROR, 'Invalid value provided for grpc.default_compression_algorithm option'); } } From 7a31b4a65a2e2cc08231984602b84c02e336cd9a Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 26 Oct 2021 14:03:14 -0700 Subject: [PATCH 115/694] change test to use write instead of _write --- packages/grpc-js/test/test-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index b6cffd81c..c0e7ebf5f 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -869,7 +869,7 @@ describe('Compressed requests', () => { done(); }); - bidiStream._write({ message: 'foo' }, '2', (err: any) => { + bidiStream.write({ message: 'foo' }, '2', (err: any) => { assert.ifError(err); timesRequested += 1; setTimeout(() => bidiStream.end(), 10); From 25a1806e114431bd355abe1264455fac7ed74bde Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 26 Oct 2021 14:09:09 -0700 Subject: [PATCH 116/694] change year in new copyright heade --- packages/grpc-js/src/compression-algorithms.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/compression-algorithms.ts b/packages/grpc-js/src/compression-algorithms.ts index 8ec283cf6..ccc38e76b 100644 --- a/packages/grpc-js/src/compression-algorithms.ts +++ b/packages/grpc-js/src/compression-algorithms.ts @@ -1,5 +1,5 @@ /* - * Copyright 2019 gRPC authors. + * Copyright 2021 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From d01ff794fc2f28a7194fc4cb5e996e13b071dd29 Mon Sep 17 00:00:00 2001 From: Robert M Date: Tue, 26 Oct 2021 14:21:58 -0700 Subject: [PATCH 117/694] Update error message for invalid default_compression_algorithm Co-authored-by: Michael Lumish --- packages/grpc-js/src/compression-filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 26b2836b6..9568b413d 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -189,7 +189,7 @@ export class CompressionFilter extends BaseFilter implements Filter { this.defaultCompressionAlgorithm = CompressionAlgorithms[compressionAlgorithmKey]; this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); } else { - logging.log(LogVerbosity.ERROR, 'Invalid value provided for grpc.default_compression_algorithm option'); + logging.log(LogVerbosity.ERROR, `Invalid value provided for grpc.default_compression_algorithm option: ${compressionAlgorithmKey}`); } } From 6ba008110b0ba80d0211c05e7fb5e2da712eb4ad Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 26 Oct 2021 14:25:21 -0700 Subject: [PATCH 118/694] only attempt to use custom compression filter when option value is provided --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/compression-filter.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 9131d470f..893070948 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -59,7 +59,7 @@ }, "dependencies": { "@grpc/proto-loader": "^0.6.4", - "@types/node": "16.10.0", + "@types/node": ">=12.12.47", "@types/semver": "^7.3.9" }, "files": [ diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 26b2836b6..ea81ed8cf 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -185,11 +185,13 @@ export class CompressionFilter extends BaseFilter implements Filter { super(); const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']; - if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { - this.defaultCompressionAlgorithm = CompressionAlgorithms[compressionAlgorithmKey]; - this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); - } else { - logging.log(LogVerbosity.ERROR, 'Invalid value provided for grpc.default_compression_algorithm option'); + if (compressionAlgorithmKey !== undefined) { + if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { + this.defaultCompressionAlgorithm = CompressionAlgorithms[compressionAlgorithmKey]; + this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); + } else { + logging.log(LogVerbosity.ERROR, 'Invalid value provided for grpc.default_compression_algorithm option'); + } } } From c9659b5158d7a26b6046e20127ba6dc3e844486d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 28 Oct 2021 20:29:47 -0700 Subject: [PATCH 119/694] persist server supported encoding header across compression filter instances --- packages/grpc-js/src/compression-filter.ts | 25 ++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 090556381..e5f53334b 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -34,6 +34,10 @@ const isCompressionAlgorithmKey = (key: number | undefined): key is keyof typeof type CompressionAlgorithm = (typeof CompressionAlgorithms)[keyof typeof CompressionAlgorithms]; +type SharedCompressionFilterConfig = { + serverSupportedEncodingHeader?: string; +}; + abstract class CompressionHandler { protected abstract compressMessage(message: Buffer): Promise; protected abstract decompressMessage(data: Buffer): Promise; @@ -181,14 +185,25 @@ export class CompressionFilter extends BaseFilter implements Filter { private receiveCompression: CompressionHandler = new IdentityHandler(); private defaultCompressionAlgorithm: CompressionAlgorithm | undefined; - constructor(channelOptions: ChannelOptions) { + constructor(channelOptions: ChannelOptions, private sharedFilterConfig: SharedCompressionFilterConfig) { super(); + const serverSupportedEncodings = sharedFilterConfig.serverSupportedEncodingHeader?.split(','); const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']; if (compressionAlgorithmKey !== undefined) { if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { - this.defaultCompressionAlgorithm = CompressionAlgorithms[compressionAlgorithmKey]; - this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); + const clientSelectedEncoding = CompressionAlgorithms[compressionAlgorithmKey]; + /** + * There are two possible situations here: + * 1) We don't have any info yet from the server about what compression it supports + * In that case we should just use what the client tells us to use + * 2) We've previously received a response from the server including a grpc-accept-encoding header + * In that case we only want to use the encoding chosen by the client if the server supports it + */ + if (!serverSupportedEncodings || serverSupportedEncodings.includes(clientSelectedEncoding)) { + this.defaultCompressionAlgorithm = clientSelectedEncoding; + this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); + } } else { logging.log(LogVerbosity.ERROR, `Invalid value provided for grpc.default_compression_algorithm option: ${compressionAlgorithmKey}`); } @@ -223,6 +238,7 @@ export class CompressionFilter extends BaseFilter implements Filter { * If not, reset the sendCompression filter and have it use the default IdentityHandler */ const serverSupportedEncodingsHeader = metadata.get('grpc-accept-encoding')[0] as string | undefined; if (serverSupportedEncodingsHeader) { + this.sharedFilterConfig.serverSupportedEncodingHeader = serverSupportedEncodingsHeader; const serverSupportedEncodings = serverSupportedEncodingsHeader.split(','); if ((this.sendCompression instanceof DeflateHandler && !serverSupportedEncodings.includes('deflate')) @@ -263,8 +279,9 @@ export class CompressionFilter extends BaseFilter implements Filter { export class CompressionFilterFactory implements FilterFactory { + private sharedFilterConfig: SharedCompressionFilterConfig = {}; constructor(private readonly channel: Channel, private readonly options: ChannelOptions) {} createFilter(callStream: Call): CompressionFilter { - return new CompressionFilter(this.options); + return new CompressionFilter(this.options, this.sharedFilterConfig); } } From 47bb8b669e6e28453f84092b6f3ea0c4ff81e64a Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 28 Oct 2021 20:36:29 -0700 Subject: [PATCH 120/694] move string split down to where it matters --- packages/grpc-js/src/compression-filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index e5f53334b..a6428ce01 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -188,11 +188,11 @@ export class CompressionFilter extends BaseFilter implements Filter { constructor(channelOptions: ChannelOptions, private sharedFilterConfig: SharedCompressionFilterConfig) { super(); - const serverSupportedEncodings = sharedFilterConfig.serverSupportedEncodingHeader?.split(','); const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']; if (compressionAlgorithmKey !== undefined) { if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { const clientSelectedEncoding = CompressionAlgorithms[compressionAlgorithmKey]; + const serverSupportedEncodings = sharedFilterConfig.serverSupportedEncodingHeader?.split(','); /** * There are two possible situations here: * 1) We don't have any info yet from the server about what compression it supports From 9150bdfd247b5f3215bac8b87097cf4691b262e3 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 29 Oct 2021 19:19:09 -0700 Subject: [PATCH 121/694] default to identity header everywhere instead of leaving it undefined --- packages/grpc-js/src/compression-filter.ts | 24 +++++++++++-------- packages/grpc-js/src/server-call.ts | 27 ++++++++++++++-------- packages/grpc-js/src/server.ts | 10 ++++---- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index a6428ce01..cbdd35104 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -183,7 +183,7 @@ function getCompressionHandler(compressionName: string): CompressionHandler { export class CompressionFilter extends BaseFilter implements Filter { private sendCompression: CompressionHandler = new IdentityHandler(); private receiveCompression: CompressionHandler = new IdentityHandler(); - private defaultCompressionAlgorithm: CompressionAlgorithm | undefined; + private currentCompressionAlgorithm: CompressionAlgorithm = 'identity'; constructor(channelOptions: ChannelOptions, private sharedFilterConfig: SharedCompressionFilterConfig) { super(); @@ -201,8 +201,8 @@ export class CompressionFilter extends BaseFilter implements Filter { * In that case we only want to use the encoding chosen by the client if the server supports it */ if (!serverSupportedEncodings || serverSupportedEncodings.includes(clientSelectedEncoding)) { - this.defaultCompressionAlgorithm = clientSelectedEncoding; - this.sendCompression = getCompressionHandler(this.defaultCompressionAlgorithm); + this.currentCompressionAlgorithm = clientSelectedEncoding; + this.sendCompression = getCompressionHandler(this.currentCompressionAlgorithm); } } else { logging.log(LogVerbosity.ERROR, `Invalid value provided for grpc.default_compression_algorithm option: ${compressionAlgorithmKey}`); @@ -215,10 +215,11 @@ export class CompressionFilter extends BaseFilter implements Filter { headers.set('grpc-accept-encoding', 'identity,deflate,gzip'); headers.set('accept-encoding', 'identity'); - if (this.defaultCompressionAlgorithm && ['deflate', 'gzip'].includes(this.defaultCompressionAlgorithm)) { - headers.set('grpc-encoding', this.defaultCompressionAlgorithm); - } else { + // No need to send the header if it's "identity" - behavior is identical; save the bandwidth + if (this.currentCompressionAlgorithm === 'identity') { headers.remove('grpc-encoding'); + } else { + headers.set('grpc-encoding', this.currentCompressionAlgorithm); } return headers; @@ -255,10 +256,13 @@ export class CompressionFilter extends BaseFilter implements Filter { * and the output is a framed and possibly compressed message. For this * reason, this filter should be at the bottom of the filter stack */ const resolvedMessage: WriteObject = await message; - const compress = - resolvedMessage.flags === undefined - ? !(this.sendCompression instanceof IdentityHandler) - : (resolvedMessage.flags & WriteFlags.NoCompress) === 0; + let compress: boolean; + if (this.sendCompression instanceof IdentityHandler) { + compress = false; + } else { + compress = ((resolvedMessage.flags ?? 0) & WriteFlags.NoCompress) === 0; + } + return { message: await this.sendCompression.writeMessage( resolvedMessage.message, diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index d3c622b5c..b340b3446 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -138,7 +138,7 @@ export class ServerReadableStreamImpl private call: Http2ServerCallStream, public metadata: Metadata, public deserialize: Deserialize, - encoding?: string + encoding: string ) { super({ objectMode: true }); this.cancelled = false; @@ -253,7 +253,7 @@ export class ServerDuplexStreamImpl public metadata: Metadata, public serialize: Serialize, public deserialize: Deserialize, - encoding?: string + encoding: string ) { super({ objectMode: true }); this.cancelled = false; @@ -442,7 +442,7 @@ export class Http2ServerCallStream< return this.cancelled; } - private getDecompressedMessage(message: Buffer, encoding?: string) { + private getDecompressedMessage(message: Buffer, encoding: string) { switch (encoding) { case 'deflate': { return new Promise((resolve, reject) => { @@ -477,9 +477,18 @@ export class Http2ServerCallStream< }); }); } - - default: + + case 'identity': { return Promise.resolve(message); + } + + default: { + this.sendError({ + code: Status.UNIMPLEMENTED, + details: `Received "grpc-encoding" header "${encoding}", which is not supported`, + }); + return Promise.resolve(); + } } } @@ -533,7 +542,7 @@ export class Http2ServerCallStream< return metadata; } - receiveUnaryMessage(encoding?: string): Promise { + receiveUnaryMessage(encoding: string): Promise { return new Promise((resolve, reject) => { const stream = this.stream; const chunks: Buffer[] = []; @@ -561,7 +570,7 @@ export class Http2ServerCallStream< this.emit('receiveMessage'); const compressed = requestBytes.readUInt8(0) === 1; - const compressedMessageEncoding = compressed ? encoding : undefined; + const compressedMessageEncoding = compressed ? encoding : 'identity'; const decompressedMessage = await this.getDecompressedMessage(requestBytes, compressedMessageEncoding); // Encountered an error with decompression; it'll already have been propogated back @@ -729,7 +738,7 @@ export class Http2ServerCallStream< readable: | ServerReadableStream | ServerDuplexStream, - encoding?: string + encoding: string ) { const decoder = new StreamDecoder(); @@ -750,7 +759,7 @@ export class Http2ServerCallStream< this.emit('receiveMessage'); const compressed = message.readUInt8(0) === 1; - const compressedMessageEncoding = compressed ? encoding : undefined; + const compressedMessageEncoding = compressed ? encoding : 'identity'; const decompressedMessage = await this.getDecompressedMessage(message, compressedMessageEncoding); // Encountered an error with decompression; it'll already have been propogated back diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index b69a795e9..a83363674 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -778,7 +778,7 @@ export class Server { }); } const metadata = call.receiveMetadata(headers); - const encoding = metadata.get('grpc-encoding')[0] as string | undefined; + const encoding = (metadata.get('grpc-encoding')[0] as string | undefined) ?? 'identity'; metadata.remove('grpc-encoding'); switch (handler.type) { @@ -863,7 +863,7 @@ async function handleUnary( call: Http2ServerCallStream, handler: UnaryHandler, metadata: Metadata, - encoding?: string + encoding: string ): Promise { const request = await call.receiveUnaryMessage(encoding); @@ -894,7 +894,7 @@ function handleClientStreaming( call: Http2ServerCallStream, handler: ClientStreamingHandler, metadata: Metadata, - encoding?: string + encoding: string ): void { const stream = new ServerReadableStreamImpl( call, @@ -925,7 +925,7 @@ async function handleServerStreaming( call: Http2ServerCallStream, handler: ServerStreamingHandler, metadata: Metadata, - encoding?: string + encoding: string ): Promise { const request = await call.receiveUnaryMessage(encoding); @@ -947,7 +947,7 @@ function handleBidiStreaming( call: Http2ServerCallStream, handler: BidiStreamingHandler, metadata: Metadata, - encoding?: string + encoding: string ): void { const stream = new ServerDuplexStreamImpl( call, From 692ee3c03faab8fcbd3dfaa70bff57e32c739c3d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 1 Nov 2021 14:23:53 -0700 Subject: [PATCH 122/694] grpc-js-xds: Switch from udpa to xds submodule --- .gitmodules | 6 +- packages/grpc-js-xds/deps/udpa | 1 - packages/grpc-js-xds/deps/xds | 1 + packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/generated/cluster.ts | 9 +++ packages/grpc-js-xds/src/generated/fault.ts | 9 +++ .../generated/google/protobuf/FieldOptions.ts | 3 + .../generated/google/protobuf/FileOptions.ts | 3 + .../google/protobuf/MessageOptions.ts | 3 + .../src/generated/http_connection_manager.ts | 9 +++ .../grpc-js-xds/src/generated/listener.ts | 9 +++ packages/grpc-js-xds/src/generated/route.ts | 9 +++ .../grpc-js-xds/src/generated/typed_struct.ts | 7 ++ .../annotations/FieldMigrateAnnotation.ts | 2 +- .../annotations/FieldSecurityAnnotation.ts | 2 +- .../udpa/annotations/FileMigrateAnnotation.ts | 2 +- .../udpa/annotations/MigrateAnnotation.ts | 2 +- .../udpa/annotations/PackageVersionStatus.ts | 2 +- .../udpa/annotations/StatusAnnotation.ts | 2 +- .../udpa/annotations/VersioningAnnotation.ts | 2 +- .../src/generated/udpa/type/v1/TypedStruct.ts | 2 +- .../annotations/v3/FieldStatusAnnotation.ts | 16 ++++ .../annotations/v3/FileStatusAnnotation.ts | 16 ++++ .../annotations/v3/MessageStatusAnnotation.ts | 16 ++++ .../annotations/v3/PackageVersionStatus.ts | 21 +++++ .../xds/annotations/v3/StatusAnnotation.ts | 25 ++++++ .../src/generated/xds/core/v3/Authority.ts | 2 +- .../generated/xds/core/v3/CollectionEntry.ts | 2 +- .../generated/xds/core/v3/ContextParams.ts | 2 +- .../generated/xds/core/v3/ResourceLocator.ts | 4 +- .../src/generated/xds/type/v3/TypedStruct.ts | 77 +++++++++++++++++++ packages/grpc-js-xds/src/http-filter.ts | 17 ++-- .../src/http-filter/fault-injection-filter.ts | 2 +- packages/grpc-js-xds/src/resources.ts | 2 +- packages/grpc-js-xds/src/xds-client.ts | 2 +- 35 files changed, 263 insertions(+), 28 deletions(-) delete mode 160000 packages/grpc-js-xds/deps/udpa create mode 160000 packages/grpc-js-xds/deps/xds create mode 100644 packages/grpc-js-xds/src/generated/xds/annotations/v3/FieldStatusAnnotation.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/annotations/v3/FileStatusAnnotation.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/annotations/v3/MessageStatusAnnotation.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/type/v3/TypedStruct.ts diff --git a/.gitmodules b/.gitmodules index d3c1ebaf6..f54fa6afc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,12 +10,12 @@ [submodule "packages/grpc-js-xds/deps/envoy-api"] path = packages/grpc-js-xds/deps/envoy-api url = https://github.com/envoyproxy/data-plane-api.git -[submodule "packages/grpc-js-xds/deps/udpa"] - path = packages/grpc-js-xds/deps/udpa - url = https://github.com/cncf/udpa.git [submodule "packages/grpc-js-xds/deps/googleapis"] path = packages/grpc-js-xds/deps/googleapis url = https://github.com/googleapis/googleapis.git [submodule "packages/grpc-js-xds/deps/protoc-gen-validate"] path = packages/grpc-js-xds/deps/protoc-gen-validate url = https://github.com/envoyproxy/protoc-gen-validate.git +[submodule "packages/grpc-js-xds/deps/xds"] + path = packages/grpc-js-xds/deps/xds + url = https://github.com/cncf/xds.git diff --git a/packages/grpc-js-xds/deps/udpa b/packages/grpc-js-xds/deps/udpa deleted file mode 160000 index cc1b757b3..000000000 --- a/packages/grpc-js-xds/deps/udpa +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cc1b757b3eddccaaaf0743cbb107742bb7e3ee4f diff --git a/packages/grpc-js-xds/deps/xds b/packages/grpc-js-xds/deps/xds new file mode 160000 index 000000000..cb28da345 --- /dev/null +++ b/packages/grpc-js-xds/deps/xds @@ -0,0 +1 @@ +Subproject commit cb28da3451f158a947dfc45090fe92b07b243bc1 diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index fefc0f33b..9be41a0a4 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/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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 envoy/extensions/filters/http/fault/v3/fault.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/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", "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" }, "repository": { diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index bc4465a30..6c2f7aa6f 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -189,6 +189,15 @@ export interface ProtoGrpcType { UInt64Rules: MessageTypeDefinition } xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } core: { v3: { Authority: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/fault.ts b/packages/grpc-js-xds/src/generated/fault.ts index 6eefcdfbd..0f60fc224 100644 --- a/packages/grpc-js-xds/src/generated/fault.ts +++ b/packages/grpc-js-xds/src/generated/fault.ts @@ -210,6 +210,15 @@ export interface ProtoGrpcType { UInt64Rules: MessageTypeDefinition } xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } core: { v3: { Authority: MessageTypeDefinition 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 028e14b74..91af8a985 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -3,6 +3,7 @@ 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 @@ -32,6 +33,7 @@ export interface FieldOptions { '.udpa.annotations.sensitive'?: (boolean); '.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 { @@ -46,4 +48,5 @@ export interface FieldOptions__Output { '.udpa.annotations.sensitive': (boolean); '.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 b046c3ad3..48a376cd0 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts @@ -3,6 +3,7 @@ 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 @@ -30,6 +31,7 @@ export interface FileOptions { '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,4 +52,5 @@ export interface FileOptions__Output { '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 9dc521214..71d8c855b 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MessageOptions.ts @@ -3,6 +3,7 @@ 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); @@ -13,6 +14,7 @@ export interface MessageOptions { '.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 { @@ -24,4 +26,5 @@ export interface MessageOptions__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/http_connection_manager.ts b/packages/grpc-js-xds/src/generated/http_connection_manager.ts index bb4e80360..351e65400 100644 --- a/packages/grpc-js-xds/src/generated/http_connection_manager.ts +++ b/packages/grpc-js-xds/src/generated/http_connection_manager.ts @@ -250,6 +250,15 @@ export interface ProtoGrpcType { UInt64Rules: MessageTypeDefinition } xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } core: { v3: { Authority: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/listener.ts b/packages/grpc-js-xds/src/generated/listener.ts index cf7297a5b..aac080a90 100644 --- a/packages/grpc-js-xds/src/generated/listener.ts +++ b/packages/grpc-js-xds/src/generated/listener.ts @@ -228,6 +228,15 @@ export interface ProtoGrpcType { UInt64Rules: MessageTypeDefinition } xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } core: { v3: { Authority: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/route.ts b/packages/grpc-js-xds/src/generated/route.ts index 702395d19..67b9c5ce4 100644 --- a/packages/grpc-js-xds/src/generated/route.ts +++ b/packages/grpc-js-xds/src/generated/route.ts @@ -192,6 +192,15 @@ export interface ProtoGrpcType { UInt64Rules: MessageTypeDefinition } xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } core: { v3: { Authority: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/typed_struct.ts b/packages/grpc-js-xds/src/generated/typed_struct.ts index 47abe063c..e8dca13d5 100644 --- a/packages/grpc-js-xds/src/generated/typed_struct.ts +++ b/packages/grpc-js-xds/src/generated/typed_struct.ts @@ -70,5 +70,12 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + type: { + v3: { + TypedStruct: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/FieldMigrateAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldMigrateAnnotation.ts index 1ad015b25..4cbe9fdcb 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/FieldMigrateAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldMigrateAnnotation.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/annotations/migrate.proto +// Original file: deps/xds/udpa/annotations/migrate.proto export interface FieldMigrateAnnotation { diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts index 13d48b5ce..9c25fb84e 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/FieldSecurityAnnotation.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/annotations/security.proto +// Original file: deps/xds/udpa/annotations/security.proto /** diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/FileMigrateAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/FileMigrateAnnotation.ts index b7ef7c21d..95d29245d 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/FileMigrateAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/FileMigrateAnnotation.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/annotations/migrate.proto +// Original file: deps/xds/udpa/annotations/migrate.proto export interface FileMigrateAnnotation { diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/MigrateAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/MigrateAnnotation.ts index e3fdcaa99..16def9f28 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/MigrateAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/MigrateAnnotation.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/annotations/migrate.proto +// Original file: deps/xds/udpa/annotations/migrate.proto export interface MigrateAnnotation { diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts index c60c3f984..d0e181aa5 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/annotations/status.proto +// Original file: deps/xds/udpa/annotations/status.proto export enum PackageVersionStatus { /** diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts index 7b33ce9c8..f01b45063 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/annotations/status.proto +// Original file: deps/xds/udpa/annotations/status.proto import type { PackageVersionStatus as _udpa_annotations_PackageVersionStatus } from '../../udpa/annotations/PackageVersionStatus'; diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts index 1a3d09dc8..7a517a06c 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/VersioningAnnotation.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/annotations/versioning.proto +// Original file: deps/xds/udpa/annotations/versioning.proto export interface VersioningAnnotation { diff --git a/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts index 435872808..b080c6348 100644 --- a/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts +++ b/packages/grpc-js-xds/src/generated/udpa/type/v1/TypedStruct.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/udpa/type/v1/typed_struct.proto +// Original file: deps/xds/udpa/type/v1/typed_struct.proto import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct'; diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/FieldStatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FieldStatusAnnotation.ts new file mode 100644 index 000000000..744e13837 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FieldStatusAnnotation.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + + +export interface FieldStatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); +} + +export interface FieldStatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/FileStatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FileStatusAnnotation.ts new file mode 100644 index 000000000..cbc3ab3f9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/FileStatusAnnotation.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + + +export interface FileStatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); +} + +export interface FileStatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/MessageStatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/MessageStatusAnnotation.ts new file mode 100644 index 000000000..f403f6506 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/MessageStatusAnnotation.ts @@ -0,0 +1,16 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + + +export interface MessageStatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); +} + +export interface MessageStatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts new file mode 100644 index 000000000..e76e2848f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts @@ -0,0 +1,21 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + +export enum PackageVersionStatus { + /** + * Unknown package version status. + */ + UNKNOWN = 0, + /** + * This version of the package is frozen. + */ + FROZEN = 1, + /** + * This version of the package is the active development version. + */ + ACTIVE = 2, + /** + * This version of the package is the candidate for the next major version. It + * is typically machine generated from the active development version. + */ + NEXT_MAJOR_VERSION_CANDIDATE = 3, +} diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts new file mode 100644 index 000000000..58efbd8f7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts @@ -0,0 +1,25 @@ +// Original file: deps/xds/xds/annotations/v3/status.proto + +import type { PackageVersionStatus as _xds_annotations_v3_PackageVersionStatus } from '../../../xds/annotations/v3/PackageVersionStatus'; + +export interface StatusAnnotation { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress'?: (boolean); + /** + * The entity belongs to a package with the given version status. + */ + 'package_version_status'?: (_xds_annotations_v3_PackageVersionStatus | keyof typeof _xds_annotations_v3_PackageVersionStatus); +} + +export interface StatusAnnotation__Output { + /** + * The entity is work-in-progress and subject to breaking changes. + */ + 'work_in_progress': (boolean); + /** + * The entity belongs to a package with the given version status. + */ + 'package_version_status': (keyof typeof _xds_annotations_v3_PackageVersionStatus); +} diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts index 8e9211838..9505731d7 100644 --- a/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/Authority.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/xds/core/v3/authority.proto +// Original file: deps/xds/xds/core/v3/authority.proto /** diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts index 8d583d961..5d2ce9721 100644 --- a/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/CollectionEntry.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/xds/core/v3/collection_entry.proto +// Original file: deps/xds/xds/core/v3/collection_entry.proto import type { ResourceLocator as _xds_core_v3_ResourceLocator, ResourceLocator__Output as _xds_core_v3_ResourceLocator__Output } from '../../../xds/core/v3/ResourceLocator'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts index f9c57249c..19a8a99bb 100644 --- a/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ContextParams.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/xds/core/v3/context_params.proto +// Original file: deps/xds/xds/core/v3/context_params.proto /** diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts index 2b314d8bb..bb1f822b8 100644 --- a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts @@ -1,4 +1,4 @@ -// Original file: deps/udpa/xds/core/v3/resource_locator.proto +// Original file: deps/xds/xds/core/v3/resource_locator.proto import type { ContextParams as _xds_core_v3_ContextParams, ContextParams__Output as _xds_core_v3_ContextParams__Output } from '../../../xds/core/v3/ContextParams'; import type { ResourceLocator as _xds_core_v3_ResourceLocator, ResourceLocator__Output as _xds_core_v3_ResourceLocator__Output } from '../../../xds/core/v3/ResourceLocator'; @@ -95,7 +95,7 @@ export interface _xds_core_v3_ResourceLocator_Directive__Output { 'directive': "alt"|"entry"; } -// Original file: deps/udpa/xds/core/v3/resource_locator.proto +// Original file: deps/xds/xds/core/v3/resource_locator.proto export enum _xds_core_v3_ResourceLocator_Scheme { XDSTP = 0, diff --git a/packages/grpc-js-xds/src/generated/xds/type/v3/TypedStruct.ts b/packages/grpc-js-xds/src/generated/xds/type/v3/TypedStruct.ts new file mode 100644 index 000000000..a0df831d2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/type/v3/TypedStruct.ts @@ -0,0 +1,77 @@ +// Original file: deps/xds/xds/type/v3/typed_struct.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct'; + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url'?: (string); + /** + * A JSON representation of the above specified type. + */ + 'value'?: (_google_protobuf_Struct | null); +} + +/** + * A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that + * describes the type of the serialized message. This is very similar to google.protobuf.Any, + * instead of having protocol buffer binary, this employs google.protobuf.Struct as value. + * + * This message is intended to be embedded inside Any, so it shouldn't be directly referred + * from other UDPA messages. + * + * When packing an opaque extension config, packing the expected type into Any is preferred + * wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor + * is not available, for example if: + * - A control plane sends opaque message that is originally from external source in human readable + * format such as JSON or YAML. + * - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot + * serialize the message in protocol buffer binary format. + * - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension + * uses. This has to be indicated in the DPLB capability negotiation. + * + * When a DPLB receives a TypedStruct in Any, it should: + * - Check if the type_url of the TypedStruct matches the type the extension expects. + * - Convert value to the type described in type_url and perform validation. + * TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link + * protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions). + */ +export interface TypedStruct__Output { + /** + * A URL that uniquely identifies the type of the serialize protocol buffer message. + * This has same semantics and format described in google.protobuf.Any: + * https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto + */ + 'type_url': (string); + /** + * A JSON representation of the above specified type. + */ + 'value': (_google_protobuf_Struct__Output | null); +} diff --git a/packages/grpc-js-xds/src/http-filter.ts b/packages/grpc-js-xds/src/http-filter.ts index a00cde5db..29ce5958f 100644 --- a/packages/grpc-js-xds/src/http-filter.ts +++ b/packages/grpc-js-xds/src/http-filter.ts @@ -20,7 +20,7 @@ import { experimental, logVerbosity } from '@grpc/grpc-js'; import { Any__Output } from './generated/google/protobuf/Any'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; -import { TypedStruct__Output } from './generated/udpa/type/v1/TypedStruct'; +import { TypedStruct__Output as TypedStruct__Output } from './generated/xds/type/v3/TypedStruct'; import { FilterConfig__Output } from './generated/envoy/config/route/v3/FilterConfig'; import { HttpFilter__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter'; @@ -30,19 +30,22 @@ function trace(text: string): void { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); } -const TYPED_STRUCT_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct'; -const TYPED_STRUCT_NAME = 'udpa.type.v1.TypedStruct'; +const TYPED_STRUCT_UDPA_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct'; +const TYPED_STRUCT_UDPA_NAME = 'udpa.type.v1.TypedStruct'; +const TYPED_STRUCT_XDS_URL = 'type.googleapis.com/xds.type.v3.TypedStruct'; +const TYPED_STRUCT_XDS_NAME = 'xds.type.v3.TypedStruct'; const FILTER_CONFIG_URL = 'type.googleapis.com/envoy.config.route.v3.FilterConfig'; const FILTER_CONFIG_NAME = 'envoy.config.route.v3.FilterConfig'; const resourceRoot = loadProtosWithOptionsSync([ 'udpa/type/v1/typed_struct.proto', + 'xds/type/v3/typed_struct.proto', 'envoy/config/route/v3/route_components.proto'], { keepCase: true, includeDirs: [ // Paths are relative to src/build - __dirname + '/../../deps/udpa/', + __dirname + '/../../deps/xds/', __dirname + '/../../deps/envoy-api/', __dirname + '/../../deps/protoc-gen-validate/' ], @@ -91,7 +94,7 @@ function parseAnyMessage(message: Any__Output): MessageType | null export function getTopLevelFilterUrl(encodedConfig: Any__Output): string { let typeUrl: string; - if (encodedConfig.type_url === TYPED_STRUCT_URL) { + if (encodedConfig.type_url === TYPED_STRUCT_UDPA_URL || encodedConfig.type_url === TYPED_STRUCT_XDS_URL) { const typedStruct = parseAnyMessage(encodedConfig) if (typedStruct) { return typedStruct.type_url; @@ -154,7 +157,7 @@ export function validateOverrideFilter(encodedConfig: Any__Output): boolean { } else { realConfig = encodedConfig; } - if (realConfig.type_url === TYPED_STRUCT_URL) { + if (realConfig.type_url === TYPED_STRUCT_UDPA_URL || realConfig.type_url === TYPED_STRUCT_XDS_URL) { const typedStruct = parseAnyMessage(encodedConfig); if (typedStruct) { typeUrl = typedStruct.type_url; @@ -215,7 +218,7 @@ export function parseOverrideFilterConfig(encodedConfig: Any__Output) { } else { realConfig = encodedConfig; } - if (realConfig.type_url === TYPED_STRUCT_URL) { + if (realConfig.type_url === TYPED_STRUCT_UDPA_URL || realConfig.type_url === TYPED_STRUCT_XDS_URL) { const typedStruct = parseAnyMessage(encodedConfig); if (typedStruct) { typeUrl = typedStruct.type_url; diff --git a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts index e0ee658c7..a49ec77d4 100644 --- a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts +++ b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts @@ -38,7 +38,7 @@ const resourceRoot = loadProtosWithOptionsSync([ keepCase: true, includeDirs: [ // Paths are relative to src/build/http-filter - __dirname + '/../../../deps/udpa/', + __dirname + '/../../../deps/xds/', __dirname + '/../../../deps/envoy-api/', __dirname + '/../../../deps/protoc-gen-validate/' ], diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts index 516980de8..4a7e22763 100644 --- a/packages/grpc-js-xds/src/resources.ts +++ b/packages/grpc-js-xds/src/resources.ts @@ -70,7 +70,7 @@ const resourceRoot = loadProtosWithOptionsSync([ includeDirs: [ // Paths are relative to src/build __dirname + '/../../deps/envoy-api/', - __dirname + '/../../deps/udpa/', + __dirname + '/../../deps/xds/', __dirname + '/../../deps/googleapis/', __dirname + '/../../deps/protoc-gen-validate/', ], diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 2b8fae44c..8a08276eb 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -87,7 +87,7 @@ function loadAdsProtos(): Promise< includeDirs: [ // Paths are relative to src/build __dirname + '/../../deps/envoy-api/', - __dirname + '/../../deps/udpa/', + __dirname + '/../../deps/xds/', __dirname + '/../../deps/googleapis/', __dirname + '/../../deps/protoc-gen-validate/', ], From 3c48058d9a2c5c95d5fc88d54f55221a56c7aea5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 1 Nov 2021 16:09:49 -0700 Subject: [PATCH 123/694] grpc-js-xds: Update files list to account for submodule change --- packages/grpc-js-xds/package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 9be41a0a4..fca6ab80d 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -63,7 +63,11 @@ "deps/googleapis/google/api/**/*.proto", "deps/googleapis/google/protobuf/**/*.proto", "deps/googleapis/google/rpc/**/*.proto", - "deps/udpa/udpa/annotations/**/*.proto", + "deps/xds/udpa/annotations/**/*.proto", + "deps/xds/udpa/type/**/*.proto", + "deps/xds/xds/annotations/**/*.proto", + "deps/xds/xds/core/**/*.proto", + "deps/xds/xds/type/**/*.proto", "deps/protoc-gen-validate/validate/**/*.proto" ] } From 5c61a6a34e375eebbab1cd399de7a0b805916a4c Mon Sep 17 00:00:00 2001 From: Robert M Date: Mon, 1 Nov 2021 18:38:37 -0700 Subject: [PATCH 124/694] Update packages/grpc-js/src/server-call.ts Co-authored-by: Michael Lumish --- packages/grpc-js/src/server-call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index b340b3446..24028c073 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -485,7 +485,7 @@ export class Http2ServerCallStream< default: { this.sendError({ code: Status.UNIMPLEMENTED, - details: `Received "grpc-encoding" header "${encoding}", which is not supported`, + details: `Received message compressed with unsupported encoding "${encoding}"`, }); return Promise.resolve(); } From dc9752addcd70785f26f1cde17404c7a4b2ae47f Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 1 Nov 2021 19:04:21 -0700 Subject: [PATCH 125/694] change CompressionAlgorithms to an enum --- packages/grpc-js/src/channel-options.ts | 2 +- packages/grpc-js/src/compression-algorithms.ts | 10 +++++----- packages/grpc-js/src/compression-filter.ts | 10 ++++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index b54ae4eda..e10e92758 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -38,7 +38,7 @@ export interface ChannelOptions { 'grpc.enable_http_proxy'?: number; 'grpc.http_connect_target'?: string; 'grpc.http_connect_creds'?: string; - 'grpc.default_compression_algorithm'?: keyof typeof CompressionAlgorithms; + 'grpc.default_compression_algorithm'?: CompressionAlgorithms; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; diff --git a/packages/grpc-js/src/compression-algorithms.ts b/packages/grpc-js/src/compression-algorithms.ts index ccc38e76b..ca2c7a624 100644 --- a/packages/grpc-js/src/compression-algorithms.ts +++ b/packages/grpc-js/src/compression-algorithms.ts @@ -15,8 +15,8 @@ * */ -export const CompressionAlgorithms = { - 0: 'identity', - 1: 'deflate', - 2: 'gzip' -} as const; +export enum CompressionAlgorithms { + identity = 0, + deflate = 1, + gzip = 2 +}; diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index cbdd35104..de35dd44a 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -26,13 +26,11 @@ import { BaseFilter, Filter, FilterFactory } from './filter'; import * as logging from './logging'; import { Metadata, MetadataValue } from './metadata'; -const CompressionAlgorithKeys = new Set(Object.keys(CompressionAlgorithms)); - -const isCompressionAlgorithmKey = (key: number | undefined): key is keyof typeof CompressionAlgorithms => { - return typeof key === 'number' && CompressionAlgorithKeys.has(key.toString()); +const isCompressionAlgorithmKey = (key: number): key is CompressionAlgorithms => { + return typeof key === 'number' && typeof CompressionAlgorithms[key] === 'string'; } -type CompressionAlgorithm = (typeof CompressionAlgorithms)[keyof typeof CompressionAlgorithms]; +type CompressionAlgorithm = keyof typeof CompressionAlgorithms; type SharedCompressionFilterConfig = { serverSupportedEncodingHeader?: string; @@ -191,7 +189,7 @@ export class CompressionFilter extends BaseFilter implements Filter { const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']; if (compressionAlgorithmKey !== undefined) { if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { - const clientSelectedEncoding = CompressionAlgorithms[compressionAlgorithmKey]; + const clientSelectedEncoding = CompressionAlgorithms[compressionAlgorithmKey] as CompressionAlgorithm; const serverSupportedEncodings = sharedFilterConfig.serverSupportedEncodingHeader?.split(','); /** * There are two possible situations here: From c4d7fab13ea8a18e3bcf6b57bfb3220177ff2006 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 2 Nov 2021 19:17:44 -0700 Subject: [PATCH 126/694] simplify compression filter handling of server supported encoding headers --- packages/grpc-js/src/compression-filter.ts | 8 ++++---- packages/grpc-js/test/test-server.ts | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index de35dd44a..40825467e 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -240,10 +240,10 @@ export class CompressionFilter extends BaseFilter implements Filter { this.sharedFilterConfig.serverSupportedEncodingHeader = serverSupportedEncodingsHeader; const serverSupportedEncodings = serverSupportedEncodingsHeader.split(','); - if ((this.sendCompression instanceof DeflateHandler && !serverSupportedEncodings.includes('deflate')) - || (this.sendCompression instanceof GzipHandler && !serverSupportedEncodings.includes('gzip'))) { - this.sendCompression = new IdentityHandler(); - } + if (!serverSupportedEncodings.includes(this.currentCompressionAlgorithm)) { + this.sendCompression = new IdentityHandler(); + this.currentCompressionAlgorithm = 'identity'; + } } metadata.remove('grpc-accept-encoding'); return metadata; diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index c0e7ebf5f..cc20164a4 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -33,6 +33,7 @@ import { loadProtoFile } from './common'; import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; import { Request__Output } from './generated/Request'; +import { CompressionAlgorithms } from '../src/compression-algorithms'; const loadedTestServiceProto = protoLoader.loadSync('test/fixtures/test_service.proto', { keepCase: true, @@ -683,7 +684,7 @@ describe('Compressed requests', () => { `localhost:${assignedPort}`, grpc.credentials.createInsecure(), { - 'grpc.default_compression_algorithm': 1 + 'grpc.default_compression_algorithm': CompressionAlgorithms.deflate } ); done(); @@ -774,7 +775,7 @@ describe('Compressed requests', () => { `localhost:${assignedPort}`, grpc.credentials.createInsecure(), { - 'grpc.default_compression_algorithm': 2 + 'grpc.default_compression_algorithm': CompressionAlgorithms.gzip } ); From 1a60c4f3a92d6c0559422666ec446bdc3b0f70a3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Nov 2021 14:11:16 -0700 Subject: [PATCH 127/694] grpc-js: channelz: Fix algorithm for representing an IPv6 address in binary --- packages/grpc-js/src/channelz.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 768efaa58..94d84d8f2 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -392,6 +392,16 @@ export function unregisterChannelzRef(ref: ChannelRef | SubchannelRef | ServerRe } } +/** + * Parses a single section of an IPv6 address as two bytes + * @param addressSection A hexadecimal string of length up to 4 + * @returns The pair of bytes representing this address section + */ +function parseIPv6Section(addressSection: string): [number, number] { + const numberValue = Number.parseInt(addressSection, 16); + return [numberValue / 256 | 0, numberValue % 256]; +} + /** * Converts an IPv4 or IPv6 address from string representation to binary * representation @@ -412,8 +422,8 @@ function ipAddressStringToBuffer(ipAddress: string): Buffer | null { leftSection = ipAddress.substring(0, doubleColonIndex); rightSection = ipAddress.substring(doubleColonIndex + 2); } - const leftBuffer = Uint8Array.from(leftSection.split(':').map(segment => Number.parseInt(segment, 16))); - const rightBuffer = rightSection ? Uint8Array.from(rightSection.split(':').map(segment => Number.parseInt(segment, 16))) : new Uint8Array(); + const leftBuffer = Buffer.from(leftSection.split(':').map(segment => parseIPv6Section(segment)).flat()); + const rightBuffer = rightSection ? Buffer.from(rightSection.split(':').map(segment => parseIPv6Section(segment)).flat()) : Buffer.alloc(0); const middleBuffer = Buffer.alloc(16 - leftBuffer.length - rightBuffer.length, 0); return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]); } else { From bb26dcfd1ef1019af887e4a76c72262bc26a5378 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 5 Nov 2021 10:11:22 -0700 Subject: [PATCH 128/694] grpc-js: Fix handling of grpc.enable_channelz option --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel.ts | 11 +++++++-- packages/grpc-js/test/test-channelz.ts | 32 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 4acf948df..69d0662b7 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.4.2", + "version": "1.4.3", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 0b6465b2e..87defa740 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -832,6 +832,7 @@ export class Subchannel { headersString ); const streamSession = this.session; + let statsTracker: SubchannelCallStatsTracker; if (this.channelzEnabled) { this.callTracker.addCallStarted(); callStream.addStatusWatcher(status => { @@ -851,7 +852,7 @@ export class Subchannel { } } }); - callStream.attachHttp2Stream(http2Stream, this, extraFilters, { + statsTracker = { addMessageSent: () => { this.messagesSent += 1; this.lastMessageSentTimestamp = new Date(); @@ -859,8 +860,14 @@ export class Subchannel { addMessageReceived: () => { this.messagesReceived += 1; } - }); + } + } else { + statsTracker = { + addMessageSent: () => {}, + addMessageReceived: () => {} + } } + callStream.attachHttp2Stream(http2Stream, this, extraFilters, statsTracker); } /** diff --git a/packages/grpc-js/test/test-channelz.ts b/packages/grpc-js/test/test-channelz.ts index 2cca780e8..f14145c37 100644 --- a/packages/grpc-js/test/test-channelz.ts +++ b/packages/grpc-js/test/test-channelz.ts @@ -286,4 +286,36 @@ describe('Channelz', () => { }); }); }); +}); + +describe('Disabling channelz', () => { + let testServer: grpc.Server; + let testClient: ServiceClient; + beforeEach((done) => { + testServer = new grpc.Server({'grpc.enable_channelz': 0}); + testServer.addService(TestServiceClient.service, testServiceImpl); + testServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + testServer.start(); + testClient = new TestServiceClient(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.enable_channelz': 0}); + done(); + }); + }); + + afterEach(() => { + testClient.close(); + testServer.forceShutdown(); + }); + + it('Should still work', (done) => { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + testClient.unary({}, {deadline}, (error: grpc.ServiceError, value: unknown) => { + assert.ifError(error); + done(); + }); + }); }); \ No newline at end of file From b1be84a0214ed140e3c58d8624c11c5756223376 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 5 Nov 2021 11:54:15 -0700 Subject: [PATCH 129/694] Make IPv6 parsing code compatible with Node 10 --- packages/grpc-js/src/channelz.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 94d84d8f2..14c94fd0c 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -393,7 +393,7 @@ export function unregisterChannelzRef(ref: ChannelRef | SubchannelRef | ServerRe } /** - * Parses a single section of an IPv6 address as two bytes + * Parse a single section of an IPv6 address as two bytes * @param addressSection A hexadecimal string of length up to 4 * @returns The pair of bytes representing this address section */ @@ -402,6 +402,21 @@ function parseIPv6Section(addressSection: string): [number, number] { return [numberValue / 256 | 0, numberValue % 256]; } +/** + * Parse a chunk of an IPv6 address string to some number of bytes + * @param addressChunk Some number of segments of up to 4 hexadecimal + * characters each, joined by colons. + * @returns The list of bytes representing this address chunk + */ +function parseIPv6Chunk(addressChunk: string): number[] { + if (addressChunk === '') { + return []; + } + const bytePairs = addressChunk.split(':').map(section => parseIPv6Section(section)); + const result: number[] = []; + return result.concat(...bytePairs); +} + /** * Converts an IPv4 or IPv6 address from string representation to binary * representation @@ -413,17 +428,17 @@ function ipAddressStringToBuffer(ipAddress: string): Buffer | null { return Buffer.from(Uint8Array.from(ipAddress.split('.').map(segment => Number.parseInt(segment)))); } else if (isIPv6(ipAddress)) { let leftSection: string; - let rightSection: string | null; + let rightSection: string; const doubleColonIndex = ipAddress.indexOf('::'); if (doubleColonIndex === -1) { leftSection = ipAddress; - rightSection = null; + rightSection = ''; } else { leftSection = ipAddress.substring(0, doubleColonIndex); rightSection = ipAddress.substring(doubleColonIndex + 2); } - const leftBuffer = Buffer.from(leftSection.split(':').map(segment => parseIPv6Section(segment)).flat()); - const rightBuffer = rightSection ? Buffer.from(rightSection.split(':').map(segment => parseIPv6Section(segment)).flat()) : Buffer.alloc(0); + const leftBuffer = Buffer.from(parseIPv6Chunk(leftSection)); + const rightBuffer = Buffer.from(parseIPv6Chunk(rightSection)); const middleBuffer = Buffer.alloc(16 - leftBuffer.length - rightBuffer.length, 0); return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]); } else { From 96ae102eaf82e2094556a13539faa4f9a9edcabe Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 5 Nov 2021 19:59:03 -0700 Subject: [PATCH 130/694] fix path for loading test_service proto --- packages/grpc-js/test/test-server.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index cc20164a4..a5f7ec80b 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -35,15 +35,12 @@ import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; import { Request__Output } from './generated/Request'; import { CompressionAlgorithms } from '../src/compression-algorithms'; -const loadedTestServiceProto = protoLoader.loadSync('test/fixtures/test_service.proto', { +const loadedTestServiceProto = protoLoader.loadSync(path.join(__dirname, 'fixtures/test_service.proto'), { keepCase: true, longs: String, enums: String, defaults: true, - oneofs: true, - includeDirs: [ - `${__dirname}/../../proto` - ] + oneofs: true }); const testServiceGrpcObject = grpc.loadPackageDefinition(loadedTestServiceProto) as unknown as TestServiceGrpcType; From af966f04b864a811c7a38df26ca683af78c1a02f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Nov 2021 09:44:11 -0800 Subject: [PATCH 131/694] grpc-js: Remove an extra call to registerChannelzSocket --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 69d0662b7..e14ffa0eb 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.4.3", + "version": "1.4.4", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 87defa740..6f9471bb9 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -555,7 +555,6 @@ export class Subchannel { (error as Error).message ); }); - registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); } private startConnectingInternal() { From 69428b04457f0e9337e38072bc88932585855768 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 9 Nov 2021 21:26:54 -0800 Subject: [PATCH 132/694] simplify removal of compression prefix bytes --- packages/grpc-js/src/server-call.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 24028c073..709d20d41 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -454,8 +454,7 @@ export class Http2ServerCallStream< }); resolve(); } else { - const joined = Buffer.concat([message.slice(0, 5), output]); - resolve(joined); + resolve(output); } }); }); @@ -471,15 +470,14 @@ export class Http2ServerCallStream< }); resolve(); } else { - const joined = Buffer.concat([message.slice(0, 5), output]); - resolve(joined); + resolve(output); } }); }); } case 'identity': { - return Promise.resolve(message); + return Promise.resolve(message.slice(5)); } default: { @@ -603,10 +601,7 @@ export class Http2ServerCallStream< } deserializeMessage(bytes: Buffer) { - // TODO(cjihrig): Call compression aware deserializeMessage(). - const receivedMessage = bytes.slice(5); - - return this.handler.deserialize(receivedMessage); + return this.handler.deserialize(bytes); } async sendUnaryMessage( From 4b20bf3fce00106b975752d6fd5e226b1f227000 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 15 Nov 2021 10:05:10 -0800 Subject: [PATCH 133/694] proto-loader: Fix generated types for callbacks --- .../bin/proto-loader-gen-types.ts | 2 +- .../google/longrunning/Operations.ts | 80 +++++++++---------- .../google/showcase/v1beta1/Echo.ts | 80 +++++++++---------- packages/proto-loader/package.json | 2 +- 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 6c9596980..3ecf6dcc4 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -503,7 +503,7 @@ function generateServiceClientInterface(formatter: TextFormatter, serviceType: P } const requestType = getTypeInterfaceName(method.resolvedRequestType!); const responseType = getTypeInterfaceName(method.resolvedResponseType!) + '__Output'; - const callbackType = `(error?: grpc.ServiceError, result?: ${responseType}) => void`; + const callbackType = `grpc.requestCallback<${responseType}>`; if (method.requestStream) { if (method.responseStream) { // Bidi streaming diff --git a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts index 7644f974d..6358cbbfd 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts @@ -35,10 +35,10 @@ export interface OperationsClient extends grpc.Client { * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, * corresponding to `Code.CANCELLED`. */ - CancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - CancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - CancelOperation(argument: _google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - CancelOperation(argument: _google_longrunning_CancelOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; + CancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + CancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + CancelOperation(argument: _google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + CancelOperation(argument: _google_longrunning_CancelOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; /** * Starts asynchronous cancellation on a long-running operation. The server * makes a best effort to cancel the operation, but success is not @@ -51,10 +51,10 @@ export interface OperationsClient extends grpc.Client { * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, * corresponding to `Code.CANCELLED`. */ - cancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - cancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - cancelOperation(argument: _google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - cancelOperation(argument: _google_longrunning_CancelOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; + cancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + cancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + cancelOperation(argument: _google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + cancelOperation(argument: _google_longrunning_CancelOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; /** * Deletes a long-running operation. This method indicates that the client is @@ -62,39 +62,39 @@ export interface OperationsClient extends grpc.Client { * operation. If the server doesn't support this method, it returns * `google.rpc.Code.UNIMPLEMENTED`. */ - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; + DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; /** * Deletes a long-running operation. This method indicates that the client is * no longer interested in the operation result. It does not cancel the * operation. If the server doesn't support this method, it returns * `google.rpc.Code.UNIMPLEMENTED`. */ - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_protobuf_Empty__Output) => void): grpc.ClientUnaryCall; + deleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + deleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + deleteOperation(argument: _google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + deleteOperation(argument: _google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; /** * Gets the latest state of a long-running operation. Clients can use this * method to poll the operation result at intervals as recommended by the API * service. */ - GetOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - GetOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - GetOperation(argument: _google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - GetOperation(argument: _google_longrunning_GetOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; + GetOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + GetOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + GetOperation(argument: _google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + GetOperation(argument: _google_longrunning_GetOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; /** * Gets the latest state of a long-running operation. Clients can use this * method to poll the operation result at intervals as recommended by the API * service. */ - getOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - getOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - getOperation(argument: _google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - getOperation(argument: _google_longrunning_GetOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; + getOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + getOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + getOperation(argument: _google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + getOperation(argument: _google_longrunning_GetOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; /** * Lists operations that match the specified filter in the request. If the @@ -108,10 +108,10 @@ export interface OperationsClient extends grpc.Client { * collection id, however overriding users must ensure the name binding * is the parent resource, without the operations collection id. */ - ListOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; - ListOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; - ListOperations(argument: _google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; - ListOperations(argument: _google_longrunning_ListOperationsRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; + ListOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + ListOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + ListOperations(argument: _google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + ListOperations(argument: _google_longrunning_ListOperationsRequest, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; /** * Lists operations that match the specified filter in the request. If the * server doesn't support this method, it returns `UNIMPLEMENTED`. @@ -124,10 +124,10 @@ export interface OperationsClient extends grpc.Client { * collection id, however overriding users must ensure the name binding * is the parent resource, without the operations collection id. */ - listOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; - listOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; - listOperations(argument: _google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; - listOperations(argument: _google_longrunning_ListOperationsRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_ListOperationsResponse__Output) => void): grpc.ClientUnaryCall; + listOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + listOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + listOperations(argument: _google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + listOperations(argument: _google_longrunning_ListOperationsRequest, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; /** * Waits for the specified long-running operation until it is done or reaches @@ -140,10 +140,10 @@ export interface OperationsClient extends grpc.Client { * state before the specified timeout (including immediately), meaning even an * immediate response is no guarantee that the operation is done. */ - WaitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - WaitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - WaitOperation(argument: _google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - WaitOperation(argument: _google_longrunning_WaitOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; + WaitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + WaitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + WaitOperation(argument: _google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + WaitOperation(argument: _google_longrunning_WaitOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; /** * Waits for the specified long-running operation until it is done or reaches * at most a specified timeout, returning the latest state. If the operation @@ -155,10 +155,10 @@ export interface OperationsClient extends grpc.Client { * state before the specified timeout (including immediately), meaning even an * immediate response is no guarantee that the operation is done. */ - waitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - waitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - waitOperation(argument: _google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - waitOperation(argument: _google_longrunning_WaitOperationRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; + waitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + waitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + waitOperation(argument: _google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + waitOperation(argument: _google_longrunning_WaitOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts index 8e75c6c1a..30ecc8e23 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts @@ -25,19 +25,19 @@ export interface EchoClient extends grpc.Client { * and then return the response or error. * This method showcases how a client handles delays or retries. */ - Block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; - Block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; - Block(argument: _google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; - Block(argument: _google_showcase_v1beta1_BlockRequest, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; + Block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + Block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + Block(argument: _google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + Block(argument: _google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; /** * This method will block (wait) for the requested amount of time * and then return the response or error. * This method showcases how a client handles delays or retries. */ - block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; - block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; - block(argument: _google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; - block(argument: _google_showcase_v1beta1_BlockRequest, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_BlockResponse__Output) => void): grpc.ClientUnaryCall; + block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + block(argument: _google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + block(argument: _google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; /** * This method, upon receiving a request on the stream, the same content will @@ -59,34 +59,34 @@ export interface EchoClient extends grpc.Client { * by the client, this method will return the a concatenation of the strings * passed to it. This method showcases client-side streaming rpcs. */ - Collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - Collect(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - Collect(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - Collect(callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + Collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + Collect(metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + Collect(options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + Collect(callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; /** * This method will collect the words given to it. When the stream is closed * by the client, this method will return the a concatenation of the strings * passed to it. This method showcases client-side streaming rpcs. */ - collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - collect(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - collect(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - collect(callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + collect(metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + collect(options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + collect(callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; /** * This method simply echos the request. This method is showcases unary rpcs. */ - Echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; - Echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; - Echo(argument: _google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; - Echo(argument: _google_showcase_v1beta1_EchoRequest, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; + Echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; /** * This method simply echos the request. This method is showcases unary rpcs. */ - echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; - echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; - echo(argument: _google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; - echo(argument: _google_showcase_v1beta1_EchoRequest, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_EchoResponse__Output) => void): grpc.ClientUnaryCall; + echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; /** * This method split the given content into words and will pass each word back @@ -105,35 +105,35 @@ export interface EchoClient extends grpc.Client { * This is similar to the Expand method but instead of returning a stream of * expanded words, this method returns a paged list of expanded words. */ - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; + PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; /** * This is similar to the Expand method but instead of returning a stream of * expanded words, this method returns a paged list of expanded words. */ - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, callback: (error?: grpc.ServiceError, result?: _google_showcase_v1beta1_PagedExpandResponse__Output) => void): grpc.ClientUnaryCall; + pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; /** * This method will wait the requested amount of and then return. * This method showcases how a client handles a request timing out. */ - Wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - Wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - Wait(argument: _google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - Wait(argument: _google_showcase_v1beta1_WaitRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; + Wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + Wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + Wait(argument: _google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + Wait(argument: _google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; /** * This method will wait the requested amount of and then return. * This method showcases how a client handles a request timing out. */ - wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - wait(argument: _google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; - wait(argument: _google_showcase_v1beta1_WaitRequest, callback: (error?: grpc.ServiceError, result?: _google_longrunning_Operation__Output) => void): grpc.ClientUnaryCall; + wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + wait(argument: _google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + wait(argument: _google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; } diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index d31220186..cd27743a0 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.6", + "version": "0.6.7", "author": "Google Inc.", "contributors": [ { From 472baec1ff7d17ca8dbea409eb7a23d7820208bb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 15 Nov 2021 10:53:31 -0800 Subject: [PATCH 134/694] grpc-js: Provide full certificate in checkServerIdentity callback --- packages/grpc-js/src/channel-credentials.ts | 25 +++++---------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index 675e91628..c501692c9 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -27,16 +27,6 @@ function verifyIsBufferOrNull(obj: any, friendlyName: string): void { } } -/** - * A certificate as received by the checkServerIdentity callback. - */ -export interface Certificate { - /** - * The raw certificate in DER form. - */ - raw: Buffer; -} - /** * A callback that will receive the expected hostname and presented peer * certificate as parameters. The callback should return an error to @@ -45,7 +35,7 @@ export interface Certificate { */ export type CheckServerIdentityCallback = ( hostname: string, - cert: Certificate + cert: PeerCertificate ) => Error | undefined; function bufferOrNullEqual(buf1: Buffer | null, buf2: Buffer | null) { @@ -192,15 +182,10 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { cert: certChain || undefined, ciphers: CIPHER_SUITES, }); - this.connectionOptions = { secureContext }; - if (verifyOptions && verifyOptions.checkServerIdentity) { - this.connectionOptions.checkServerIdentity = ( - host: string, - cert: PeerCertificate - ) => { - return verifyOptions.checkServerIdentity!(host, { raw: cert.raw }); - }; - } + this.connectionOptions = { + secureContext, + checkServerIdentity: verifyOptions?.checkServerIdentity + }; } compose(callCredentials: CallCredentials): ChannelCredentials { From 3106057f5ad8f79a71d2ae411e116ad308a2e835 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 17 Nov 2021 12:36:38 -0800 Subject: [PATCH 135/694] grpc-js: Don't pass undefined checkServerIdentity --- packages/grpc-js/src/channel-credentials.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index c501692c9..02c9c309b 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -183,9 +183,12 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { ciphers: CIPHER_SUITES, }); this.connectionOptions = { - secureContext, - checkServerIdentity: verifyOptions?.checkServerIdentity + secureContext }; + // Node asserts that this option is a function, so we cannot pass undefined + if (verifyOptions?.checkServerIdentity) { + this.connectionOptions.checkServerIdentity = verifyOptions.checkServerIdentity; + } } compose(callCredentials: CallCredentials): ChannelCredentials { From 8658fd5752cca9338210eb4544c6f76231fdcf51 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 18 Nov 2021 12:48:40 -0800 Subject: [PATCH 136/694] grpc-js-xds: Remove LDS and CDS code for removing RDS and EDS entries --- .../src/xds-stream-state/cds-state.ts | 6 ------ .../src/xds-stream-state/eds-state.ts | 17 ----------------- .../src/xds-stream-state/lds-state.ts | 6 ------ .../src/xds-stream-state/rds-state.ts | 10 ---------- 4 files changed, 39 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 5dae09fb4..01e1db135 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -149,14 +149,9 @@ export class CdsState implements XdsStreamState { } this.latestResponses = validResponses; this.latestIsV2 = isV2; - const allEdsServiceNames: Set = new Set(); const allClusterNames: Set = new Set(); for (const message of validResponses) { allClusterNames.add(message.name); - const edsServiceName = message.eds_cluster_config?.service_name ?? ''; - allEdsServiceNames.add( - edsServiceName === '' ? message.name : edsServiceName - ); const watchers = this.watchers.get(message.name) ?? []; for (const watcher of watchers) { watcher.onValidUpdate(message, isV2); @@ -164,7 +159,6 @@ export class CdsState implements XdsStreamState { } trace('Received CDS updates for cluster names ' + Array.from(allClusterNames)); this.handleMissingNames(allClusterNames); - this.edsState.handleMissingNames(allEdsServiceNames); return errorMessage; } diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index 7d28ed5ff..7e57012cb 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -128,23 +128,6 @@ export class EdsState implements XdsStreamState { return true; } - /** - * Given a list of edsServiceNames (which may actually be the cluster name), - * for each watcher watching a name not on the list, call that watcher's - * onResourceDoesNotExist method. - * @param allClusterNames - */ - handleMissingNames(allEdsServiceNames: Set) { - for (const [edsServiceName, watcherList] of this.watchers.entries()) { - if (!allEdsServiceNames.has(edsServiceName)) { - trace('Reporting EDS resource does not exist for edsServiceName ' + edsServiceName); - for (const watcher of watcherList) { - watcher.onResourceDoesNotExist(); - } - } - } - } - handleResponses(responses: ClusterLoadAssignment__Output[], isV2: boolean) { const validResponses: ClusterLoadAssignment__Output[] = []; let errorMessage: string | null = null; diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 5706e3766..2797bc1a8 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -167,13 +167,8 @@ export class LdsState implements XdsStreamState { this.latestResponses = validResponses; this.latestIsV2 = isV2; const allTargetNames = new Set(); - const allRouteConfigNames = new Set(); for (const message of validResponses) { allTargetNames.add(message.name); - const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener!.value); - if (httpConnectionManager.rds) { - allRouteConfigNames.add(httpConnectionManager.rds.route_config_name); - } const watchers = this.watchers.get(message.name) ?? []; for (const watcher of watchers) { watcher.onValidUpdate(message, isV2); @@ -181,7 +176,6 @@ export class LdsState implements XdsStreamState { } trace('Received RDS response with route config names ' + Array.from(allTargetNames)); this.handleMissingNames(allTargetNames); - this.rdsState.handleMissingNames(allRouteConfigNames); return errorMessage; } diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 5a3854323..4f06bd2dc 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -172,16 +172,6 @@ export class RdsState implements XdsStreamState { return true; } - handleMissingNames(allRouteConfigNames: Set) { - for (const [routeConfigName, watcherList] of this.watchers.entries()) { - if (!allRouteConfigNames.has(routeConfigName)) { - for (const watcher of watcherList) { - watcher.onResourceDoesNotExist(); - } - } - } - } - handleResponses(responses: RouteConfiguration__Output[], isV2: boolean): string | null { const validResponses: RouteConfiguration__Output[] = []; let errorMessage: string | null = null; From 8b7a4a0d9e5fabe758d21150f99b0961db3dc5db Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 2 Dec 2021 16:14:03 -0500 Subject: [PATCH 137/694] grpc-js-xds: Update envoy submodule, generate CSDS code --- packages/grpc-js-xds/deps/envoy-api | 2 +- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/generated/ads.ts | 19 + packages/grpc-js-xds/src/generated/cluster.ts | 7 + packages/grpc-js-xds/src/generated/csds.ts | 393 ++++++++++ .../grpc-js-xds/src/generated/endpoint.ts | 27 + .../envoy/admin/v3/BootstrapConfigDump.ts | 32 + .../envoy/admin/v3/ClientResourceStatus.ts | 34 + .../envoy/admin/v3/ClustersConfigDump.ts | 164 +++++ .../generated/envoy/admin/v3/ConfigDump.ts | 65 ++ .../envoy/admin/v3/EndpointsConfigDump.ts | 126 ++++ .../envoy/admin/v3/ListenersConfigDump.ts | 198 +++++ .../envoy/admin/v3/RoutesConfigDump.ts | 130 ++++ .../envoy/admin/v3/ScopedRoutesConfigDump.ts | 144 ++++ .../envoy/admin/v3/SecretsConfigDump.ts | 162 +++++ .../envoy/admin/v3/UpdateFailureState.ts | 46 ++ .../generated/envoy/api/v2/core/CidrRange.ts | 4 +- .../envoy/config/accesslog/v3/AccessLog.ts | 40 +- .../config/accesslog/v3/RuntimeFilter.ts | 8 +- .../envoy/config/bootstrap/v3/Admin.ts | 75 ++ .../envoy/config/bootstrap/v3/Bootstrap.ts | 642 +++++++++++++++++ .../config/bootstrap/v3/ClusterManager.ts | 99 +++ .../config/bootstrap/v3/CustomInlineHeader.ts | 85 +++ .../envoy/config/bootstrap/v3/FatalAction.ts | 39 + .../config/bootstrap/v3/LayeredRuntime.ts | 25 + .../envoy/config/bootstrap/v3/Runtime.ts | 77 ++ .../envoy/config/bootstrap/v3/RuntimeLayer.ts | 142 ++++ .../envoy/config/bootstrap/v3/Watchdog.ts | 141 ++++ .../envoy/config/bootstrap/v3/Watchdogs.ts | 35 + .../config/cluster/v3/CircuitBreakers.ts | 20 +- .../envoy/config/cluster/v3/Cluster.ts | 508 +++++++++---- .../envoy/config/cluster/v3/Filter.ts | 6 +- .../config/cluster/v3/LoadBalancingPolicy.ts | 18 +- .../config/cluster/v3/OutlierDetection.ts | 40 +- .../config/core/v3/AggregatedConfigSource.ts | 4 +- .../core/v3/AlternateProtocolsCacheOptions.ts | 72 ++ .../envoy/config/core/v3/BackoffStrategy.ts | 12 +- .../envoy/config/core/v3/BindConfig.ts | 4 +- .../envoy/config/core/v3/CidrRange.ts | 4 +- .../envoy/config/core/v3/ConfigSource.ts | 12 +- .../config/core/v3/DnsResolutionConfig.ts | 42 ++ .../config/core/v3/DnsResolverOptions.ts | 36 + .../config/core/v3/EnvoyInternalAddress.ts | 4 +- .../envoy/config/core/v3/GrpcService.ts | 22 +- .../envoy/config/core/v3/HeaderValueOption.ts | 35 + .../envoy/config/core/v3/HealthCheck.ts | 84 ++- .../config/core/v3/Http1ProtocolOptions.ts | 23 +- .../config/core/v3/Http2ProtocolOptions.ts | 8 + .../config/core/v3/Http3ProtocolOptions.ts | 54 +- .../config/core/v3/HttpProtocolOptions.ts | 44 +- .../envoy/config/core/v3/KeepaliveSettings.ts | 18 + .../envoy/config/core/v3/Locality.ts | 8 +- .../envoy/config/core/v3/Metadata.ts | 27 + .../generated/envoy/config/core/v3/Node.ts | 33 +- .../envoy/config/core/v3/QueryParameter.ts | 30 + .../config/core/v3/QuicProtocolOptions.ts | 69 ++ .../envoy/config/core/v3/RetryPolicy.ts | 4 +- .../core/v3/RuntimeFractionalPercent.ts | 4 +- .../core/v3/SchemeHeaderTransformation.ts | 24 + .../envoy/config/core/v3/SelfConfigSource.ts | 4 +- .../envoy/config/core/v3/SocketAddress.ts | 20 +- .../core/v3/SubstitutionFormatString.ts | 2 + .../envoy/config/core/v3/TransportSocket.ts | 4 +- .../envoy/config/core/v3/UdpSocketConfig.ts | 45 ++ .../core/v3/UpstreamHttpProtocolOptions.ts | 42 +- .../endpoint/v3/ClusterLoadAssignment.ts | 8 +- .../envoy/config/endpoint/v3/ClusterStats.ts | 4 +- .../envoy/config/endpoint/v3/Endpoint.ts | 16 +- .../envoy/config/endpoint/v3/LbEndpoint.ts | 4 +- .../endpoint/v3/LedsClusterLocalityConfig.ts | 35 + .../config/endpoint/v3/LocalityLbEndpoints.ts | 53 +- .../endpoint/v3/UpstreamLocalityStats.ts | 8 +- .../envoy/config/listener/v3/ApiListener.ts | 6 +- .../envoy/config/listener/v3/Filter.ts | 2 + .../envoy/config/listener/v3/FilterChain.ts | 10 +- .../config/listener/v3/FilterChainMatch.ts | 34 +- .../envoy/config/listener/v3/Listener.ts | 237 ++++-- .../config/listener/v3/ListenerFilter.ts | 22 +- .../v3/ListenerFilterChainMatchPredicate.ts | 4 +- .../config/listener/v3/QuicProtocolOptions.ts | 97 +++ .../config/listener/v3/UdpListenerConfig.ts | 49 +- .../envoy/config/metrics/v3/DogStatsdSink.ts | 65 ++ .../metrics/v3/HistogramBucketSettings.ts | 35 + .../envoy/config/metrics/v3/HystrixSink.ts | 61 ++ .../envoy/config/metrics/v3/StatsConfig.ts | 148 ++++ .../envoy/config/metrics/v3/StatsMatcher.ts | 47 ++ .../envoy/config/metrics/v3/StatsSink.ts | 43 ++ .../envoy/config/metrics/v3/StatsdSink.ts | 103 +++ .../envoy/config/metrics/v3/TagSpecifier.ts | 174 +++++ .../config/overload/v3/BufferFactoryConfig.ts | 50 ++ .../config/overload/v3/OverloadAction.ts | 42 ++ .../config/overload/v3/OverloadManager.ts | 44 ++ .../config/overload/v3/ResourceMonitor.ts | 33 + .../v3/ScaleTimersOverloadActionConfig.ts | 89 +++ .../envoy/config/overload/v3/ScaledTrigger.ts | 28 + .../config/overload/v3/ThresholdTrigger.ts | 18 + .../envoy/config/overload/v3/Trigger.ts | 24 + .../config/route/v3/ClusterSpecifierPlugin.ts | 23 + .../envoy/config/route/v3/CorsPolicy.ts | 8 +- .../config/route/v3/DirectResponseAction.ts | 8 +- .../envoy/config/route/v3/FilterConfig.ts | 12 +- .../envoy/config/route/v3/HeaderMatcher.ts | 43 +- .../envoy/config/route/v3/HedgePolicy.ts | 4 +- .../config/route/v3/InternalRedirectPolicy.ts | 6 +- .../config/route/v3/NonForwardingAction.ts | 14 + .../envoy/config/route/v3/RateLimit.ts | 32 +- .../envoy/config/route/v3/RedirectAction.ts | 4 +- .../envoy/config/route/v3/RetryPolicy.ts | 85 ++- .../generated/envoy/config/route/v3/Route.ts | 55 +- .../envoy/config/route/v3/RouteAction.ts | 122 ++-- .../config/route/v3/RouteConfiguration.ts | 59 +- .../envoy/config/route/v3/RouteMatch.ts | 43 +- .../route/v3/ScopedRouteConfiguration.ts | 32 +- .../envoy/config/route/v3/Tracing.ts | 4 +- .../envoy/config/route/v3/VirtualHost.ts | 32 +- .../envoy/config/route/v3/WeightedCluster.ts | 108 ++- .../envoy/config/trace/v3/Tracing.ts | 54 +- .../filters/common/fault/v3/FaultDelay.ts | 18 +- .../filters/http/fault/v3/HTTPFault.ts | 22 +- .../v3/EnvoyMobileHttpConnectionManager.ts | 29 + .../v3/HttpConnectionManager.ts | 350 +++++++-- .../http_connection_manager/v3/HttpFilter.ts | 24 +- .../http_connection_manager/v3/ScopedRds.ts | 10 + .../v3/ScopedRoutes.ts | 28 +- .../v3/CertificateProviderPluginInstance.ts | 52 ++ .../tls/v3/CertificateValidationContext.ts | 372 ++++++++++ .../transport_sockets/tls/v3/GenericSecret.ts | 17 + .../tls/v3/PrivateKeyProvider.ts | 39 + .../tls/v3/SdsSecretConfig.ts | 23 + .../transport_sockets/tls/v3/Secret.ts | 36 + .../tls/v3/TlsCertificate.ts | 127 ++++ .../transport_sockets/tls/v3/TlsParameters.ts | 211 ++++++ .../tls/v3/TlsSessionTicketKeys.ts | 61 ++ .../discovery/v3/DeltaDiscoveryRequest.ts | 4 +- .../service/discovery/v3/DiscoveryRequest.ts | 4 +- .../load_stats/v3/LoadStatsResponse.ts | 4 +- .../envoy/service/status/v3/ClientConfig.ts | 159 ++++ .../service/status/v3/ClientConfigStatus.ts | 26 + .../status/v3/ClientStatusDiscoveryService.ts | 45 ++ .../service/status/v3/ClientStatusRequest.ts | 34 + .../service/status/v3/ClientStatusResponse.ts | 17 + .../envoy/service/status/v3/ConfigStatus.ts | 30 + .../envoy/service/status/v3/PerXdsConfig.ts | 67 ++ .../envoy/type/http/v3/PathTransformation.ts | 92 +++ .../envoy/type/matcher/v3/MetadataMatcher.ts | 8 + .../envoy/type/matcher/v3/NodeMatcher.ts | 34 + .../envoy/type/matcher/v3/StringMatcher.ts | 8 +- .../envoy/type/matcher/v3/StructMatcher.ts | 153 ++++ .../envoy/type/metadata/v3/MetadataKey.ts | 4 +- .../envoy/type/metadata/v3/MetadataKind.ts | 12 +- .../envoy/type/tracing/v3/CustomTag.ts | 8 +- packages/grpc-js-xds/src/generated/fault.ts | 7 + .../generated/google/api/CustomHttpPattern.ts | 30 + .../src/generated/google/api/Http.ts | 49 ++ .../src/generated/google/api/HttpRule.ts | 680 ++++++++++++++++++ .../google/protobuf/EnumValueOptions.ts | 2 + .../generated/google/protobuf/FieldOptions.ts | 5 + .../google/protobuf/MethodOptions.ts | 3 + .../src/generated/http_connection_manager.ts | 13 + .../grpc-js-xds/src/generated/listener.ts | 15 + packages/grpc-js-xds/src/generated/lrs.ts | 19 + packages/grpc-js-xds/src/generated/route.ts | 8 + packages/grpc-js-xds/src/resolver-xds.ts | 5 + 163 files changed, 8798 insertions(+), 856 deletions(-) create mode 100644 packages/grpc-js-xds/src/generated/csds.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/QueryParameter.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/SchemeHeaderTransformation.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LedsClusterLocalityConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/NonForwardingAction.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/EnvoyMobileHttpConnectionManager.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusDiscoveryService.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusRequest.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusResponse.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/NodeMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts create mode 100644 packages/grpc-js-xds/src/generated/google/api/Http.ts create mode 100644 packages/grpc-js-xds/src/generated/google/api/HttpRule.ts diff --git a/packages/grpc-js-xds/deps/envoy-api b/packages/grpc-js-xds/deps/envoy-api index 18b54850c..20b1b5fce 160000 --- a/packages/grpc-js-xds/deps/envoy-api +++ b/packages/grpc-js-xds/deps/envoy-api @@ -1 +1 @@ -Subproject commit 18b54850c9b7ba29a4ab67cbd7ed7eab7b0bbdb2 +Subproject commit 20b1b5fcee88a20a08b71051a961181839ec7268 diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index fca6ab80d..2ead8f1e8 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/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", + "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/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", "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" }, "repository": { diff --git a/packages/grpc-js-xds/src/generated/ads.ts b/packages/grpc-js-xds/src/generated/ads.ts index f8e336131..e0e46bb25 100644 --- a/packages/grpc-js-xds/src/generated/ads.ts +++ b/packages/grpc-js-xds/src/generated/ads.ts @@ -10,6 +10,8 @@ type SubtypeConstructor any, Subtype> export interface ProtoGrpcType { envoy: { + annotations: { + } api: { v2: { DeltaDiscoveryRequest: MessageTypeDefinition @@ -72,6 +74,7 @@ export interface ProtoGrpcType { Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition RetryPolicy: MessageTypeDefinition @@ -213,5 +216,21 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + ContextParams: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index 6c2f7aa6f..78ac3bbd3 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -28,6 +28,7 @@ export interface ProtoGrpcType { v3: { Address: MessageTypeDefinition AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition ApiConfigSource: MessageTypeDefinition ApiVersion: EnumTypeDefinition AsyncDataSource: MessageTypeDefinition @@ -38,6 +39,8 @@ export interface ProtoGrpcType { ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition + DnsResolutionConfig: MessageTypeDefinition + DnsResolverOptions: MessageTypeDefinition EnvoyInternalAddress: MessageTypeDefinition EventServiceConfig: MessageTypeDefinition Extension: MessageTypeDefinition @@ -59,6 +62,8 @@ export interface ProtoGrpcType { Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -69,6 +74,7 @@ export interface ProtoGrpcType { RuntimeFractionalPercent: MessageTypeDefinition RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition @@ -86,6 +92,7 @@ export interface ProtoGrpcType { ClusterLoadAssignment: MessageTypeDefinition Endpoint: MessageTypeDefinition LbEndpoint: MessageTypeDefinition + LedsClusterLocalityConfig: MessageTypeDefinition LocalityLbEndpoints: MessageTypeDefinition } } diff --git a/packages/grpc-js-xds/src/generated/csds.ts b/packages/grpc-js-xds/src/generated/csds.ts new file mode 100644 index 000000000..58e903bc9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/csds.ts @@ -0,0 +1,393 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { ClientStatusDiscoveryServiceClient as _envoy_service_status_v3_ClientStatusDiscoveryServiceClient, ClientStatusDiscoveryServiceDefinition as _envoy_service_status_v3_ClientStatusDiscoveryServiceDefinition } from './envoy/service/status/v3/ClientStatusDiscoveryService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + envoy: { + admin: { + v3: { + BootstrapConfigDump: MessageTypeDefinition + ClientResourceStatus: EnumTypeDefinition + ClustersConfigDump: MessageTypeDefinition + ConfigDump: MessageTypeDefinition + EndpointsConfigDump: MessageTypeDefinition + ListenersConfigDump: MessageTypeDefinition + RoutesConfigDump: MessageTypeDefinition + ScopedRoutesConfigDump: MessageTypeDefinition + SecretsConfigDump: MessageTypeDefinition + UpdateFailureState: MessageTypeDefinition + } + } + annotations: { + } + config: { + accesslog: { + v3: { + AccessLog: MessageTypeDefinition + AccessLogFilter: MessageTypeDefinition + AndFilter: MessageTypeDefinition + ComparisonFilter: MessageTypeDefinition + DurationFilter: MessageTypeDefinition + ExtensionFilter: MessageTypeDefinition + GrpcStatusFilter: MessageTypeDefinition + HeaderFilter: MessageTypeDefinition + MetadataFilter: MessageTypeDefinition + NotHealthCheckFilter: MessageTypeDefinition + OrFilter: MessageTypeDefinition + ResponseFlagFilter: MessageTypeDefinition + RuntimeFilter: MessageTypeDefinition + StatusCodeFilter: MessageTypeDefinition + TraceableFilter: MessageTypeDefinition + } + } + bootstrap: { + v3: { + Admin: MessageTypeDefinition + Bootstrap: MessageTypeDefinition + ClusterManager: MessageTypeDefinition + CustomInlineHeader: MessageTypeDefinition + FatalAction: MessageTypeDefinition + LayeredRuntime: MessageTypeDefinition + Runtime: MessageTypeDefinition + RuntimeLayer: MessageTypeDefinition + Watchdog: MessageTypeDefinition + Watchdogs: MessageTypeDefinition + } + } + cluster: { + v3: { + CircuitBreakers: MessageTypeDefinition + Cluster: MessageTypeDefinition + ClusterCollection: MessageTypeDefinition + Filter: MessageTypeDefinition + LoadBalancingPolicy: MessageTypeDefinition + OutlierDetection: MessageTypeDefinition + TrackClusterStats: MessageTypeDefinition + UpstreamBindConfig: MessageTypeDefinition + UpstreamConnectionOptions: MessageTypeDefinition + } + } + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + DnsResolutionConfig: MessageTypeDefinition + DnsResolverOptions: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + EventServiceConfig: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + GrpcProtocolOptions: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HealthCheck: MessageTypeDefinition + HealthStatus: EnumTypeDefinition + Http1ProtocolOptions: MessageTypeDefinition + Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition + HttpProtocolOptions: MessageTypeDefinition + HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TcpProtocolOptions: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + UdpSocketConfig: MessageTypeDefinition + UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + endpoint: { + v3: { + ClusterLoadAssignment: MessageTypeDefinition + Endpoint: MessageTypeDefinition + LbEndpoint: MessageTypeDefinition + LedsClusterLocalityConfig: MessageTypeDefinition + LocalityLbEndpoints: MessageTypeDefinition + } + } + listener: { + v3: { + ActiveRawUdpListenerConfig: MessageTypeDefinition + ApiListener: MessageTypeDefinition + Filter: MessageTypeDefinition + FilterChain: MessageTypeDefinition + FilterChainMatch: MessageTypeDefinition + Listener: MessageTypeDefinition + ListenerCollection: MessageTypeDefinition + ListenerFilter: MessageTypeDefinition + ListenerFilterChainMatchPredicate: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + UdpListenerConfig: MessageTypeDefinition + } + } + metrics: { + v3: { + DogStatsdSink: MessageTypeDefinition + HistogramBucketSettings: MessageTypeDefinition + HystrixSink: MessageTypeDefinition + StatsConfig: MessageTypeDefinition + StatsMatcher: MessageTypeDefinition + StatsSink: MessageTypeDefinition + StatsdSink: MessageTypeDefinition + TagSpecifier: MessageTypeDefinition + } + } + overload: { + v3: { + BufferFactoryConfig: MessageTypeDefinition + OverloadAction: MessageTypeDefinition + OverloadManager: MessageTypeDefinition + ResourceMonitor: MessageTypeDefinition + ScaleTimersOverloadActionConfig: MessageTypeDefinition + ScaledTrigger: MessageTypeDefinition + ThresholdTrigger: MessageTypeDefinition + Trigger: MessageTypeDefinition + } + } + route: { + v3: { + CorsPolicy: MessageTypeDefinition + Decorator: MessageTypeDefinition + DirectResponseAction: MessageTypeDefinition + FilterAction: MessageTypeDefinition + FilterConfig: MessageTypeDefinition + HeaderMatcher: MessageTypeDefinition + HedgePolicy: MessageTypeDefinition + InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition + QueryParameterMatcher: MessageTypeDefinition + RateLimit: MessageTypeDefinition + RedirectAction: MessageTypeDefinition + RetryPolicy: MessageTypeDefinition + Route: MessageTypeDefinition + RouteAction: MessageTypeDefinition + RouteMatch: MessageTypeDefinition + Tracing: MessageTypeDefinition + VirtualCluster: MessageTypeDefinition + VirtualHost: MessageTypeDefinition + WeightedCluster: MessageTypeDefinition + } + } + trace: { + v3: { + Tracing: MessageTypeDefinition + } + } + } + extensions: { + transport_sockets: { + tls: { + v3: { + CertificateProviderPluginInstance: MessageTypeDefinition + CertificateValidationContext: MessageTypeDefinition + GenericSecret: MessageTypeDefinition + PrivateKeyProvider: MessageTypeDefinition + SdsSecretConfig: MessageTypeDefinition + Secret: MessageTypeDefinition + TlsCertificate: MessageTypeDefinition + TlsParameters: MessageTypeDefinition + TlsSessionTicketKeys: MessageTypeDefinition + } + } + } + } + service: { + status: { + v3: { + ClientConfig: MessageTypeDefinition + ClientConfigStatus: EnumTypeDefinition + /** + * CSDS is Client Status Discovery Service. It can be used to get the status of + * an xDS-compliant client from the management server's point of view. It can + * also be used to get the current xDS states directly from the client. + */ + ClientStatusDiscoveryService: SubtypeConstructor & { service: _envoy_service_status_v3_ClientStatusDiscoveryServiceDefinition } + ClientStatusRequest: MessageTypeDefinition + ClientStatusResponse: MessageTypeDefinition + ConfigStatus: EnumTypeDefinition + PerXdsConfig: MessageTypeDefinition + } + } + } + type: { + matcher: { + v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition + ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition + NodeMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + StructMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition + } + } + metadata: { + v3: { + MetadataKey: MessageTypeDefinition + MetadataKind: MessageTypeDefinition + } + } + tracing: { + v3: { + CustomTag: MessageTypeDefinition + } + } + v3: { + CodecClientType: EnumTypeDefinition + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + api: { + CustomHttpPattern: MessageTypeDefinition + Http: MessageTypeDefinition + HttpRule: MessageTypeDefinition + } + 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 + FieldSecurityAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: 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 + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + CollectionEntry: MessageTypeDefinition + ContextParams: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/endpoint.ts b/packages/grpc-js-xds/src/generated/endpoint.ts index 630bdeb62..9a87bc9a5 100644 --- a/packages/grpc-js-xds/src/generated/endpoint.ts +++ b/packages/grpc-js-xds/src/generated/endpoint.ts @@ -8,15 +8,21 @@ type SubtypeConstructor any, Subtype> export interface ProtoGrpcType { envoy: { + annotations: { + } config: { core: { v3: { Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition AsyncDataSource: MessageTypeDefinition BackoffStrategy: MessageTypeDefinition BindConfig: MessageTypeDefinition BuildVersion: MessageTypeDefinition CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition EnvoyInternalAddress: MessageTypeDefinition @@ -33,6 +39,8 @@ export interface ProtoGrpcType { Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition RetryPolicy: MessageTypeDefinition @@ -42,6 +50,7 @@ export interface ProtoGrpcType { RuntimeFractionalPercent: MessageTypeDefinition RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition @@ -55,6 +64,7 @@ export interface ProtoGrpcType { ClusterLoadAssignment: MessageTypeDefinition Endpoint: MessageTypeDefinition LbEndpoint: MessageTypeDefinition + LedsClusterLocalityConfig: MessageTypeDefinition LocalityLbEndpoints: MessageTypeDefinition } } @@ -156,5 +166,22 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts new file mode 100644 index 000000000..d47f00ef3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts @@ -0,0 +1,32 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Bootstrap as _envoy_config_bootstrap_v3_Bootstrap, Bootstrap__Output as _envoy_config_bootstrap_v3_Bootstrap__Output } from '../../../envoy/config/bootstrap/v3/Bootstrap'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; + +/** + * This message describes the bootstrap configuration that Envoy was started with. This includes + * any CLI overrides that were merged. Bootstrap configuration information can be used to recreate + * the static portions of an Envoy configuration by reusing the output as the bootstrap + * configuration for another Envoy. + */ +export interface BootstrapConfigDump { + 'bootstrap'?: (_envoy_config_bootstrap_v3_Bootstrap | null); + /** + * The timestamp when the BootstrapConfig was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +/** + * This message describes the bootstrap configuration that Envoy was started with. This includes + * any CLI overrides that were merged. Bootstrap configuration information can be used to recreate + * the static portions of an Envoy configuration by reusing the output as the bootstrap + * configuration for another Envoy. + */ +export interface BootstrapConfigDump__Output { + 'bootstrap': (_envoy_config_bootstrap_v3_Bootstrap__Output | null); + /** + * The timestamp when the BootstrapConfig was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts new file mode 100644 index 000000000..31c3a813a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +/** + * Resource status from the view of a xDS client, which tells the synchronization + * status between the xDS client and the xDS server. + */ +export enum ClientResourceStatus { + /** + * Resource status is not available/unknown. + */ + UNKNOWN = 0, + /** + * Client requested this resource but hasn't received any update from management + * server. The client will not fail requests, but will queue them until update + * arrives or the client times out waiting for the resource. + */ + REQUESTED = 1, + /** + * This resource has been requested by the client but has either not been + * delivered by the server or was previously delivered by the server and then + * subsequently removed from resources provided by the server. For more + * information, please refer to the :ref:`"Knowing When a Requested Resource + * Does Not Exist" ` section. + */ + DOES_NOT_EXIST = 2, + /** + * Client received this resource and replied with ACK. + */ + ACKED = 3, + /** + * Client received this resource and replied with NACK. + */ + NACKED = 4, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts new file mode 100644 index 000000000..ab7c528bf --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts @@ -0,0 +1,164 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * Describes a dynamically loaded cluster via the CDS API. + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the cluster was loaded. In the future, discrete per-cluster versions may be supported by + * the API. + */ + 'version_info'?: (string); + /** + * The cluster config. + */ + 'cluster'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * Describes a dynamically loaded cluster via the CDS API. + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the cluster was loaded. In the future, discrete per-cluster versions may be supported by + * the API. + */ + 'version_info': (string); + /** + * The cluster config. + */ + 'cluster': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * Describes a statically loaded cluster. + */ +export interface _envoy_admin_v3_ClustersConfigDump_StaticCluster { + /** + * The cluster config. + */ + 'cluster'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +/** + * Describes a statically loaded cluster. + */ +export interface _envoy_admin_v3_ClustersConfigDump_StaticCluster__Output { + /** + * The cluster config. + */ + 'cluster': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Cluster was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's cluster manager fills this message with all currently known clusters. Cluster + * configuration information can be used to recreate an Envoy configuration by populating all + * clusters as static clusters or by returning them in a CDS response. + */ +export interface ClustersConfigDump { + /** + * This is the :ref:`version_info ` in the + * last processed CDS discovery response. If there are only static bootstrap clusters, this field + * will be "". + */ + 'version_info'?: (string); + /** + * The statically loaded cluster configs. + */ + 'static_clusters'?: (_envoy_admin_v3_ClustersConfigDump_StaticCluster)[]; + /** + * The dynamically loaded active clusters. These are clusters that are available to service + * data plane traffic. + */ + 'dynamic_active_clusters'?: (_envoy_admin_v3_ClustersConfigDump_DynamicCluster)[]; + /** + * The dynamically loaded warming clusters. These are clusters that are currently undergoing + * warming in preparation to service data plane traffic. Note that if attempting to recreate an + * Envoy configuration from a configuration dump, the warming clusters should generally be + * discarded. + */ + 'dynamic_warming_clusters'?: (_envoy_admin_v3_ClustersConfigDump_DynamicCluster)[]; +} + +/** + * Envoy's cluster manager fills this message with all currently known clusters. Cluster + * configuration information can be used to recreate an Envoy configuration by populating all + * clusters as static clusters or by returning them in a CDS response. + */ +export interface ClustersConfigDump__Output { + /** + * This is the :ref:`version_info ` in the + * last processed CDS discovery response. If there are only static bootstrap clusters, this field + * will be "". + */ + 'version_info': (string); + /** + * The statically loaded cluster configs. + */ + 'static_clusters': (_envoy_admin_v3_ClustersConfigDump_StaticCluster__Output)[]; + /** + * The dynamically loaded active clusters. These are clusters that are available to service + * data plane traffic. + */ + 'dynamic_active_clusters': (_envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output)[]; + /** + * The dynamically loaded warming clusters. These are clusters that are currently undergoing + * warming in preparation to service data plane traffic. Note that if attempting to recreate an + * Envoy configuration from a configuration dump, the warming clusters should generally be + * discarded. + */ + 'dynamic_warming_clusters': (_envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts new file mode 100644 index 000000000..8a0ab65c2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts @@ -0,0 +1,65 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * The :ref:`/config_dump ` admin endpoint uses this wrapper + * message to maintain and serve arbitrary configuration information from any component in Envoy. + */ +export interface ConfigDump { + /** + * This list is serialized and dumped in its entirety at the + * :ref:`/config_dump ` endpoint. + * + * The following configurations are currently supported and will be dumped in the order given + * below: + * + * * *bootstrap*: :ref:`BootstrapConfigDump ` + * * *clusters*: :ref:`ClustersConfigDump ` + * * *endpoints*: :ref:`EndpointsConfigDump ` + * * *listeners*: :ref:`ListenersConfigDump ` + * * *scoped_routes*: :ref:`ScopedRoutesConfigDump ` + * * *routes*: :ref:`RoutesConfigDump ` + * * *secrets*: :ref:`SecretsConfigDump ` + * + * EDS Configuration will only be dumped by using parameter `?include_eds` + * + * You can filter output with the resource and mask query parameters. + * See :ref:`/config_dump?resource={} `, + * :ref:`/config_dump?mask={} `, + * or :ref:`/config_dump?resource={},mask={} + * ` for more information. + */ + 'configs'?: (_google_protobuf_Any)[]; +} + +/** + * The :ref:`/config_dump ` admin endpoint uses this wrapper + * message to maintain and serve arbitrary configuration information from any component in Envoy. + */ +export interface ConfigDump__Output { + /** + * This list is serialized and dumped in its entirety at the + * :ref:`/config_dump ` endpoint. + * + * The following configurations are currently supported and will be dumped in the order given + * below: + * + * * *bootstrap*: :ref:`BootstrapConfigDump ` + * * *clusters*: :ref:`ClustersConfigDump ` + * * *endpoints*: :ref:`EndpointsConfigDump ` + * * *listeners*: :ref:`ListenersConfigDump ` + * * *scoped_routes*: :ref:`ScopedRoutesConfigDump ` + * * *routes*: :ref:`RoutesConfigDump ` + * * *secrets*: :ref:`SecretsConfigDump ` + * + * EDS Configuration will only be dumped by using parameter `?include_eds` + * + * You can filter output with the resource and mask query parameters. + * See :ref:`/config_dump?resource={} `, + * :ref:`/config_dump?mask={} `, + * or :ref:`/config_dump?resource={},mask={} + * ` for more information. + */ + 'configs': (_google_protobuf_Any__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts new file mode 100644 index 000000000..d68b27e7c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts @@ -0,0 +1,126 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig { + /** + * [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the endpoint configuration was loaded. + */ + 'version_info'?: (string); + /** + * The endpoint config. + */ + 'endpoint_config'?: (_google_protobuf_Any | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig__Output { + /** + * [#not-implemented-hide:] This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the endpoint configuration was loaded. + */ + 'version_info': (string); + /** + * The endpoint config. + */ + 'endpoint_config': (_google_protobuf_Any__Output | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig { + /** + * The endpoint config. + */ + 'endpoint_config'?: (_google_protobuf_Any | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig__Output { + /** + * The endpoint config. + */ + 'endpoint_config': (_google_protobuf_Any__Output | null); + /** + * [#not-implemented-hide:] The timestamp when the Endpoint was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's admin fill this message with all currently known endpoints. Endpoint + * configuration information can be used to recreate an Envoy configuration by populating all + * endpoints as static endpoints or by returning them in an EDS response. + */ +export interface EndpointsConfigDump { + /** + * The statically loaded endpoint configs. + */ + 'static_endpoint_configs'?: (_envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig)[]; + /** + * The dynamically loaded endpoint configs. + */ + 'dynamic_endpoint_configs'?: (_envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig)[]; +} + +/** + * Envoy's admin fill this message with all currently known endpoints. Endpoint + * configuration information can be used to recreate an Envoy configuration by populating all + * endpoints as static endpoints or by returning them in an EDS response. + */ +export interface EndpointsConfigDump__Output { + /** + * The statically loaded endpoint configs. + */ + 'static_endpoint_configs': (_envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig__Output)[]; + /** + * The dynamically loaded endpoint configs. + */ + 'dynamic_endpoint_configs': (_envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts new file mode 100644 index 000000000..745abedae --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts @@ -0,0 +1,198 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * Describes a dynamically loaded listener via the LDS API. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener { + /** + * The name or unique id of this listener, pulled from the DynamicListenerState config. + */ + 'name'?: (string); + /** + * The listener state for any active listener by this name. + * These are listeners that are available to service data plane traffic. + */ + 'active_state'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState | null); + /** + * The listener state for any warming listener by this name. + * These are listeners that are currently undergoing warming in preparation to service data + * plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the warming listeners should generally be discarded. + */ + 'warming_state'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState | null); + /** + * The listener state for any draining listener by this name. + * These are listeners that are currently undergoing draining in preparation to stop servicing + * data plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the draining listeners should generally be discarded. + */ + 'draining_state'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * Describes a dynamically loaded listener via the LDS API. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener__Output { + /** + * The name or unique id of this listener, pulled from the DynamicListenerState config. + */ + 'name': (string); + /** + * The listener state for any active listener by this name. + * These are listeners that are available to service data plane traffic. + */ + 'active_state': (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output | null); + /** + * The listener state for any warming listener by this name. + * These are listeners that are currently undergoing warming in preparation to service data + * plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the warming listeners should generally be discarded. + */ + 'warming_state': (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output | null); + /** + * The listener state for any draining listener by this name. + * These are listeners that are currently undergoing draining in preparation to stop servicing + * data plane traffic. Note that if attempting to recreate an Envoy configuration from a + * configuration dump, the draining listeners should generally be discarded. + */ + 'draining_state': (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListenerState { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the listener was loaded. In the future, discrete per-listener versions may be supported + * by the API. + */ + 'version_info'?: (string); + /** + * The listener config. + */ + 'listener'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time + * that the listener was loaded. In the future, discrete per-listener versions may be supported + * by the API. + */ + 'version_info': (string); + /** + * The listener config. + */ + 'listener': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Describes a statically loaded listener. + */ +export interface _envoy_admin_v3_ListenersConfigDump_StaticListener { + /** + * The listener config. + */ + 'listener'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +/** + * Describes a statically loaded listener. + */ +export interface _envoy_admin_v3_ListenersConfigDump_StaticListener__Output { + /** + * The listener config. + */ + 'listener': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Listener was last successfully updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's listener manager fills this message with all currently known listeners. Listener + * configuration information can be used to recreate an Envoy configuration by populating all + * listeners as static listeners or by returning them in a LDS response. + */ +export interface ListenersConfigDump { + /** + * This is the :ref:`version_info ` in the + * last processed LDS discovery response. If there are only static bootstrap listeners, this field + * will be "". + */ + 'version_info'?: (string); + /** + * The statically loaded listener configs. + */ + 'static_listeners'?: (_envoy_admin_v3_ListenersConfigDump_StaticListener)[]; + /** + * State for any warming, active, or draining listeners. + */ + 'dynamic_listeners'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListener)[]; +} + +/** + * Envoy's listener manager fills this message with all currently known listeners. Listener + * configuration information can be used to recreate an Envoy configuration by populating all + * listeners as static listeners or by returning them in a LDS response. + */ +export interface ListenersConfigDump__Output { + /** + * This is the :ref:`version_info ` in the + * last processed LDS discovery response. If there are only static bootstrap listeners, this field + * will be "". + */ + 'version_info': (string); + /** + * The statically loaded listener configs. + */ + 'static_listeners': (_envoy_admin_v3_ListenersConfigDump_StaticListener__Output)[]; + /** + * State for any warming, active, or draining listeners. + */ + 'dynamic_listeners': (_envoy_admin_v3_ListenersConfigDump_DynamicListener__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts new file mode 100644 index 000000000..2a62e9b74 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts @@ -0,0 +1,130 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the route configuration was loaded. + */ + 'version_info'?: (string); + /** + * The route config. + */ + 'route_config'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig__Output { + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the route configuration was loaded. + */ + 'version_info': (string); + /** + * The route config. + */ + 'route_config': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_RoutesConfigDump_StaticRouteConfig { + /** + * The route config. + */ + 'route_config'?: (_google_protobuf_Any | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_RoutesConfigDump_StaticRouteConfig__Output { + /** + * The route config. + */ + 'route_config': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the Route was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's RDS implementation fills this message with all currently loaded routes, as described by + * their RouteConfiguration objects. Static routes that are either defined in the bootstrap configuration + * or defined inline while configuring listeners are separated from those configured dynamically via RDS. + * Route configuration information can be used to recreate an Envoy configuration by populating all routes + * as static routes or by returning them in RDS responses. + */ +export interface RoutesConfigDump { + /** + * The statically loaded route configs. + */ + 'static_route_configs'?: (_envoy_admin_v3_RoutesConfigDump_StaticRouteConfig)[]; + /** + * The dynamically loaded route configs. + */ + 'dynamic_route_configs'?: (_envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig)[]; +} + +/** + * Envoy's RDS implementation fills this message with all currently loaded routes, as described by + * their RouteConfiguration objects. Static routes that are either defined in the bootstrap configuration + * or defined inline while configuring listeners are separated from those configured dynamically via RDS. + * Route configuration information can be used to recreate an Envoy configuration by populating all routes + * as static routes or by returning them in RDS responses. + */ +export interface RoutesConfigDump__Output { + /** + * The statically loaded route configs. + */ + 'static_route_configs': (_envoy_admin_v3_RoutesConfigDump_StaticRouteConfig__Output)[]; + /** + * The dynamically loaded route configs. + */ + 'dynamic_route_configs': (_envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts new file mode 100644 index 000000000..f271635bc --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts @@ -0,0 +1,144 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs { + /** + * The name assigned to the scoped route configurations. + */ + 'name'?: (string); + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the scoped routes configuration was loaded. + */ + 'version_info'?: (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs'?: (_google_protobuf_Any)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs__Output { + /** + * The name assigned to the scoped route configurations. + */ + 'name': (string); + /** + * This is the per-resource version information. This version is currently taken from the + * :ref:`version_info ` field at the time that + * the scoped routes configuration was loaded. + */ + 'version_info': (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs': (_google_protobuf_Any__Output)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +export interface _envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs { + /** + * The name assigned to the scoped route configurations. + */ + 'name'?: (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs'?: (_google_protobuf_Any)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); +} + +export interface _envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs__Output { + /** + * The name assigned to the scoped route configurations. + */ + 'name': (string); + /** + * The scoped route configurations. + */ + 'scoped_route_configs': (_google_protobuf_Any__Output)[]; + /** + * The timestamp when the scoped route config set was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); +} + +/** + * Envoy's scoped RDS implementation fills this message with all currently loaded route + * configuration scopes (defined via ScopedRouteConfigurationsSet protos). This message lists both + * the scopes defined inline with the higher order object (i.e., the HttpConnectionManager) and the + * dynamically obtained scopes via the SRDS API. + */ +export interface ScopedRoutesConfigDump { + /** + * The statically loaded scoped route configs. + */ + 'inline_scoped_route_configs'?: (_envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs)[]; + /** + * The dynamically loaded scoped route configs. + */ + 'dynamic_scoped_route_configs'?: (_envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs)[]; +} + +/** + * Envoy's scoped RDS implementation fills this message with all currently loaded route + * configuration scopes (defined via ScopedRouteConfigurationsSet protos). This message lists both + * the scopes defined inline with the higher order object (i.e., the HttpConnectionManager) and the + * dynamically obtained scopes via the SRDS API. + */ +export interface ScopedRoutesConfigDump__Output { + /** + * The statically loaded scoped route configs. + */ + 'inline_scoped_route_configs': (_envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs__Output)[]; + /** + * The dynamically loaded scoped route configs. + */ + 'dynamic_scoped_route_configs': (_envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfigs__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts new file mode 100644 index 000000000..21921edec --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts @@ -0,0 +1,162 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * DynamicSecret contains secret information fetched via SDS. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_SecretsConfigDump_DynamicSecret { + /** + * The name assigned to the secret. + */ + 'name'?: (string); + /** + * This is the per-resource version information. + */ + 'version_info'?: (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret'?: (_google_protobuf_Any | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * DynamicSecret contains secret information fetched via SDS. + * [#next-free-field: 7] + */ +export interface _envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output { + /** + * The name assigned to the secret. + */ + 'name': (string); + /** + * This is the per-resource version information. + */ + 'version_info': (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret': (_google_protobuf_Any__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The *error_state* field contains the rejected version of this particular + * resource along with the reason and timestamp. For successfully updated or + * acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * StaticSecret specifies statically loaded secret in bootstrap. + */ +export interface _envoy_admin_v3_SecretsConfigDump_StaticSecret { + /** + * The name assigned to the secret. + */ + 'name'?: (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret'?: (_google_protobuf_Any | null); +} + +/** + * StaticSecret specifies statically loaded secret in bootstrap. + */ +export interface _envoy_admin_v3_SecretsConfigDump_StaticSecret__Output { + /** + * The name assigned to the secret. + */ + 'name': (string); + /** + * The timestamp when the secret was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * The actual secret information. + * Security sensitive information is redacted (replaced with "[redacted]") for + * private keys and passwords in TLS certificates. + */ + 'secret': (_google_protobuf_Any__Output | null); +} + +/** + * Envoys SDS implementation fills this message with all secrets fetched dynamically via SDS. + */ +export interface SecretsConfigDump { + /** + * The statically loaded secrets. + */ + 'static_secrets'?: (_envoy_admin_v3_SecretsConfigDump_StaticSecret)[]; + /** + * The dynamically loaded active secrets. These are secrets that are available to service + * clusters or listeners. + */ + 'dynamic_active_secrets'?: (_envoy_admin_v3_SecretsConfigDump_DynamicSecret)[]; + /** + * The dynamically loaded warming secrets. These are secrets that are currently undergoing + * warming in preparation to service clusters or listeners. + */ + 'dynamic_warming_secrets'?: (_envoy_admin_v3_SecretsConfigDump_DynamicSecret)[]; +} + +/** + * Envoys SDS implementation fills this message with all secrets fetched dynamically via SDS. + */ +export interface SecretsConfigDump__Output { + /** + * The statically loaded secrets. + */ + 'static_secrets': (_envoy_admin_v3_SecretsConfigDump_StaticSecret__Output)[]; + /** + * The dynamically loaded active secrets. These are secrets that are available to service + * clusters or listeners. + */ + 'dynamic_active_secrets': (_envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output)[]; + /** + * The dynamically loaded warming secrets. These are secrets that are currently undergoing + * warming in preparation to service clusters or listeners. + */ + 'dynamic_warming_secrets': (_envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts new file mode 100644 index 000000000..b98e8cd4d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts @@ -0,0 +1,46 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; + +export interface UpdateFailureState { + /** + * What the component configuration would have been if the update had succeeded. + * This field may not be populated by xDS clients due to storage overhead. + */ + 'failed_configuration'?: (_google_protobuf_Any | null); + /** + * Time of the latest failed update attempt. + */ + 'last_update_attempt'?: (_google_protobuf_Timestamp | null); + /** + * Details about the last failed update attempt. + */ + 'details'?: (string); + /** + * This is the version of the rejected resource. + * [#not-implemented-hide:] + */ + 'version_info'?: (string); +} + +export interface UpdateFailureState__Output { + /** + * What the component configuration would have been if the update had succeeded. + * This field may not be populated by xDS clients due to storage overhead. + */ + 'failed_configuration': (_google_protobuf_Any__Output | null); + /** + * Time of the latest failed update attempt. + */ + 'last_update_attempt': (_google_protobuf_Timestamp__Output | null); + /** + * Details about the last failed update attempt. + */ + 'details': (string); + /** + * This is the version of the rejected resource. + * [#not-implemented-hide:] + */ + 'version_info': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts index cca1bc2d8..5e1df8a13 100644 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts +++ b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts @@ -12,7 +12,7 @@ export interface CidrRange { */ 'address_prefix'?: (string); /** - * Length of prefix, e.g. 0, 32. + * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. */ 'prefix_len'?: (_google_protobuf_UInt32Value | null); } @@ -27,7 +27,7 @@ export interface CidrRange__Output { */ 'address_prefix': (string); /** - * Length of prefix, e.g. 0, 32. + * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. */ 'prefix_len': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts index 369f36bc2..367d8f302 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts @@ -5,12 +5,9 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ export interface AccessLog { /** - * The name of the access log implementation to instantiate. The name must - * match a statically registered access log. Current built-in loggers include: - * - * #. "envoy.access_loggers.file" - * #. "envoy.access_loggers.http_grpc" - * #. "envoy.access_loggers.tcp_grpc" + * The name of the access log extension to instantiate. + * The name must match one of the compiled in loggers. + * See the :ref:`extensions listed in typed_config below ` for the default list of available loggers. */ 'name'?: (string); /** @@ -19,27 +16,17 @@ export interface AccessLog { 'filter'?: (_envoy_config_accesslog_v3_AccessLogFilter | null); 'typed_config'?: (_google_protobuf_Any | null); /** - * Custom configuration that depends on the access log being instantiated. - * Built-in configurations include: - * - * #. "envoy.access_loggers.file": :ref:`FileAccessLog - * ` - * #. "envoy.access_loggers.http_grpc": :ref:`HttpGrpcAccessLogConfig - * ` - * #. "envoy.access_loggers.tcp_grpc": :ref:`TcpGrpcAccessLogConfig - * ` + * Custom configuration that must be set according to the access logger extension being instantiated. + * [#extension-category: envoy.access_loggers] */ 'config_type'?: "typed_config"; } export interface AccessLog__Output { /** - * The name of the access log implementation to instantiate. The name must - * match a statically registered access log. Current built-in loggers include: - * - * #. "envoy.access_loggers.file" - * #. "envoy.access_loggers.http_grpc" - * #. "envoy.access_loggers.tcp_grpc" + * The name of the access log extension to instantiate. + * The name must match one of the compiled in loggers. + * See the :ref:`extensions listed in typed_config below ` for the default list of available loggers. */ 'name': (string); /** @@ -48,15 +35,8 @@ export interface AccessLog__Output { 'filter': (_envoy_config_accesslog_v3_AccessLogFilter__Output | null); 'typed_config'?: (_google_protobuf_Any__Output | null); /** - * Custom configuration that depends on the access log being instantiated. - * Built-in configurations include: - * - * #. "envoy.access_loggers.file": :ref:`FileAccessLog - * ` - * #. "envoy.access_loggers.http_grpc": :ref:`HttpGrpcAccessLogConfig - * ` - * #. "envoy.access_loggers.tcp_grpc": :ref:`TcpGrpcAccessLogConfig - * ` + * Custom configuration that must be set according to the access logger extension being instantiated. + * [#extension-category: envoy.access_loggers] */ 'config_type': "typed_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts index e605fa1a4..83b075388 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts @@ -28,8 +28,8 @@ export interface RuntimeFilter { * randomly sample based on the runtime key value alone. * *use_independent_randomness* can be used for logging kill switches within * complex nested :ref:`AndFilter - * ` and :ref:`OrFilter - * ` blocks that are easier to + * ` and :ref:`OrFilter + * ` blocks that are easier to * reason about from a probability perspective (i.e., setting to true will * cause the filter to behave like an independent random variable when * composed within logical operator filters). @@ -63,8 +63,8 @@ export interface RuntimeFilter__Output { * randomly sample based on the runtime key value alone. * *use_independent_randomness* can be used for logging kill switches within * complex nested :ref:`AndFilter - * ` and :ref:`OrFilter - * ` blocks that are easier to + * ` and :ref:`OrFilter + * ` blocks that are easier to * reason about from a probability perspective (i.e., setting to true will * cause the filter to behave like an independent random variable when * composed within logical operator filters). diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts new file mode 100644 index 000000000..a7f3826a1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts @@ -0,0 +1,75 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; +import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; + +/** + * Administration interface :ref:`operations documentation + * `. + * [#next-free-field: 6] + */ +export interface Admin { + /** + * The path to write the access log for the administration server. If no + * access log is desired specify ‘/dev/null’. This is only required if + * :ref:`address ` is set. + * Deprecated in favor of *access_log* which offers more options. + */ + 'access_log_path'?: (string); + /** + * The cpu profiler output path for the administration server. If no profile + * path is specified, the default is ‘/var/log/envoy/envoy.prof’. + */ + 'profile_path'?: (string); + /** + * The TCP address that the administration server will listen on. + * If not specified, Envoy will not start an administration server. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; + /** + * Configuration for :ref:`access logs ` + * emitted by the administration server. + */ + 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; +} + +/** + * Administration interface :ref:`operations documentation + * `. + * [#next-free-field: 6] + */ +export interface Admin__Output { + /** + * The path to write the access log for the administration server. If no + * access log is desired specify ‘/dev/null’. This is only required if + * :ref:`address ` is set. + * Deprecated in favor of *access_log* which offers more options. + */ + 'access_log_path': (string); + /** + * The cpu profiler output path for the administration server. If no profile + * path is specified, the default is ‘/var/log/envoy/envoy.prof’. + */ + 'profile_path': (string); + /** + * The TCP address that the administration server will listen on. + * If not specified, Envoy will not start an administration server. + */ + 'address': (_envoy_config_core_v3_Address__Output | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. + */ + 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; + /** + * Configuration for :ref:`access logs ` + * emitted by the administration server. + */ + 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts new file mode 100644 index 000000000..797148675 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts @@ -0,0 +1,642 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { ClusterManager as _envoy_config_bootstrap_v3_ClusterManager, ClusterManager__Output as _envoy_config_bootstrap_v3_ClusterManager__Output } from '../../../../envoy/config/bootstrap/v3/ClusterManager'; +import type { StatsSink as _envoy_config_metrics_v3_StatsSink, StatsSink__Output as _envoy_config_metrics_v3_StatsSink__Output } from '../../../../envoy/config/metrics/v3/StatsSink'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Watchdog as _envoy_config_bootstrap_v3_Watchdog, Watchdog__Output as _envoy_config_bootstrap_v3_Watchdog__Output } from '../../../../envoy/config/bootstrap/v3/Watchdog'; +import type { Tracing as _envoy_config_trace_v3_Tracing, Tracing__Output as _envoy_config_trace_v3_Tracing__Output } from '../../../../envoy/config/trace/v3/Tracing'; +import type { Admin as _envoy_config_bootstrap_v3_Admin, Admin__Output as _envoy_config_bootstrap_v3_Admin__Output } from '../../../../envoy/config/bootstrap/v3/Admin'; +import type { StatsConfig as _envoy_config_metrics_v3_StatsConfig, StatsConfig__Output as _envoy_config_metrics_v3_StatsConfig__Output } from '../../../../envoy/config/metrics/v3/StatsConfig'; +import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; +import type { OverloadManager as _envoy_config_overload_v3_OverloadManager, OverloadManager__Output as _envoy_config_overload_v3_OverloadManager__Output } from '../../../../envoy/config/overload/v3/OverloadManager'; +import type { LayeredRuntime as _envoy_config_bootstrap_v3_LayeredRuntime, LayeredRuntime__Output as _envoy_config_bootstrap_v3_LayeredRuntime__Output } from '../../../../envoy/config/bootstrap/v3/LayeredRuntime'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; +import type { Watchdogs as _envoy_config_bootstrap_v3_Watchdogs, Watchdogs__Output as _envoy_config_bootstrap_v3_Watchdogs__Output } from '../../../../envoy/config/bootstrap/v3/Watchdogs'; +import type { FatalAction as _envoy_config_bootstrap_v3_FatalAction, FatalAction__Output as _envoy_config_bootstrap_v3_FatalAction__Output } from '../../../../envoy/config/bootstrap/v3/FatalAction'; +import type { DnsResolutionConfig as _envoy_config_core_v3_DnsResolutionConfig, DnsResolutionConfig__Output as _envoy_config_core_v3_DnsResolutionConfig__Output } from '../../../../envoy/config/core/v3/DnsResolutionConfig'; +import type { CustomInlineHeader as _envoy_config_bootstrap_v3_CustomInlineHeader, CustomInlineHeader__Output as _envoy_config_bootstrap_v3_CustomInlineHeader__Output } from '../../../../envoy/config/bootstrap/v3/CustomInlineHeader'; +import type { Listener as _envoy_config_listener_v3_Listener, Listener__Output as _envoy_config_listener_v3_Listener__Output } from '../../../../envoy/config/listener/v3/Listener'; +import type { Cluster as _envoy_config_cluster_v3_Cluster, Cluster__Output as _envoy_config_cluster_v3_Cluster__Output } from '../../../../envoy/config/cluster/v3/Cluster'; +import type { Secret as _envoy_extensions_transport_sockets_tls_v3_Secret, Secret__Output as _envoy_extensions_transport_sockets_tls_v3_Secret__Output } from '../../../../envoy/extensions/transport_sockets/tls/v3/Secret'; +import type { Long } from '@grpc/proto-loader'; + +/** + * [#next-free-field: 7] + */ +export interface _envoy_config_bootstrap_v3_Bootstrap_DynamicResources { + /** + * All :ref:`Listeners ` are provided by a single + * :ref:`LDS ` configuration source. + */ + 'lds_config'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * xdstp:// resource locator for listener collection. + * [#not-implemented-hide:] + */ + 'lds_resources_locator'?: (string); + /** + * All post-bootstrap :ref:`Cluster ` definitions are + * provided by a single :ref:`CDS ` + * configuration source. + */ + 'cds_config'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * xdstp:// resource locator for cluster collection. + * [#not-implemented-hide:] + */ + 'cds_resources_locator'?: (string); + /** + * A single :ref:`ADS ` source may be optionally + * specified. This must have :ref:`api_type + * ` :ref:`GRPC + * `. Only + * :ref:`ConfigSources ` that have + * the :ref:`ads ` field set will be + * streamed on the ADS channel. + */ + 'ads_config'?: (_envoy_config_core_v3_ApiConfigSource | null); +} + +/** + * [#next-free-field: 7] + */ +export interface _envoy_config_bootstrap_v3_Bootstrap_DynamicResources__Output { + /** + * All :ref:`Listeners ` are provided by a single + * :ref:`LDS ` configuration source. + */ + 'lds_config': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * xdstp:// resource locator for listener collection. + * [#not-implemented-hide:] + */ + 'lds_resources_locator': (string); + /** + * All post-bootstrap :ref:`Cluster ` definitions are + * provided by a single :ref:`CDS ` + * configuration source. + */ + 'cds_config': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * xdstp:// resource locator for cluster collection. + * [#not-implemented-hide:] + */ + 'cds_resources_locator': (string); + /** + * A single :ref:`ADS ` source may be optionally + * specified. This must have :ref:`api_type + * ` :ref:`GRPC + * `. Only + * :ref:`ConfigSources ` that have + * the :ref:`ads ` field set will be + * streamed on the ADS channel. + */ + 'ads_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); +} + +export interface _envoy_config_bootstrap_v3_Bootstrap_StaticResources { + /** + * Static :ref:`Listeners `. These listeners are + * available regardless of LDS configuration. + */ + 'listeners'?: (_envoy_config_listener_v3_Listener)[]; + /** + * If a network based configuration source is specified for :ref:`cds_config + * `, it's necessary + * to have some initial cluster definitions available to allow Envoy to know + * how to speak to the management server. These cluster definitions may not + * use :ref:`EDS ` (i.e. they should be static + * IP or DNS-based). + */ + 'clusters'?: (_envoy_config_cluster_v3_Cluster)[]; + /** + * These static secrets can be used by :ref:`SdsSecretConfig + * ` + */ + 'secrets'?: (_envoy_extensions_transport_sockets_tls_v3_Secret)[]; +} + +export interface _envoy_config_bootstrap_v3_Bootstrap_StaticResources__Output { + /** + * Static :ref:`Listeners `. These listeners are + * available regardless of LDS configuration. + */ + 'listeners': (_envoy_config_listener_v3_Listener__Output)[]; + /** + * If a network based configuration source is specified for :ref:`cds_config + * `, it's necessary + * to have some initial cluster definitions available to allow Envoy to know + * how to speak to the management server. These cluster definitions may not + * use :ref:`EDS ` (i.e. they should be static + * IP or DNS-based). + */ + 'clusters': (_envoy_config_cluster_v3_Cluster__Output)[]; + /** + * These static secrets can be used by :ref:`SdsSecretConfig + * ` + */ + 'secrets': (_envoy_extensions_transport_sockets_tls_v3_Secret__Output)[]; +} + +/** + * Bootstrap :ref:`configuration overview `. + * [#next-free-field: 33] + */ +export interface Bootstrap { + /** + * Node identity to present to the management server and for instance + * identification purposes (e.g. in generated headers). + */ + 'node'?: (_envoy_config_core_v3_Node | null); + /** + * Statically specified resources. + */ + 'static_resources'?: (_envoy_config_bootstrap_v3_Bootstrap_StaticResources | null); + /** + * xDS configuration sources. + */ + 'dynamic_resources'?: (_envoy_config_bootstrap_v3_Bootstrap_DynamicResources | null); + /** + * Configuration for the cluster manager which owns all upstream clusters + * within the server. + */ + 'cluster_manager'?: (_envoy_config_bootstrap_v3_ClusterManager | null); + /** + * Optional file system path to search for startup flag files. + */ + 'flags_path'?: (string); + /** + * Optional set of stats sinks. + */ + 'stats_sinks'?: (_envoy_config_metrics_v3_StatsSink)[]; + /** + * Optional duration between flushes to configured stats sinks. For + * performance reasons Envoy latches counters and only flushes counters and + * gauges at a periodic interval. If not specified the default is 5000ms (5 + * seconds). Only one of `stats_flush_interval` or `stats_flush_on_admin` + * can be set. + * Duration must be at least 1ms and at most 5 min. + */ + 'stats_flush_interval'?: (_google_protobuf_Duration | null); + /** + * Optional watchdog configuration. + * This is for a single watchdog configuration for the entire system. + * Deprecated in favor of *watchdogs* which has finer granularity. + */ + 'watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); + /** + * Configuration for an external tracing provider. + * + * .. attention:: + * This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider + * `. + */ + 'tracing'?: (_envoy_config_trace_v3_Tracing | null); + /** + * Configuration for the local administration HTTP server. + */ + 'admin'?: (_envoy_config_bootstrap_v3_Admin | null); + /** + * Configuration for internal processing of stats. + */ + 'stats_config'?: (_envoy_config_metrics_v3_StatsConfig | null); + /** + * Health discovery service config option. + * (:ref:`core.ApiConfigSource `) + */ + 'hds_config'?: (_envoy_config_core_v3_ApiConfigSource | null); + /** + * Optional overload manager configuration. + */ + 'overload_manager'?: (_envoy_config_overload_v3_OverloadManager | null); + /** + * Enable :ref:`stats for event dispatcher `, defaults to false. + * Note that this records a value for each iteration of the event loop on every thread. This + * should normally be minimal overhead, but when using + * :ref:`statsd `, it will send each observed value + * over the wire individually because the statsd protocol doesn't have any way to represent a + * histogram summary. Be aware that this can be a very large volume of data. + */ + 'enable_dispatcher_stats'?: (boolean); + /** + * Configuration for the runtime configuration provider. If not + * specified, a “null” provider will be used which will result in all defaults + * being used. + */ + 'layered_runtime'?: (_envoy_config_bootstrap_v3_LayeredRuntime | null); + /** + * Optional string which will be used in lieu of x-envoy in prefixing headers. + * + * For example, if this string is present and set to X-Foo, then x-envoy-retry-on will be + * transformed into x-foo-retry-on etc. + * + * Note this applies to the headers Envoy will generate, the headers Envoy will sanitize, and the + * headers Envoy will trust for core code and core extensions only. Be VERY careful making + * changes to this string, especially in multi-layer Envoy deployments or deployments using + * extensions which are not upstream. + */ + 'header_prefix'?: (string); + /** + * Optional proxy version which will be used to set the value of :ref:`server.version statistic + * ` if specified. Envoy will not process this value, it will be sent as is to + * :ref:`stats sinks `. + */ + 'stats_server_version_override'?: (_google_protobuf_UInt64Value | null); + /** + * Always use TCP queries instead of UDP queries for DNS lookups. + * This may be overridden on a per-cluster basis in cds_config, + * when :ref:`dns_resolvers ` and + * :ref:`use_tcp_for_dns_lookups ` are + * specified. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'use_tcp_for_dns_lookups'?: (boolean); + /** + * Specifies optional bootstrap extensions to be instantiated at startup time. + * Each item contains extension specific configuration. + * [#extension-category: envoy.bootstrap] + */ + 'bootstrap_extensions'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Configuration sources that will participate in + * xdstp:// URL authority resolution. The algorithm is as + * follows: + * 1. The authority field is taken from the xdstp:// URL, call + * this *resource_authority*. + * 2. *resource_authority* is compared against the authorities in any peer + * *ConfigSource*. The peer *ConfigSource* is the configuration source + * message which would have been used unconditionally for resolution + * with opaque resource names. If there is a match with an authority, the + * peer *ConfigSource* message is used. + * 3. *resource_authority* is compared sequentially with the authorities in + * each configuration source in *config_sources*. The first *ConfigSource* + * to match wins. + * 4. As a fallback, if no configuration source matches, then + * *default_config_source* is used. + * 5. If *default_config_source* is not specified, resolution fails. + * [#not-implemented-hide:] + */ + 'config_sources'?: (_envoy_config_core_v3_ConfigSource)[]; + /** + * Default configuration source for xdstp:// URLs if all + * other resolution fails. + * [#not-implemented-hide:] + */ + 'default_config_source'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * Optional overriding of default socket interface. The value must be the name of one of the + * socket interface factories initialized through a bootstrap extension + */ + 'default_socket_interface'?: (string); + /** + * Global map of CertificateProvider instances. These instances are referred to by name in the + * :ref:`CommonTlsContext.CertificateProviderInstance.instance_name + * ` + * field. + * [#not-implemented-hide:] + */ + 'certificate_provider_instances'?: ({[key: string]: _envoy_config_core_v3_TypedExtensionConfig}); + /** + * A list of :ref:`Node ` field names + * that will be included in the context parameters of the effective + * xdstp:// URL that is sent in a discovery request when resource + * locators are used for LDS/CDS. Any non-string field will have its JSON + * encoding set as the context parameter value, with the exception of + * metadata, which will be flattened (see example below). The supported field + * names are: + * - "cluster" + * - "id" + * - "locality.region" + * - "locality.sub_zone" + * - "locality.zone" + * - "metadata" + * - "user_agent_build_version.metadata" + * - "user_agent_build_version.version" + * - "user_agent_name" + * - "user_agent_version" + * + * The node context parameters act as a base layer dictionary for the context + * parameters (i.e. more specific resource specific context parameters will + * override). Field names will be prefixed with “udpa.node.” when included in + * context parameters. + * + * For example, if node_context_params is ``["user_agent_name", "metadata"]``, + * the implied context parameters might be:: + * + * node.user_agent_name: "envoy" + * node.metadata.foo: "{\"bar\": \"baz\"}" + * node.metadata.some: "42" + * node.metadata.thing: "\"thing\"" + * + * [#not-implemented-hide:] + */ + 'node_context_params'?: (string)[]; + /** + * Optional watchdogs configuration. + * This is used for specifying different watchdogs for the different subsystems. + * [#extension-category: envoy.guarddog_actions] + */ + 'watchdogs'?: (_envoy_config_bootstrap_v3_Watchdogs | null); + /** + * Specifies optional extensions instantiated at startup time and + * invoked during crash time on the request that caused the crash. + */ + 'fatal_actions'?: (_envoy_config_bootstrap_v3_FatalAction)[]; + /** + * Flush stats to sinks only when queried for on the admin interface. If set, + * a flush timer is not created. Only one of `stats_flush_on_admin` or + * `stats_flush_interval` can be set. + */ + 'stats_flush_on_admin'?: (boolean); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * This may be overridden on a per-cluster basis in cds_config, when + * :ref:`dns_resolution_config ` + * is specified. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config'?: (_envoy_config_core_v3_DnsResolutionConfig | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Specifies a set of headers that need to be registered as inline header. This configuration + * allows users to customize the inline headers on-demand at Envoy startup without modifying + * Envoy's source code. + * + * Note that the 'set-cookie' header cannot be registered as inline header. + */ + 'inline_headers'?: (_envoy_config_bootstrap_v3_CustomInlineHeader)[]; + 'stats_flush'?: "stats_flush_on_admin"; +} + +/** + * Bootstrap :ref:`configuration overview `. + * [#next-free-field: 33] + */ +export interface Bootstrap__Output { + /** + * Node identity to present to the management server and for instance + * identification purposes (e.g. in generated headers). + */ + 'node': (_envoy_config_core_v3_Node__Output | null); + /** + * Statically specified resources. + */ + 'static_resources': (_envoy_config_bootstrap_v3_Bootstrap_StaticResources__Output | null); + /** + * xDS configuration sources. + */ + 'dynamic_resources': (_envoy_config_bootstrap_v3_Bootstrap_DynamicResources__Output | null); + /** + * Configuration for the cluster manager which owns all upstream clusters + * within the server. + */ + 'cluster_manager': (_envoy_config_bootstrap_v3_ClusterManager__Output | null); + /** + * Optional file system path to search for startup flag files. + */ + 'flags_path': (string); + /** + * Optional set of stats sinks. + */ + 'stats_sinks': (_envoy_config_metrics_v3_StatsSink__Output)[]; + /** + * Optional duration between flushes to configured stats sinks. For + * performance reasons Envoy latches counters and only flushes counters and + * gauges at a periodic interval. If not specified the default is 5000ms (5 + * seconds). Only one of `stats_flush_interval` or `stats_flush_on_admin` + * can be set. + * Duration must be at least 1ms and at most 5 min. + */ + 'stats_flush_interval': (_google_protobuf_Duration__Output | null); + /** + * Optional watchdog configuration. + * This is for a single watchdog configuration for the entire system. + * Deprecated in favor of *watchdogs* which has finer granularity. + */ + 'watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); + /** + * Configuration for an external tracing provider. + * + * .. attention:: + * This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider + * `. + */ + 'tracing': (_envoy_config_trace_v3_Tracing__Output | null); + /** + * Configuration for the local administration HTTP server. + */ + 'admin': (_envoy_config_bootstrap_v3_Admin__Output | null); + /** + * Configuration for internal processing of stats. + */ + 'stats_config': (_envoy_config_metrics_v3_StatsConfig__Output | null); + /** + * Health discovery service config option. + * (:ref:`core.ApiConfigSource `) + */ + 'hds_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); + /** + * Optional overload manager configuration. + */ + 'overload_manager': (_envoy_config_overload_v3_OverloadManager__Output | null); + /** + * Enable :ref:`stats for event dispatcher `, defaults to false. + * Note that this records a value for each iteration of the event loop on every thread. This + * should normally be minimal overhead, but when using + * :ref:`statsd `, it will send each observed value + * over the wire individually because the statsd protocol doesn't have any way to represent a + * histogram summary. Be aware that this can be a very large volume of data. + */ + 'enable_dispatcher_stats': (boolean); + /** + * Configuration for the runtime configuration provider. If not + * specified, a “null” provider will be used which will result in all defaults + * being used. + */ + 'layered_runtime': (_envoy_config_bootstrap_v3_LayeredRuntime__Output | null); + /** + * Optional string which will be used in lieu of x-envoy in prefixing headers. + * + * For example, if this string is present and set to X-Foo, then x-envoy-retry-on will be + * transformed into x-foo-retry-on etc. + * + * Note this applies to the headers Envoy will generate, the headers Envoy will sanitize, and the + * headers Envoy will trust for core code and core extensions only. Be VERY careful making + * changes to this string, especially in multi-layer Envoy deployments or deployments using + * extensions which are not upstream. + */ + 'header_prefix': (string); + /** + * Optional proxy version which will be used to set the value of :ref:`server.version statistic + * ` if specified. Envoy will not process this value, it will be sent as is to + * :ref:`stats sinks `. + */ + 'stats_server_version_override': (_google_protobuf_UInt64Value__Output | null); + /** + * Always use TCP queries instead of UDP queries for DNS lookups. + * This may be overridden on a per-cluster basis in cds_config, + * when :ref:`dns_resolvers ` and + * :ref:`use_tcp_for_dns_lookups ` are + * specified. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. + */ + 'use_tcp_for_dns_lookups': (boolean); + /** + * Specifies optional bootstrap extensions to be instantiated at startup time. + * Each item contains extension specific configuration. + * [#extension-category: envoy.bootstrap] + */ + 'bootstrap_extensions': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Configuration sources that will participate in + * xdstp:// URL authority resolution. The algorithm is as + * follows: + * 1. The authority field is taken from the xdstp:// URL, call + * this *resource_authority*. + * 2. *resource_authority* is compared against the authorities in any peer + * *ConfigSource*. The peer *ConfigSource* is the configuration source + * message which would have been used unconditionally for resolution + * with opaque resource names. If there is a match with an authority, the + * peer *ConfigSource* message is used. + * 3. *resource_authority* is compared sequentially with the authorities in + * each configuration source in *config_sources*. The first *ConfigSource* + * to match wins. + * 4. As a fallback, if no configuration source matches, then + * *default_config_source* is used. + * 5. If *default_config_source* is not specified, resolution fails. + * [#not-implemented-hide:] + */ + 'config_sources': (_envoy_config_core_v3_ConfigSource__Output)[]; + /** + * Default configuration source for xdstp:// URLs if all + * other resolution fails. + * [#not-implemented-hide:] + */ + 'default_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * Optional overriding of default socket interface. The value must be the name of one of the + * socket interface factories initialized through a bootstrap extension + */ + 'default_socket_interface': (string); + /** + * Global map of CertificateProvider instances. These instances are referred to by name in the + * :ref:`CommonTlsContext.CertificateProviderInstance.instance_name + * ` + * field. + * [#not-implemented-hide:] + */ + 'certificate_provider_instances': ({[key: string]: _envoy_config_core_v3_TypedExtensionConfig__Output}); + /** + * A list of :ref:`Node ` field names + * that will be included in the context parameters of the effective + * xdstp:// URL that is sent in a discovery request when resource + * locators are used for LDS/CDS. Any non-string field will have its JSON + * encoding set as the context parameter value, with the exception of + * metadata, which will be flattened (see example below). The supported field + * names are: + * - "cluster" + * - "id" + * - "locality.region" + * - "locality.sub_zone" + * - "locality.zone" + * - "metadata" + * - "user_agent_build_version.metadata" + * - "user_agent_build_version.version" + * - "user_agent_name" + * - "user_agent_version" + * + * The node context parameters act as a base layer dictionary for the context + * parameters (i.e. more specific resource specific context parameters will + * override). Field names will be prefixed with “udpa.node.” when included in + * context parameters. + * + * For example, if node_context_params is ``["user_agent_name", "metadata"]``, + * the implied context parameters might be:: + * + * node.user_agent_name: "envoy" + * node.metadata.foo: "{\"bar\": \"baz\"}" + * node.metadata.some: "42" + * node.metadata.thing: "\"thing\"" + * + * [#not-implemented-hide:] + */ + 'node_context_params': (string)[]; + /** + * Optional watchdogs configuration. + * This is used for specifying different watchdogs for the different subsystems. + * [#extension-category: envoy.guarddog_actions] + */ + 'watchdogs': (_envoy_config_bootstrap_v3_Watchdogs__Output | null); + /** + * Specifies optional extensions instantiated at startup time and + * invoked during crash time on the request that caused the crash. + */ + 'fatal_actions': (_envoy_config_bootstrap_v3_FatalAction__Output)[]; + /** + * Flush stats to sinks only when queried for on the admin interface. If set, + * a flush timer is not created. Only one of `stats_flush_on_admin` or + * `stats_flush_interval` can be set. + */ + 'stats_flush_on_admin'?: (boolean); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * This may be overridden on a per-cluster basis in cds_config, when + * :ref:`dns_resolution_config ` + * is specified. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config': (_envoy_config_core_v3_DnsResolutionConfig__Output | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Specifies a set of headers that need to be registered as inline header. This configuration + * allows users to customize the inline headers on-demand at Envoy startup without modifying + * Envoy's source code. + * + * Note that the 'set-cookie' header cannot be registered as inline header. + */ + 'inline_headers': (_envoy_config_bootstrap_v3_CustomInlineHeader__Output)[]; + 'stats_flush': "stats_flush_on_admin"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts new file mode 100644 index 000000000..571b96fb7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts @@ -0,0 +1,99 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { BindConfig as _envoy_config_core_v3_BindConfig, BindConfig__Output as _envoy_config_core_v3_BindConfig__Output } from '../../../../envoy/config/core/v3/BindConfig'; +import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; +import type { EventServiceConfig as _envoy_config_core_v3_EventServiceConfig, EventServiceConfig__Output as _envoy_config_core_v3_EventServiceConfig__Output } from '../../../../envoy/config/core/v3/EventServiceConfig'; + +export interface _envoy_config_bootstrap_v3_ClusterManager_OutlierDetection { + /** + * Specifies the path to the outlier event log. + */ + 'event_log_path'?: (string); + /** + * [#not-implemented-hide:] + * The gRPC service for the outlier detection event service. + * If empty, outlier detection events won't be sent to a remote endpoint. + */ + 'event_service'?: (_envoy_config_core_v3_EventServiceConfig | null); +} + +export interface _envoy_config_bootstrap_v3_ClusterManager_OutlierDetection__Output { + /** + * Specifies the path to the outlier event log. + */ + 'event_log_path': (string); + /** + * [#not-implemented-hide:] + * The gRPC service for the outlier detection event service. + * If empty, outlier detection events won't be sent to a remote endpoint. + */ + 'event_service': (_envoy_config_core_v3_EventServiceConfig__Output | null); +} + +/** + * Cluster manager :ref:`architecture overview `. + */ +export interface ClusterManager { + /** + * Name of the local cluster (i.e., the cluster that owns the Envoy running + * this configuration). In order to enable :ref:`zone aware routing + * ` this option must be set. + * If *local_cluster_name* is defined then :ref:`clusters + * ` must be defined in the :ref:`Bootstrap + * static cluster resources + * `. This is unrelated to + * the :option:`--service-cluster` option which does not `affect zone aware + * routing `_. + */ + 'local_cluster_name'?: (string); + /** + * Optional global configuration for outlier detection. + */ + 'outlier_detection'?: (_envoy_config_bootstrap_v3_ClusterManager_OutlierDetection | null); + /** + * Optional configuration used to bind newly established upstream connections. + * This may be overridden on a per-cluster basis by upstream_bind_config in the cds_config. + */ + 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig | null); + /** + * A management server endpoint to stream load stats to via + * *StreamLoadStats*. This must have :ref:`api_type + * ` :ref:`GRPC + * `. + */ + 'load_stats_config'?: (_envoy_config_core_v3_ApiConfigSource | null); +} + +/** + * Cluster manager :ref:`architecture overview `. + */ +export interface ClusterManager__Output { + /** + * Name of the local cluster (i.e., the cluster that owns the Envoy running + * this configuration). In order to enable :ref:`zone aware routing + * ` this option must be set. + * If *local_cluster_name* is defined then :ref:`clusters + * ` must be defined in the :ref:`Bootstrap + * static cluster resources + * `. This is unrelated to + * the :option:`--service-cluster` option which does not `affect zone aware + * routing `_. + */ + 'local_cluster_name': (string); + /** + * Optional global configuration for outlier detection. + */ + 'outlier_detection': (_envoy_config_bootstrap_v3_ClusterManager_OutlierDetection__Output | null); + /** + * Optional configuration used to bind newly established upstream connections. + * This may be overridden on a per-cluster basis by upstream_bind_config in the cds_config. + */ + 'upstream_bind_config': (_envoy_config_core_v3_BindConfig__Output | null); + /** + * A management server endpoint to stream load stats to via + * *StreamLoadStats*. This must have :ref:`api_type + * ` :ref:`GRPC + * `. + */ + 'load_stats_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts new file mode 100644 index 000000000..f0e2d29aa --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts @@ -0,0 +1,85 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + + +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +export enum _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType { + REQUEST_HEADER = 0, + REQUEST_TRAILER = 1, + RESPONSE_HEADER = 2, + RESPONSE_TRAILER = 3, +} + +/** + * Used to specify the header that needs to be registered as an inline header. + * + * If request or response contain multiple headers with the same name and the header + * name is registered as an inline header. Then multiple headers will be folded + * into one, and multiple header values will be concatenated by a suitable delimiter. + * The delimiter is generally a comma. + * + * For example, if 'foo' is registered as an inline header, and the headers contains + * the following two headers: + * + * .. code-block:: text + * + * foo: bar + * foo: eep + * + * Then they will eventually be folded into: + * + * .. code-block:: text + * + * foo: bar, eep + * + * Inline headers provide O(1) search performance, but each inline header imposes + * an additional memory overhead on all instances of the corresponding type of + * HeaderMap or TrailerMap. + */ +export interface CustomInlineHeader { + /** + * The name of the header that is expected to be set as the inline header. + */ + 'inline_header_name'?: (string); + /** + * The type of the header that is expected to be set as the inline header. + */ + 'inline_header_type'?: (_envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType | keyof typeof _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType); +} + +/** + * Used to specify the header that needs to be registered as an inline header. + * + * If request or response contain multiple headers with the same name and the header + * name is registered as an inline header. Then multiple headers will be folded + * into one, and multiple header values will be concatenated by a suitable delimiter. + * The delimiter is generally a comma. + * + * For example, if 'foo' is registered as an inline header, and the headers contains + * the following two headers: + * + * .. code-block:: text + * + * foo: bar + * foo: eep + * + * Then they will eventually be folded into: + * + * .. code-block:: text + * + * foo: bar, eep + * + * Inline headers provide O(1) search performance, but each inline header imposes + * an additional memory overhead on all instances of the corresponding type of + * HeaderMap or TrailerMap. + */ +export interface CustomInlineHeader__Output { + /** + * The name of the header that is expected to be set as the inline header. + */ + 'inline_header_name': (string); + /** + * The type of the header that is expected to be set as the inline header. + */ + 'inline_header_type': (keyof typeof _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts new file mode 100644 index 000000000..236afded5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts @@ -0,0 +1,39 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Fatal actions to run while crashing. Actions can be safe (meaning they are + * async-signal safe) or unsafe. We run all safe actions before we run unsafe actions. + * If using an unsafe action that could get stuck or deadlock, it important to + * have an out of band system to terminate the process. + * + * The interface for the extension is ``Envoy::Server::Configuration::FatalAction``. + * *FatalAction* extensions live in the ``envoy.extensions.fatal_actions`` API + * namespace. + */ +export interface FatalAction { + /** + * Extension specific configuration for the action. It's expected to conform + * to the ``Envoy::Server::Configuration::FatalAction`` interface. + */ + 'config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Fatal actions to run while crashing. Actions can be safe (meaning they are + * async-signal safe) or unsafe. We run all safe actions before we run unsafe actions. + * If using an unsafe action that could get stuck or deadlock, it important to + * have an out of band system to terminate the process. + * + * The interface for the extension is ``Envoy::Server::Configuration::FatalAction``. + * *FatalAction* extensions live in the ``envoy.extensions.fatal_actions`` API + * namespace. + */ +export interface FatalAction__Output { + /** + * Extension specific configuration for the action. It's expected to conform + * to the ``Envoy::Server::Configuration::FatalAction`` interface. + */ + 'config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts new file mode 100644 index 000000000..3514d3140 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { RuntimeLayer as _envoy_config_bootstrap_v3_RuntimeLayer, RuntimeLayer__Output as _envoy_config_bootstrap_v3_RuntimeLayer__Output } from '../../../../envoy/config/bootstrap/v3/RuntimeLayer'; + +/** + * Runtime :ref:`configuration overview `. + */ +export interface LayeredRuntime { + /** + * The :ref:`layers ` of the runtime. This is ordered + * such that later layers in the list overlay earlier entries. + */ + 'layers'?: (_envoy_config_bootstrap_v3_RuntimeLayer)[]; +} + +/** + * Runtime :ref:`configuration overview `. + */ +export interface LayeredRuntime__Output { + /** + * The :ref:`layers ` of the runtime. This is ordered + * such that later layers in the list overlay earlier entries. + */ + 'layers': (_envoy_config_bootstrap_v3_RuntimeLayer__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts new file mode 100644 index 000000000..4f7713bcf --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts @@ -0,0 +1,77 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; + +/** + * Runtime :ref:`configuration overview ` (deprecated). + */ +export interface Runtime { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. Envoy + * will watch the location for changes and reload the file system tree when + * they happen. If this parameter is not set, there will be no disk based + * runtime. + */ + 'symlink_root'?: (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory'?: (string); + /** + * Specifies an optional subdirectory to load within the root directory. If + * specified and the directory exists, configuration values within this + * directory will override those found in the primary subdirectory. This is + * useful when Envoy is deployed across many different types of servers. + * Sometimes it is useful to have a per service cluster directory for runtime + * configuration. See below for exactly how the override directory is used. + */ + 'override_subdirectory'?: (string); + /** + * Static base runtime. This will be :ref:`overridden + * ` by other runtime layers, e.g. + * disk or admin. This follows the :ref:`runtime protobuf JSON representation + * encoding `. + */ + 'base'?: (_google_protobuf_Struct | null); +} + +/** + * Runtime :ref:`configuration overview ` (deprecated). + */ +export interface Runtime__Output { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. Envoy + * will watch the location for changes and reload the file system tree when + * they happen. If this parameter is not set, there will be no disk based + * runtime. + */ + 'symlink_root': (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory': (string); + /** + * Specifies an optional subdirectory to load within the root directory. If + * specified and the directory exists, configuration values within this + * directory will override those found in the primary subdirectory. This is + * useful when Envoy is deployed across many different types of servers. + * Sometimes it is useful to have a per service cluster directory for runtime + * configuration. See below for exactly how the override directory is used. + */ + 'override_subdirectory': (string); + /** + * Static base runtime. This will be :ref:`overridden + * ` by other runtime layers, e.g. + * disk or admin. This follows the :ref:`runtime protobuf JSON representation + * encoding `. + */ + 'base': (_google_protobuf_Struct__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts new file mode 100644 index 000000000..b072bfa7d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts @@ -0,0 +1,142 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; + +/** + * :ref:`Admin console runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer { +} + +/** + * :ref:`Admin console runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer__Output { +} + +/** + * :ref:`Disk runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. + * Envoy will watch the location for changes and reload the file system tree + * when they happen. See documentation on runtime :ref:`atomicity + * ` for further details on how reloads are + * treated. + */ + 'symlink_root'?: (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory'?: (string); + /** + * :ref:`Append ` the + * service cluster to the path under symlink root. + */ + 'append_service_cluster'?: (boolean); +} + +/** + * :ref:`Disk runtime ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer__Output { + /** + * The implementation assumes that the file system tree is accessed via a + * symbolic link. An atomic link swap is used when a new tree should be + * switched to. This parameter specifies the path to the symbolic link. + * Envoy will watch the location for changes and reload the file system tree + * when they happen. See documentation on runtime :ref:`atomicity + * ` for further details on how reloads are + * treated. + */ + 'symlink_root': (string); + /** + * Specifies the subdirectory to load within the root directory. This is + * useful if multiple systems share the same delivery mechanism. Envoy + * configuration elements can be contained in a dedicated subdirectory. + */ + 'subdirectory': (string); + /** + * :ref:`Append ` the + * service cluster to the path under symlink root. + */ + 'append_service_cluster': (boolean); +} + +/** + * :ref:`Runtime Discovery Service (RTDS) ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer { + /** + * Resource to subscribe to at *rtds_config* for the RTDS layer. + */ + 'name'?: (string); + /** + * RTDS configuration source. + */ + 'rtds_config'?: (_envoy_config_core_v3_ConfigSource | null); +} + +/** + * :ref:`Runtime Discovery Service (RTDS) ` layer. + */ +export interface _envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer__Output { + /** + * Resource to subscribe to at *rtds_config* for the RTDS layer. + */ + 'name': (string); + /** + * RTDS configuration source. + */ + 'rtds_config': (_envoy_config_core_v3_ConfigSource__Output | null); +} + +/** + * [#next-free-field: 6] + */ +export interface RuntimeLayer { + /** + * Descriptive name for the runtime layer. This is only used for the runtime + * :http:get:`/runtime` output. + */ + 'name'?: (string); + /** + * :ref:`Static runtime ` layer. + * This follows the :ref:`runtime protobuf JSON representation encoding + * `. Unlike static xDS resources, this static + * layer is overridable by later layers in the runtime virtual filesystem. + */ + 'static_layer'?: (_google_protobuf_Struct | null); + 'disk_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer | null); + 'admin_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer | null); + 'rtds_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer | null); + 'layer_specifier'?: "static_layer"|"disk_layer"|"admin_layer"|"rtds_layer"; +} + +/** + * [#next-free-field: 6] + */ +export interface RuntimeLayer__Output { + /** + * Descriptive name for the runtime layer. This is only used for the runtime + * :http:get:`/runtime` output. + */ + 'name': (string); + /** + * :ref:`Static runtime ` layer. + * This follows the :ref:`runtime protobuf JSON representation encoding + * `. Unlike static xDS resources, this static + * layer is overridable by later layers in the runtime virtual filesystem. + */ + 'static_layer'?: (_google_protobuf_Struct__Output | null); + 'disk_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer__Output | null); + 'admin_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer__Output | null); + 'rtds_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer__Output | null); + 'layer_specifier': "static_layer"|"disk_layer"|"admin_layer"|"rtds_layer"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts new file mode 100644 index 000000000..8cd743b56 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts @@ -0,0 +1,141 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +export interface _envoy_config_bootstrap_v3_Watchdog_WatchdogAction { + /** + * Extension specific configuration for the action. + */ + 'config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'event'?: (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent | keyof typeof _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent); +} + +export interface _envoy_config_bootstrap_v3_Watchdog_WatchdogAction__Output { + /** + * Extension specific configuration for the action. + */ + 'config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'event': (keyof typeof _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent); +} + +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +/** + * The events are fired in this order: KILL, MULTIKILL, MEGAMISS, MISS. + * Within an event type, actions execute in the order they are configured. + * For KILL/MULTIKILL there is a default PANIC that will run after the + * registered actions and kills the process if it wasn't already killed. + * It might be useful to specify several debug actions, and possibly an + * alternate FATAL action. + */ +export enum _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent { + UNKNOWN = 0, + KILL = 1, + MULTIKILL = 2, + MEGAMISS = 3, + MISS = 4, +} + +/** + * Envoy process watchdog configuration. When configured, this monitors for + * nonresponsive threads and kills the process after the configured thresholds. + * See the :ref:`watchdog documentation ` for more information. + * [#next-free-field: 8] + */ +export interface Watchdog { + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_miss* statistic. If not specified the default is 200ms. + */ + 'miss_timeout'?: (_google_protobuf_Duration | null); + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_mega_miss* statistic. If not specified the default is + * 1000ms. + */ + 'megamiss_timeout'?: (_google_protobuf_Duration | null); + /** + * If a watched thread has been nonresponsive for this duration, assume a + * programming error and kill the entire Envoy process. Set to 0 to disable + * kill behavior. If not specified the default is 0 (disabled). + */ + 'kill_timeout'?: (_google_protobuf_Duration | null); + /** + * If max(2, ceil(registered_threads * Fraction(*multikill_threshold*))) + * threads have been nonresponsive for at least this duration kill the entire + * Envoy process. Set to 0 to disable this behavior. If not specified the + * default is 0 (disabled). + */ + 'multikill_timeout'?: (_google_protobuf_Duration | null); + /** + * Sets the threshold for *multikill_timeout* in terms of the percentage of + * nonresponsive threads required for the *multikill_timeout*. + * If not specified the default is 0. + */ + 'multikill_threshold'?: (_envoy_type_v3_Percent | null); + /** + * Defines the maximum jitter used to adjust the *kill_timeout* if *kill_timeout* is + * enabled. Enabling this feature would help to reduce risk of synchronized + * watchdog kill events across proxies due to external triggers. Set to 0 to + * disable. If not specified the default is 0 (disabled). + */ + 'max_kill_timeout_jitter'?: (_google_protobuf_Duration | null); + /** + * Register actions that will fire on given WatchDog events. + * See *WatchDogAction* for priority of events. + */ + 'actions'?: (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction)[]; +} + +/** + * Envoy process watchdog configuration. When configured, this monitors for + * nonresponsive threads and kills the process after the configured thresholds. + * See the :ref:`watchdog documentation ` for more information. + * [#next-free-field: 8] + */ +export interface Watchdog__Output { + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_miss* statistic. If not specified the default is 200ms. + */ + 'miss_timeout': (_google_protobuf_Duration__Output | null); + /** + * The duration after which Envoy counts a nonresponsive thread in the + * *watchdog_mega_miss* statistic. If not specified the default is + * 1000ms. + */ + 'megamiss_timeout': (_google_protobuf_Duration__Output | null); + /** + * If a watched thread has been nonresponsive for this duration, assume a + * programming error and kill the entire Envoy process. Set to 0 to disable + * kill behavior. If not specified the default is 0 (disabled). + */ + 'kill_timeout': (_google_protobuf_Duration__Output | null); + /** + * If max(2, ceil(registered_threads * Fraction(*multikill_threshold*))) + * threads have been nonresponsive for at least this duration kill the entire + * Envoy process. Set to 0 to disable this behavior. If not specified the + * default is 0 (disabled). + */ + 'multikill_timeout': (_google_protobuf_Duration__Output | null); + /** + * Sets the threshold for *multikill_timeout* in terms of the percentage of + * nonresponsive threads required for the *multikill_timeout*. + * If not specified the default is 0. + */ + 'multikill_threshold': (_envoy_type_v3_Percent__Output | null); + /** + * Defines the maximum jitter used to adjust the *kill_timeout* if *kill_timeout* is + * enabled. Enabling this feature would help to reduce risk of synchronized + * watchdog kill events across proxies due to external triggers. Set to 0 to + * disable. If not specified the default is 0 (disabled). + */ + 'max_kill_timeout_jitter': (_google_protobuf_Duration__Output | null); + /** + * Register actions that will fire on given WatchDog events. + * See *WatchDogAction* for priority of events. + */ + 'actions': (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts new file mode 100644 index 000000000..b478615ea --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto + +import type { Watchdog as _envoy_config_bootstrap_v3_Watchdog, Watchdog__Output as _envoy_config_bootstrap_v3_Watchdog__Output } from '../../../../envoy/config/bootstrap/v3/Watchdog'; + +/** + * Allows you to specify different watchdog configs for different subsystems. + * This allows finer tuned policies for the watchdog. If a subsystem is omitted + * the default values for that system will be used. + */ +export interface Watchdogs { + /** + * Watchdog for the main thread. + */ + 'main_thread_watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); + /** + * Watchdog for the worker threads. + */ + 'worker_watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); +} + +/** + * Allows you to specify different watchdog configs for different subsystems. + * This allows finer tuned policies for the watchdog. If a subsystem is omitted + * the default values for that system will be used. + */ +export interface Watchdogs__Output { + /** + * Watchdog for the main thread. + */ + 'main_thread_watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); + /** + * Watchdog for the worker threads. + */ + 'worker_watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts index e64afb780..12731b056 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts @@ -42,12 +42,12 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds_RetryBudget /** * A Thresholds defines CircuitBreaker settings for a - * :ref:`RoutingPriority`. + * :ref:`RoutingPriority`. * [#next-free-field: 9] */ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { /** - * The :ref:`RoutingPriority` + * The :ref:`RoutingPriority` * the specified CircuitBreaker settings apply to. */ 'priority'?: (_envoy_config_core_v3_RoutingPriority | keyof typeof _envoy_config_core_v3_RoutingPriority); @@ -104,12 +104,12 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { /** * A Thresholds defines CircuitBreaker settings for a - * :ref:`RoutingPriority`. + * :ref:`RoutingPriority`. * [#next-free-field: 9] */ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { /** - * The :ref:`RoutingPriority` + * The :ref:`RoutingPriority` * the specified CircuitBreaker settings apply to. */ 'priority': (keyof typeof _envoy_config_core_v3_RoutingPriority); @@ -170,10 +170,10 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { */ export interface CircuitBreakers { /** - * If multiple :ref:`Thresholds` - * are defined with the same :ref:`RoutingPriority`, + * If multiple :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, * the first one in the list is used. If no Thresholds is defined for a given - * :ref:`RoutingPriority`, the default values + * :ref:`RoutingPriority`, the default values * are used. */ 'thresholds'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds)[]; @@ -185,10 +185,10 @@ export interface CircuitBreakers { */ export interface CircuitBreakers__Output { /** - * If multiple :ref:`Thresholds` - * are defined with the same :ref:`RoutingPriority`, + * If multiple :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, * the first one in the list is used. If no Thresholds is defined for a given - * :ref:`RoutingPriority`, the default values + * :ref:`RoutingPriority`, the default values * are used. */ 'thresholds': (_envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output)[]; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts index c20440704..12ec7b633 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts @@ -21,6 +21,8 @@ import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__ import type { UpstreamHttpProtocolOptions as _envoy_config_core_v3_UpstreamHttpProtocolOptions, UpstreamHttpProtocolOptions__Output as _envoy_config_core_v3_UpstreamHttpProtocolOptions__Output } from '../../../../envoy/config/core/v3/UpstreamHttpProtocolOptions'; import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; import type { TrackClusterStats as _envoy_config_cluster_v3_TrackClusterStats, TrackClusterStats__Output as _envoy_config_cluster_v3_TrackClusterStats__Output } from '../../../../envoy/config/cluster/v3/TrackClusterStats'; +import type { DnsResolutionConfig as _envoy_config_core_v3_DnsResolutionConfig, DnsResolutionConfig__Output as _envoy_config_core_v3_DnsResolutionConfig__Output } from '../../../../envoy/config/core/v3/DnsResolutionConfig'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; import type { RuntimeDouble as _envoy_config_core_v3_RuntimeDouble, RuntimeDouble__Output as _envoy_config_core_v3_RuntimeDouble__Output } from '../../../../envoy/config/core/v3/RuntimeDouble'; import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; @@ -33,7 +35,7 @@ import type { Long } from '@grpc/proto-loader'; export enum _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection { /** * Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). - * If :ref:`http2_protocol_options ` are + * If :ref:`http2_protocol_options ` are * present, HTTP2 will be used, otherwise HTTP1.1 will be used. */ USE_CONFIGURED_PROTOCOL = 0, @@ -220,6 +222,7 @@ export interface _envoy_config_cluster_v3_Cluster_CustomClusterType { /** * Cluster specific configuration which depends on the cluster being instantiated. * See the supported cluster for further documentation. + * [#extension-category: envoy.clusters] */ 'typed_config'?: (_google_protobuf_Any | null); } @@ -235,6 +238,7 @@ export interface _envoy_config_cluster_v3_Cluster_CustomClusterType__Output { /** * Cluster specific configuration which depends on the cluster being instantiated. * See the supported cluster for further documentation. + * [#extension-category: envoy.clusters] */ 'typed_config': (_google_protobuf_Any__Output | null); } @@ -284,16 +288,24 @@ export enum _envoy_config_cluster_v3_Cluster_DiscoveryType { * only perform a lookup for addresses in the IPv6 family. If AUTO is * specified, the DNS resolver will first perform a lookup for addresses in * the IPv6 family and fallback to a lookup for addresses in the IPv4 family. + * This is semantically equivalent to a non-existent V6_PREFERRED option. + * AUTO is a legacy name that is more opaque than + * necessary and will be deprecated in favor of V6_PREFERRED in a future major version of the API. + * If V4_PREFERRED is specified, the DNS resolver will first perform a lookup for addresses in the + * IPv4 family and fallback to a lookup for addresses in the IPv6 family. i.e., the callback + * target will only get v6 addresses if there were NO v4 addresses to return. * For cluster types other than - * :ref:`STRICT_DNS` and - * :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS`, * this setting is * ignored. + * [#next-major-version: deprecate AUTO in favor of a V6_PREFERRED option.] */ export enum _envoy_config_cluster_v3_Cluster_DnsLookupFamily { AUTO = 0, V4_ONLY = 1, V6_ONLY = 2, + V4_PREFERRED = 3, } /** @@ -389,8 +401,8 @@ export enum _envoy_config_cluster_v3_Cluster_LbPolicy { */ CLUSTER_PROVIDED = 6, /** - * [#not-implemented-hide:] Use the new :ref:`load_balancing_policy - * ` field to determine the LB policy. + * Use the new :ref:`load_balancing_policy + * ` field to determine the LB policy. * [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field * and instead using the new load_balancing_policy field as the one and only mechanism for * configuring this.] @@ -407,18 +419,18 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { /** * The behavior used when no endpoint subset matches the selected route's * metadata. The value defaults to - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); /** * Specifies the default subset of endpoints used during fallback if * fallback_policy is - * :ref:`DEFAULT_SUBSET`. + * :ref:`DEFAULT_SUBSET`. * Each field in default_subset is * compared to the matching LbEndpoint.Metadata under the *envoy.lb* * namespace. It is valid for no hosts to match, in which case the behavior * is the same as a fallback_policy of - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ 'default_subset'?: (_google_protobuf_Struct | null); /** @@ -484,18 +496,18 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { /** * The behavior used when no endpoint subset matches the selected route's * metadata. The value defaults to - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); /** * Specifies the default subset of endpoints used during fallback if * fallback_policy is - * :ref:`DEFAULT_SUBSET`. + * :ref:`DEFAULT_SUBSET`. * Each field in default_subset is * compared to the matching LbEndpoint.Metadata under the *envoy.lb* * namespace. It is valid for no hosts to match, in which case the behavior * is the same as a fallback_policy of - * :ref:`NO_FALLBACK`. + * :ref:`NO_FALLBACK`. */ 'default_subset': (_google_protobuf_Struct__Output | null); /** @@ -597,13 +609,13 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); /** * Subset of - * :ref:`keys` used by - * :ref:`KEYS_SUBSET` + * :ref:`keys` used by + * :ref:`KEYS_SUBSET` * fallback policy. * It has to be a non empty list if KEYS_SUBSET fallback policy is selected. * For any other fallback policy the parameter is not used and should not be set. * Only values also present in - * :ref:`keys` are allowed, but + * :ref:`keys` are allowed, but * `fallback_keys_subset` cannot be equal to `keys`. */ 'fallback_keys_subset'?: (string)[]; @@ -639,13 +651,13 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); /** * Subset of - * :ref:`keys` used by - * :ref:`KEYS_SUBSET` + * :ref:`keys` used by + * :ref:`KEYS_SUBSET` * fallback policy. * It has to be a non empty list if KEYS_SUBSET fallback policy is selected. * For any other fallback policy the parameter is not used and should not be set. * Only values also present in - * :ref:`keys` are allowed, but + * :ref:`keys` are allowed, but * `fallback_keys_subset` cannot be equal to `keys`. */ 'fallback_keys_subset': (string)[]; @@ -678,7 +690,7 @@ export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbS /** * If KEYS_SUBSET is selected, subset selector matching is performed again with metadata * keys reduced to - * :ref:`fallback_keys_subset`. + * :ref:`fallback_keys_subset`. * It allows for a fallback to a different, less specific selector if some of the keys of * the selector are considered optional. */ @@ -720,6 +732,11 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig { * This setting only takes effect if all host weights are not equal. */ 'active_request_bias'?: (_envoy_config_core_v3_RuntimeDouble | null); + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config'?: (_envoy_config_cluster_v3_Cluster_SlowStartConfig | null); } /** @@ -757,6 +774,11 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output { * This setting only takes effect if all host weights are not equal. */ 'active_request_bias': (_envoy_config_core_v3_RuntimeDouble__Output | null); + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config': (_envoy_config_cluster_v3_Cluster_SlowStartConfig__Output | null); } /** @@ -782,7 +804,7 @@ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig { * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same * upstream as it was before. Increasing the table size reduces the amount of disruption. - * The table size must be prime number. If it is not specified, the default is 65537. + * The table size must be prime number limited to 5000011. If it is not specified, the default is 65537. */ 'table_size'?: (_google_protobuf_UInt64Value | null); } @@ -796,7 +818,7 @@ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output { * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same * upstream as it was before. Increasing the table size reduces the amount of disruption. - * The table size must be prime number. If it is not specified, the default is 65537. + * The table size must be prime number limited to 5000011. If it is not specified, the default is 65537. */ 'table_size': (_google_protobuf_UInt64Value__Output | null); } @@ -963,14 +985,14 @@ export interface _envoy_config_cluster_v3_Cluster_RefreshRate { /** * Specifies the base interval between refreshes. This parameter is required and must be greater * than zero and less than - * :ref:`max_interval `. + * :ref:`max_interval `. */ 'base_interval'?: (_google_protobuf_Duration | null); /** * Specifies the maximum interval between refreshes. This parameter is optional, but must be * greater than or equal to the - * :ref:`base_interval ` if set. The default - * is 10 times the :ref:`base_interval `. + * :ref:`base_interval ` if set. The default + * is 10 times the :ref:`base_interval `. */ 'max_interval'?: (_google_protobuf_Duration | null); } @@ -979,14 +1001,14 @@ export interface _envoy_config_cluster_v3_Cluster_RefreshRate__Output { /** * Specifies the base interval between refreshes. This parameter is required and must be greater * than zero and less than - * :ref:`max_interval `. + * :ref:`max_interval `. */ 'base_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the maximum interval between refreshes. This parameter is optional, but must be * greater than or equal to the - * :ref:`base_interval ` if set. The default - * is 10 times the :ref:`base_interval `. + * :ref:`base_interval ` if set. The default + * is 10 times the :ref:`base_interval `. */ 'max_interval': (_google_protobuf_Duration__Output | null); } @@ -1000,18 +1022,18 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig { * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each * provided host) the better the request distribution will reflect the desired weights. Defaults * to 1024 entries, and limited to 8M entries. See also - * :ref:`maximum_ring_size`. + * :ref:`maximum_ring_size`. */ 'minimum_ring_size'?: (_google_protobuf_UInt64Value | null); /** * The hash function used to hash hosts onto the ketama ring. The value defaults to - * :ref:`XX_HASH`. + * :ref:`XX_HASH`. */ 'hash_function'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction | keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); /** * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered * to further constrain resource use. See also - * :ref:`minimum_ring_size`. + * :ref:`minimum_ring_size`. */ 'maximum_ring_size'?: (_google_protobuf_UInt64Value | null); } @@ -1025,22 +1047,98 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output { * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each * provided host) the better the request distribution will reflect the desired weights. Defaults * to 1024 entries, and limited to 8M entries. See also - * :ref:`maximum_ring_size`. + * :ref:`maximum_ring_size`. */ 'minimum_ring_size': (_google_protobuf_UInt64Value__Output | null); /** * The hash function used to hash hosts onto the ketama ring. The value defaults to - * :ref:`XX_HASH`. + * :ref:`XX_HASH`. */ 'hash_function': (keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); /** * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered * to further constrain resource use. See also - * :ref:`minimum_ring_size`. + * :ref:`minimum_ring_size`. */ 'maximum_ring_size': (_google_protobuf_UInt64Value__Output | null); } +/** + * Specific configuration for the RoundRobin load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_RoundRobinLbConfig { + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config'?: (_envoy_config_cluster_v3_Cluster_SlowStartConfig | null); +} + +/** + * Specific configuration for the RoundRobin load balancing policy. + */ +export interface _envoy_config_cluster_v3_Cluster_RoundRobinLbConfig__Output { + /** + * Configuration for slow start mode. + * If this configuration is not set, slow start will not be not enabled. + */ + 'slow_start_config': (_envoy_config_cluster_v3_Cluster_SlowStartConfig__Output | null); +} + +/** + * Configuration for :ref:`slow start mode `. + */ +export interface _envoy_config_cluster_v3_Cluster_SlowStartConfig { + /** + * Represents the size of slow start window. + * If set, the newly created host remains in slow start mode starting from its creation time + * for the duration of slow start window. + */ + 'slow_start_window'?: (_google_protobuf_Duration | null); + /** + * This parameter controls the speed of traffic increase over the slow start window. Defaults to 1.0, + * so that endpoint would get linearly increasing amount of traffic. + * When increasing the value for this parameter, the speed of traffic ramp-up increases non-linearly. + * The value of aggression parameter should be greater than 0.0. + * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. + * + * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: + * `new_weight = weight * time_factor ^ (1 / aggression)`, + * where `time_factor=(time_since_start_seconds / slow_start_time_seconds)`. + * + * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. + * Once host exits slow start, time_factor and aggression no longer affect its weight. + */ + 'aggression'?: (_envoy_config_core_v3_RuntimeDouble | null); +} + +/** + * Configuration for :ref:`slow start mode `. + */ +export interface _envoy_config_cluster_v3_Cluster_SlowStartConfig__Output { + /** + * Represents the size of slow start window. + * If set, the newly created host remains in slow start mode starting from its creation time + * for the duration of slow start window. + */ + 'slow_start_window': (_google_protobuf_Duration__Output | null); + /** + * This parameter controls the speed of traffic increase over the slow start window. Defaults to 1.0, + * so that endpoint would get linearly increasing amount of traffic. + * When increasing the value for this parameter, the speed of traffic ramp-up increases non-linearly. + * The value of aggression parameter should be greater than 0.0. + * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. + * + * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: + * `new_weight = weight * time_factor ^ (1 / aggression)`, + * where `time_factor=(time_since_start_seconds / slow_start_time_seconds)`. + * + * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. + * Once host exits slow start, time_factor and aggression no longer affect its weight. + */ + 'aggression': (_envoy_config_core_v3_RuntimeDouble__Output | null); +} + /** * TransportSocketMatch specifies what transport socket config will be used * when the match conditions are satisfied. @@ -1060,6 +1158,7 @@ export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch { 'match'?: (_google_protobuf_Struct | null); /** * The configuration of the transport socket. + * [#extension-category: envoy.transport_sockets.upstream] */ 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); } @@ -1083,6 +1182,7 @@ export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output { 'match': (_google_protobuf_Struct__Output | null); /** * The configuration of the transport socket. + * [#extension-category: envoy.transport_sockets.upstream] */ 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); } @@ -1147,14 +1247,14 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ZoneAwareLbConf /** * Configuration for a single upstream cluster. - * [#next-free-field: 53] + * [#next-free-field: 57] */ export interface Cluster { /** * Supplies the name of the cluster which must be unique across all clusters. * The cluster name is used when emitting * :ref:`statistics ` if :ref:`alt_stat_name - * ` is not provided. + * ` is not provided. * Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. */ 'name'?: (string); @@ -1169,6 +1269,7 @@ export interface Cluster { 'eds_cluster_config'?: (_envoy_config_cluster_v3_Cluster_EdsClusterConfig | null); /** * The timeout for new network connections to hosts in the cluster. + * If not set, a default value of 5s will be used. */ 'connect_timeout'?: (_google_protobuf_Duration | null); /** @@ -1179,7 +1280,6 @@ export interface Cluster { /** * The :ref:`load balancer type ` to use * when picking a host in the cluster. - * [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] */ 'lb_policy'?: (_envoy_config_cluster_v3_Cluster_LbPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); /** @@ -1194,6 +1294,9 @@ export interface Cluster { * is respected by both the HTTP/1.1 and HTTP/2 connection pool * implementations. If not specified, there is no limit. Setting this * parameter to 1 will effectively disable keep alive. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`max_requests_per_connection ` field. */ 'max_requests_per_connection'?: (_google_protobuf_UInt32Value | null); /** @@ -1202,12 +1305,12 @@ export interface Cluster { 'circuit_breakers'?: (_envoy_config_cluster_v3_CircuitBreakers | null); /** * Additional options when handling HTTP1 requests. - * This has been deprecated in favor of http_protocol_options fields in the in the - * :ref:`http_protocol_options ` message. + * This has been deprecated in favor of http_protocol_options fields in the + * :ref:`http_protocol_options ` message. * http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions | null); @@ -1218,47 +1321,49 @@ export interface Cluster { * supports prior knowledge for upstream connections. Even if TLS is used * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 * connections to happen over plain text. - * This has been deprecated in favor of http2_protocol_options fields in the in the - * :ref:`http_protocol_options ` + * This has been deprecated in favor of http2_protocol_options fields in the + * :ref:`http_protocol_options ` * message. http2_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); /** * If the DNS refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used as the cluster’s DNS refresh * rate. The value configured must be at least 1ms. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. */ 'dns_refresh_rate'?: (_google_protobuf_Duration | null); /** * The DNS IP address resolution policy. If this setting is not specified, the * value defaults to - * :ref:`AUTO`. + * :ref:`AUTO`. */ 'dns_lookup_family'?: (_envoy_config_cluster_v3_Cluster_DnsLookupFamily | keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); /** * If DNS resolvers are specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used to specify the cluster’s dns resolvers. * If this setting is not specified, the value defaults to the default * resolver, which uses /etc/resolv.conf for configuration. For cluster types * other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. * Setting this value causes failure if the * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during * server startup. Apple's API only allows overriding DNS resolvers via system settings. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. */ 'dns_resolvers'?: (_envoy_config_core_v3_Address)[]; /** @@ -1269,7 +1374,7 @@ export interface Cluster { 'outlier_detection'?: (_envoy_config_cluster_v3_OutlierDetection | null); /** * The interval for removing stale hosts from a cluster type - * :ref:`ORIGINAL_DST`. + * :ref:`ORIGINAL_DST`. * Hosts are considered stale if they have not been used * as upstream destinations during this interval. New hosts are added * to original destination clusters on demand as new connections are @@ -1279,7 +1384,7 @@ export interface Cluster { * them remain open, saving the latency that would otherwise be spent * on opening new connections. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`ORIGINAL_DST` + * :ref:`ORIGINAL_DST` * this setting is ignored. */ 'cleanup_interval'?: (_google_protobuf_Duration | null); @@ -1299,8 +1404,8 @@ export interface Cluster { 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig | null); /** * Optional custom transport socket implementation to use for upstream connections. - * To setup TLS, set a transport socket with name `tls` and - * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`UpstreamTlsContexts ` in the `typed_config`. * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ @@ -1317,9 +1422,9 @@ export interface Cluster { * Determines how Envoy selects the protocol used to speak to upstream hosts. * This has been deprecated in favor of setting explicit protocol selection * in the :ref:`http_protocol_options - * ` message. + * ` message. * http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. + * :ref:`extension_protocol_options`. */ 'protocol_selection'?: (_envoy_config_cluster_v3_Cluster_ClusterProtocolSelection | keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); /** @@ -1327,22 +1432,27 @@ export interface Cluster { */ 'common_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig | null); /** - * An optional alternative to the cluster name to be used while emitting stats. - * Any ``:`` in the name will be converted to ``_`` when emitting statistics. This should not be - * confused with :ref:`Router Filter Header - * `. + * An optional alternative to the cluster name to be used for observability. This name is used + * emitting stats for the cluster and access logging the cluster name. This will appear as + * additional information in configuration dumps of a cluster's current status as + * :ref:`observability_name ` + * and as an additional tag "upstream_cluster.name" while tracing. Note: access logging using + * this field is presently enabled with runtime feature + * `envoy.reloadable_features.use_observable_cluster_name`. Any ``:`` in the name will be + * converted to ``_`` when emitting statistics. This should not be confused with :ref:`Router + * Filter Header `. */ 'alt_stat_name'?: (string); /** * Additional options when handling HTTP requests upstream. These options will be applicable to * both HTTP1 and HTTP2 requests. * This has been deprecated in favor of - * :ref:`common_http_protocol_options ` - * in the :ref:`http_protocol_options ` message. + * :ref:`common_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. * common_http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions | null); @@ -1374,15 +1484,15 @@ export interface Cluster { 'ignore_health_on_host_removal'?: (boolean); /** * Setting this is required for specifying members of - * :ref:`STATIC`, - * :ref:`STRICT_DNS` - * or :ref:`LOGICAL_DNS` clusters. + * :ref:`STATIC`, + * :ref:`STRICT_DNS` + * or :ref:`LOGICAL_DNS` clusters. * This field supersedes the *hosts* field in the v2 API. * * .. attention:: * * Setting this allows non-EDS cluster types to contain embedded EDS equivalent - * :ref:`endpoint assignments`. + * :ref:`endpoint assignments`. */ 'load_assignment'?: (_envoy_config_endpoint_v3_ClusterLoadAssignment | null); /** @@ -1418,9 +1528,9 @@ export interface Cluster { */ 'filters'?: (_envoy_config_cluster_v3_Filter)[]; /** - * [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - * :ref:`lb_policy` field has the value - * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + * New mechanism for LB policy configuration. Used only if the + * :ref:`lb_policy` field has the value + * :ref:`LOAD_BALANCING_POLICY_CONFIG`. */ 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy | null); /** @@ -1443,9 +1553,9 @@ export interface Cluster { /** * Configuration to use different transport sockets for different endpoints. * The entry of *envoy.transport_socket_match* in the - * :ref:`LbEndpoint.Metadata ` + * :ref:`LbEndpoint.Metadata ` * is used to match against the transport sockets as they appear in the list. The first - * :ref:`match ` is used. + * :ref:`match ` is used. * For example, with the following match * * .. code-block:: yaml @@ -1465,7 +1575,7 @@ export interface Cluster { * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. * - * If a :ref:`socket match ` with empty match + * If a :ref:`socket match ` with empty match * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" * socket match in case above. * @@ -1487,40 +1597,41 @@ export interface Cluster { * * This field can be used to specify custom transport socket configurations for health * checks by adding matching key/value pairs in a health check's - * :ref:`transport socket match criteria ` field. + * :ref:`transport socket match criteria ` field. * * [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] */ 'transport_socket_matches'?: (_envoy_config_cluster_v3_Cluster_TransportSocketMatch)[]; /** * If the DNS failure refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is * not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - * other than :ref:`STRICT_DNS` and - * :ref:`LOGICAL_DNS` this setting is + * other than :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS` this setting is * ignored. */ 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate | null); /** - * [#next-major-version: Reconcile DNS options in a single message.] * Always use TCP queries instead of UDP queries for DNS lookups. * Setting this value causes failure if the * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. */ 'use_tcp_for_dns_lookups'?: (boolean); /** * HTTP protocol options that are applied only to upstream HTTP connections. * These options apply to all HTTP versions. * This has been deprecated in favor of - * :ref:`upstream_http_protocol_options ` - * in the :ref:`http_protocol_options ` message. + * :ref:`upstream_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. * upstream_http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions | null); @@ -1534,7 +1645,7 @@ export interface Cluster { * .. attention:: * * This field has been deprecated in favor of `timeout_budgets`, part of - * :ref:`track_cluster_stats `. + * :ref:`track_cluster_stats `. */ 'track_timeout_budgets'?: (boolean); /** @@ -1555,6 +1666,7 @@ export interface Cluster { * If users desire custom connection pool or upstream behavior, for example terminating * CONNECT only if a custom filter indicates it is appropriate, the custom factories * can be registered and configured here. + * [#extension-category: envoy.upstreams] */ 'upstream_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); /** @@ -1574,30 +1686,64 @@ export interface Cluster { * Optional configuration for the Maglev load balancing policy. */ 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig | null); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config'?: (_envoy_config_core_v3_DnsResolutionConfig | null); + /** + * Optional configuration for having cluster readiness block on warm-up. Currently, only applicable for + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`. + * If true, cluster readiness blocks on warm-up. If false, the cluster will complete + * initialization whether or not warm-up has completed. Defaults to true. + */ + 'wait_for_warm_on_init'?: (_google_protobuf_BoolValue | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Optional configuration for the RoundRobin load balancing policy. + */ + 'round_robin_lb_config'?: (_envoy_config_cluster_v3_Cluster_RoundRobinLbConfig | null); 'cluster_discovery_type'?: "type"|"cluster_type"; /** * Optional configuration for the load balancing algorithm selected by * LbPolicy. Currently only - * :ref:`RING_HASH`, - * :ref:`MAGLEV` and - * :ref:`LEAST_REQUEST` + * :ref:`RING_HASH`, + * :ref:`MAGLEV` and + * :ref:`LEAST_REQUEST` * has additional configuration options. * Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding * LbPolicy will generate an error at runtime. */ - 'lb_config'?: "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"; + 'lb_config'?: "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"|"round_robin_lb_config"; } /** * Configuration for a single upstream cluster. - * [#next-free-field: 53] + * [#next-free-field: 57] */ export interface Cluster__Output { /** * Supplies the name of the cluster which must be unique across all clusters. * The cluster name is used when emitting * :ref:`statistics ` if :ref:`alt_stat_name - * ` is not provided. + * ` is not provided. * Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. */ 'name': (string); @@ -1612,6 +1758,7 @@ export interface Cluster__Output { 'eds_cluster_config': (_envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output | null); /** * The timeout for new network connections to hosts in the cluster. + * If not set, a default value of 5s will be used. */ 'connect_timeout': (_google_protobuf_Duration__Output | null); /** @@ -1622,7 +1769,6 @@ export interface Cluster__Output { /** * The :ref:`load balancer type ` to use * when picking a host in the cluster. - * [#comment:TODO: Remove enum constraint :ref:`LOAD_BALANCING_POLICY_CONFIG` when implemented.] */ 'lb_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); /** @@ -1637,6 +1783,9 @@ export interface Cluster__Output { * is respected by both the HTTP/1.1 and HTTP/2 connection pool * implementations. If not specified, there is no limit. Setting this * parameter to 1 will effectively disable keep alive. + * + * .. attention:: + * This field has been deprecated in favor of the :ref:`max_requests_per_connection ` field. */ 'max_requests_per_connection': (_google_protobuf_UInt32Value__Output | null); /** @@ -1645,12 +1794,12 @@ export interface Cluster__Output { 'circuit_breakers': (_envoy_config_cluster_v3_CircuitBreakers__Output | null); /** * Additional options when handling HTTP1 requests. - * This has been deprecated in favor of http_protocol_options fields in the in the - * :ref:`http_protocol_options ` message. + * This has been deprecated in favor of http_protocol_options fields in the + * :ref:`http_protocol_options ` message. * http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'http_protocol_options': (_envoy_config_core_v3_Http1ProtocolOptions__Output | null); @@ -1661,47 +1810,49 @@ export interface Cluster__Output { * supports prior knowledge for upstream connections. Even if TLS is used * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 * connections to happen over plain text. - * This has been deprecated in favor of http2_protocol_options fields in the in the - * :ref:`http_protocol_options ` + * This has been deprecated in favor of http2_protocol_options fields in the + * :ref:`http_protocol_options ` * message. http2_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); /** * If the DNS refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used as the cluster’s DNS refresh * rate. The value configured must be at least 1ms. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. */ 'dns_refresh_rate': (_google_protobuf_Duration__Output | null); /** * The DNS IP address resolution policy. If this setting is not specified, the * value defaults to - * :ref:`AUTO`. + * :ref:`AUTO`. */ 'dns_lookup_family': (keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); /** * If DNS resolvers are specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this value is used to specify the cluster’s dns resolvers. * If this setting is not specified, the value defaults to the default * resolver, which uses /etc/resolv.conf for configuration. For cluster types * other than - * :ref:`STRICT_DNS` - * and :ref:`LOGICAL_DNS` + * :ref:`STRICT_DNS` + * and :ref:`LOGICAL_DNS` * this setting is ignored. * Setting this value causes failure if the * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during * server startup. Apple's API only allows overriding DNS resolvers via system settings. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. */ 'dns_resolvers': (_envoy_config_core_v3_Address__Output)[]; /** @@ -1712,7 +1863,7 @@ export interface Cluster__Output { 'outlier_detection': (_envoy_config_cluster_v3_OutlierDetection__Output | null); /** * The interval for removing stale hosts from a cluster type - * :ref:`ORIGINAL_DST`. + * :ref:`ORIGINAL_DST`. * Hosts are considered stale if they have not been used * as upstream destinations during this interval. New hosts are added * to original destination clusters on demand as new connections are @@ -1722,7 +1873,7 @@ export interface Cluster__Output { * them remain open, saving the latency that would otherwise be spent * on opening new connections. If this setting is not specified, the * value defaults to 5000ms. For cluster types other than - * :ref:`ORIGINAL_DST` + * :ref:`ORIGINAL_DST` * this setting is ignored. */ 'cleanup_interval': (_google_protobuf_Duration__Output | null); @@ -1742,8 +1893,8 @@ export interface Cluster__Output { 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output | null); /** * Optional custom transport socket implementation to use for upstream connections. - * To setup TLS, set a transport socket with name `tls` and - * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`UpstreamTlsContexts ` in the `typed_config`. * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ @@ -1760,9 +1911,9 @@ export interface Cluster__Output { * Determines how Envoy selects the protocol used to speak to upstream hosts. * This has been deprecated in favor of setting explicit protocol selection * in the :ref:`http_protocol_options - * ` message. + * ` message. * http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. + * :ref:`extension_protocol_options`. */ 'protocol_selection': (keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); /** @@ -1770,22 +1921,27 @@ export interface Cluster__Output { */ 'common_lb_config': (_envoy_config_cluster_v3_Cluster_CommonLbConfig__Output | null); /** - * An optional alternative to the cluster name to be used while emitting stats. - * Any ``:`` in the name will be converted to ``_`` when emitting statistics. This should not be - * confused with :ref:`Router Filter Header - * `. + * An optional alternative to the cluster name to be used for observability. This name is used + * emitting stats for the cluster and access logging the cluster name. This will appear as + * additional information in configuration dumps of a cluster's current status as + * :ref:`observability_name ` + * and as an additional tag "upstream_cluster.name" while tracing. Note: access logging using + * this field is presently enabled with runtime feature + * `envoy.reloadable_features.use_observable_cluster_name`. Any ``:`` in the name will be + * converted to ``_`` when emitting statistics. This should not be confused with :ref:`Router + * Filter Header `. */ 'alt_stat_name': (string); /** * Additional options when handling HTTP requests upstream. These options will be applicable to * both HTTP1 and HTTP2 requests. * This has been deprecated in favor of - * :ref:`common_http_protocol_options ` - * in the :ref:`http_protocol_options ` message. + * :ref:`common_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. * common_http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'common_http_protocol_options': (_envoy_config_core_v3_HttpProtocolOptions__Output | null); @@ -1817,15 +1973,15 @@ export interface Cluster__Output { 'ignore_health_on_host_removal': (boolean); /** * Setting this is required for specifying members of - * :ref:`STATIC`, - * :ref:`STRICT_DNS` - * or :ref:`LOGICAL_DNS` clusters. + * :ref:`STATIC`, + * :ref:`STRICT_DNS` + * or :ref:`LOGICAL_DNS` clusters. * This field supersedes the *hosts* field in the v2 API. * * .. attention:: * * Setting this allows non-EDS cluster types to contain embedded EDS equivalent - * :ref:`endpoint assignments`. + * :ref:`endpoint assignments`. */ 'load_assignment': (_envoy_config_endpoint_v3_ClusterLoadAssignment__Output | null); /** @@ -1861,9 +2017,9 @@ export interface Cluster__Output { */ 'filters': (_envoy_config_cluster_v3_Filter__Output)[]; /** - * [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the - * :ref:`lb_policy` field has the value - * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + * New mechanism for LB policy configuration. Used only if the + * :ref:`lb_policy` field has the value + * :ref:`LOAD_BALANCING_POLICY_CONFIG`. */ 'load_balancing_policy': (_envoy_config_cluster_v3_LoadBalancingPolicy__Output | null); /** @@ -1886,9 +2042,9 @@ export interface Cluster__Output { /** * Configuration to use different transport sockets for different endpoints. * The entry of *envoy.transport_socket_match* in the - * :ref:`LbEndpoint.Metadata ` + * :ref:`LbEndpoint.Metadata ` * is used to match against the transport sockets as they appear in the list. The first - * :ref:`match ` is used. + * :ref:`match ` is used. * For example, with the following match * * .. code-block:: yaml @@ -1908,7 +2064,7 @@ export interface Cluster__Output { * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. * - * If a :ref:`socket match ` with empty match + * If a :ref:`socket match ` with empty match * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" * socket match in case above. * @@ -1930,40 +2086,41 @@ export interface Cluster__Output { * * This field can be used to specify custom transport socket configurations for health * checks by adding matching key/value pairs in a health check's - * :ref:`transport socket match criteria ` field. + * :ref:`transport socket match criteria ` field. * * [#comment:TODO(incfly): add a detailed architecture doc on intended usage.] */ 'transport_socket_matches': (_envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output)[]; /** * If the DNS failure refresh rate is specified and the cluster type is either - * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`, + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`, * this is used as the cluster’s DNS refresh rate when requests are failing. If this setting is * not specified, the failure refresh rate defaults to the DNS refresh rate. For cluster types - * other than :ref:`STRICT_DNS` and - * :ref:`LOGICAL_DNS` this setting is + * other than :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS` this setting is * ignored. */ 'dns_failure_refresh_rate': (_envoy_config_cluster_v3_Cluster_RefreshRate__Output | null); /** - * [#next-major-version: Reconcile DNS options in a single message.] * Always use TCP queries instead of UDP queries for DNS lookups. * Setting this value causes failure if the * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during * server startup. Apple' API only uses UDP for DNS resolution. + * This field is deprecated in favor of *dns_resolution_config* + * which aggregates all of the DNS resolver configuration in a single message. */ 'use_tcp_for_dns_lookups': (boolean); /** * HTTP protocol options that are applied only to upstream HTTP connections. * These options apply to all HTTP versions. * This has been deprecated in favor of - * :ref:`upstream_http_protocol_options ` - * in the :ref:`http_protocol_options ` message. + * :ref:`upstream_http_protocol_options ` + * in the :ref:`http_protocol_options ` message. * upstream_http_protocol_options can be set via the cluster's - * :ref:`extension_protocol_options`. - * See ref:`upstream_http_protocol_options - * ` + * :ref:`extension_protocol_options`. + * See :ref:`upstream_http_protocol_options + * ` * for example usage. */ 'upstream_http_protocol_options': (_envoy_config_core_v3_UpstreamHttpProtocolOptions__Output | null); @@ -1977,7 +2134,7 @@ export interface Cluster__Output { * .. attention:: * * This field has been deprecated in favor of `timeout_budgets`, part of - * :ref:`track_cluster_stats `. + * :ref:`track_cluster_stats `. */ 'track_timeout_budgets': (boolean); /** @@ -1998,6 +2155,7 @@ export interface Cluster__Output { * If users desire custom connection pool or upstream behavior, for example terminating * CONNECT only if a custom filter indicates it is appropriate, the custom factories * can be registered and configured here. + * [#extension-category: envoy.upstreams] */ 'upstream_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); /** @@ -2017,16 +2175,50 @@ export interface Cluster__Output { * Optional configuration for the Maglev load balancing policy. */ 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output | null); + /** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + * *dns_resolution_config* will be deprecated once + * :ref:'typed_dns_resolver_config ' + * is fully supported. + */ + 'dns_resolution_config': (_envoy_config_core_v3_DnsResolutionConfig__Output | null); + /** + * Optional configuration for having cluster readiness block on warm-up. Currently, only applicable for + * :ref:`STRICT_DNS`, + * or :ref:`LOGICAL_DNS`. + * If true, cluster readiness blocks on warm-up. If false, the cluster will complete + * initialization whether or not warm-up has completed. Defaults to true. + */ + 'wait_for_warm_on_init': (_google_protobuf_BoolValue__Output | null); + /** + * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, + * or any other DNS resolver types and the related parameters. + * For example, an object of :ref:`DnsResolutionConfig ` + * can be packed into this *typed_dns_resolver_config*. This configuration will replace the + * :ref:'dns_resolution_config ' + * configuration eventually. + * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. + * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, + * this configuration is optional. + * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. + * When *typed_dns_resolver_config* is missing, the default behavior is in place. + * [#not-implemented-hide:] + */ + 'typed_dns_resolver_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Optional configuration for the RoundRobin load balancing policy. + */ + 'round_robin_lb_config'?: (_envoy_config_cluster_v3_Cluster_RoundRobinLbConfig__Output | null); 'cluster_discovery_type': "type"|"cluster_type"; /** * Optional configuration for the load balancing algorithm selected by * LbPolicy. Currently only - * :ref:`RING_HASH`, - * :ref:`MAGLEV` and - * :ref:`LEAST_REQUEST` + * :ref:`RING_HASH`, + * :ref:`MAGLEV` and + * :ref:`LEAST_REQUEST` * has additional configuration options. * Specifying ring_hash_lb_config or maglev_lb_config or least_request_lb_config without setting the corresponding * LbPolicy will generate an error at runtime. */ - 'lb_config': "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"; + 'lb_config': "ring_hash_lb_config"|"maglev_lb_config"|"original_dst_lb_config"|"least_request_lb_config"|"round_robin_lb_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts index b2ccda5e4..9d9031b68 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts @@ -5,7 +5,8 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ export interface Filter { /** * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. + * supported upstream filter. Note that Envoy's :ref:`downstream network + * filters ` are not valid upstream filters. */ 'name'?: (string); /** @@ -18,7 +19,8 @@ export interface Filter { export interface Filter__Output { /** * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. + * supported upstream filter. Note that Envoy's :ref:`downstream network + * filters ` are not valid upstream filters. */ 'name': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts index 128a6458a..78d4a6bfd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts @@ -1,25 +1,17 @@ // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy { - /** - * Required. The name of the LB policy. - */ - 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any | null); + 'typed_extension_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); } export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output { - /** - * Required. The name of the LB policy. - */ - 'name': (string); - 'typed_config': (_google_protobuf_Any__Output | null); + 'typed_extension_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); } /** - * [#not-implemented-hide:] Extensible load balancing policy configuration. + * Extensible load balancing policy configuration. * * Every LB policy defined via this mechanism will be identified via a unique name using reverse * DNS notation. If the policy needs configuration parameters, it must define a message for its @@ -49,7 +41,7 @@ export interface LoadBalancingPolicy { } /** - * [#not-implemented-hide:] Extensible load balancing policy configuration. + * Extensible load balancing policy configuration. * * Every LB policy defined via this mechanism will be identified via a unique name using reverse * DNS notation. If the policy needs configuration parameters, it must define a message for its diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts index abae2e270..789004ad4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts @@ -24,7 +24,7 @@ export interface OutlierDetection { /** * The base time that a host is ejected for. The real time is equal to the * base time multiplied by the number of times the host has been ejected and is - * capped by :ref:`max_ejection_time`. + * capped by :ref:`max_ejection_time`. * Defaults to 30000ms or 30s. */ 'base_ejection_time'?: (_google_protobuf_Duration | null); @@ -84,17 +84,17 @@ export interface OutlierDetection { /** * Determines whether to distinguish local origin failures from external errors. If set to true * the following configuration parameters are taken into account: - * :ref:`consecutive_local_origin_failure`, - * :ref:`enforcing_consecutive_local_origin_failure` + * :ref:`consecutive_local_origin_failure`, + * :ref:`enforcing_consecutive_local_origin_failure` * and - * :ref:`enforcing_local_origin_success_rate`. + * :ref:`enforcing_local_origin_success_rate`. * Defaults to false. */ 'split_external_local_origin_errors'?: (boolean); /** * The number of consecutive locally originated failures before ejection * occurs. Defaults to 5. Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value | null); @@ -103,7 +103,7 @@ export interface OutlierDetection { * is detected through consecutive locally originated failures. This setting can be * used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_consecutive_local_origin_failure'?: (_google_protobuf_UInt32Value | null); @@ -112,7 +112,7 @@ export interface OutlierDetection { * is detected through success rate statistics for locally originated errors. * This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_local_origin_success_rate'?: (_google_protobuf_UInt32Value | null); @@ -151,9 +151,9 @@ export interface OutlierDetection { */ 'failure_percentage_request_volume'?: (_google_protobuf_UInt32Value | null); /** - * The maximum time that a host is ejected for. See :ref:`base_ejection_time` - * for more information. - * Defaults to 300000ms or 300s. + * The maximum time that a host is ejected for. See :ref:`base_ejection_time` + * for more information. If not specified, the default value (300000ms or 300s) or + * :ref:`base_ejection_time` value is applied, whatever is larger. */ 'max_ejection_time'?: (_google_protobuf_Duration | null); } @@ -179,7 +179,7 @@ export interface OutlierDetection__Output { /** * The base time that a host is ejected for. The real time is equal to the * base time multiplied by the number of times the host has been ejected and is - * capped by :ref:`max_ejection_time`. + * capped by :ref:`max_ejection_time`. * Defaults to 30000ms or 30s. */ 'base_ejection_time': (_google_protobuf_Duration__Output | null); @@ -239,17 +239,17 @@ export interface OutlierDetection__Output { /** * Determines whether to distinguish local origin failures from external errors. If set to true * the following configuration parameters are taken into account: - * :ref:`consecutive_local_origin_failure`, - * :ref:`enforcing_consecutive_local_origin_failure` + * :ref:`consecutive_local_origin_failure`, + * :ref:`enforcing_consecutive_local_origin_failure` * and - * :ref:`enforcing_local_origin_success_rate`. + * :ref:`enforcing_local_origin_success_rate`. * Defaults to false. */ 'split_external_local_origin_errors': (boolean); /** * The number of consecutive locally originated failures before ejection * occurs. Defaults to 5. Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'consecutive_local_origin_failure': (_google_protobuf_UInt32Value__Output | null); @@ -258,7 +258,7 @@ export interface OutlierDetection__Output { * is detected through consecutive locally originated failures. This setting can be * used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_consecutive_local_origin_failure': (_google_protobuf_UInt32Value__Output | null); @@ -267,7 +267,7 @@ export interface OutlierDetection__Output { * is detected through success rate statistics for locally originated errors. * This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. * Parameter takes effect only when - * :ref:`split_external_local_origin_errors` + * :ref:`split_external_local_origin_errors` * is set to true. */ 'enforcing_local_origin_success_rate': (_google_protobuf_UInt32Value__Output | null); @@ -306,9 +306,9 @@ export interface OutlierDetection__Output { */ 'failure_percentage_request_volume': (_google_protobuf_UInt32Value__Output | null); /** - * The maximum time that a host is ejected for. See :ref:`base_ejection_time` - * for more information. - * Defaults to 300000ms or 300s. + * The maximum time that a host is ejected for. See :ref:`base_ejection_time` + * for more information. If not specified, the default value (300000ms or 300s) or + * :ref:`base_ejection_time` value is applied, whatever is larger. */ 'max_ejection_time': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts index 824ef93c8..428ab4b56 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AggregatedConfigSource.ts @@ -3,7 +3,7 @@ /** * Aggregated Discovery Service (ADS) options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to + * set in :ref:`ConfigSource ` can be used to * specify that ADS is to be used. */ export interface AggregatedConfigSource { @@ -11,7 +11,7 @@ export interface AggregatedConfigSource { /** * Aggregated Discovery Service (ADS) options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to + * set in :ref:`ConfigSource ` can be used to * specify that ADS is to be used. */ export interface AggregatedConfigSource__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts new file mode 100644 index 000000000..6fb167174 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts @@ -0,0 +1,72 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configures the alternate protocols cache which tracks alternate protocols that can be used to + * make an HTTP connection to an origin server. See https://tools.ietf.org/html/rfc7838 for + * HTTP Alternative Services and https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 + * for the "HTTPS" DNS resource record. + */ +export interface AlternateProtocolsCacheOptions { + /** + * The name of the cache. Multiple named caches allow independent alternate protocols cache + * configurations to operate within a single Envoy process using different configurations. All + * alternate protocols cache options with the same name *must* be equal in all fields when + * referenced from different configuration components. Configuration will fail to load if this is + * not the case. + */ + 'name'?: (string); + /** + * The maximum number of entries that the cache will hold. If not specified defaults to 1024. + * + * .. note: + * + * The implementation is approximate and enforced independently on each worker thread, thus + * it is possible for the maximum entries in the cache to go slightly above the configured + * value depending on timing. This is similar to how other circuit breakers work. + */ + 'max_entries'?: (_google_protobuf_UInt32Value | null); + /** + * Allows configuring a persistent + * :ref:`key value store ` to flush + * alternate protocols entries to disk. + * This function is currently only supported if concurrency is 1 + */ + 'key_value_store_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Configures the alternate protocols cache which tracks alternate protocols that can be used to + * make an HTTP connection to an origin server. See https://tools.ietf.org/html/rfc7838 for + * HTTP Alternative Services and https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 + * for the "HTTPS" DNS resource record. + */ +export interface AlternateProtocolsCacheOptions__Output { + /** + * The name of the cache. Multiple named caches allow independent alternate protocols cache + * configurations to operate within a single Envoy process using different configurations. All + * alternate protocols cache options with the same name *must* be equal in all fields when + * referenced from different configuration components. Configuration will fail to load if this is + * not the case. + */ + 'name': (string); + /** + * The maximum number of entries that the cache will hold. If not specified defaults to 1024. + * + * .. note: + * + * The implementation is approximate and enforced independently on each worker thread, thus + * it is possible for the maximum entries in the cache to go slightly above the configured + * value depending on timing. This is similar to how other circuit breakers work. + */ + 'max_entries': (_google_protobuf_UInt32Value__Output | null); + /** + * Allows configuring a persistent + * :ref:`key value store ` to flush + * alternate protocols entries to disk. + * This function is currently only supported if concurrency is 1 + */ + 'key_value_store_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts index b85b8d5de..1049dec0c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BackoffStrategy.ts @@ -9,15 +9,15 @@ export interface BackoffStrategy { /** * The base interval to be used for the next back off computation. It should * be greater than zero and less than or equal to :ref:`max_interval - * `. + * `. */ 'base_interval'?: (_google_protobuf_Duration | null); /** * Specifies the maximum interval between retries. This parameter is optional, * but must be greater than or equal to the :ref:`base_interval - * ` if set. The default + * ` if set. The default * is 10 times the :ref:`base_interval - * `. + * `. */ 'max_interval'?: (_google_protobuf_Duration | null); } @@ -29,15 +29,15 @@ export interface BackoffStrategy__Output { /** * The base interval to be used for the next back off computation. It should * be greater than zero and less than or equal to :ref:`max_interval - * `. + * `. */ 'base_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the maximum interval between retries. This parameter is optional, * but must be greater than or equal to the :ref:`base_interval - * ` if set. The default + * ` if set. The default * is 10 times the :ref:`base_interval - * `. + * `. */ 'max_interval': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts index db6b1f654..d3b2cc584 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts @@ -12,7 +12,7 @@ export interface BindConfig { /** * Whether to set the *IP_FREEBIND* option when creating the socket. When this * flag is set to true, allows the :ref:`source_address - * ` to be an IP address + * ` to be an IP address * that is not configured on the system running Envoy. When this flag is set * to false, the option *IP_FREEBIND* is disabled on the socket. When this * flag is not set (default), the socket is not modified, i.e. the option is @@ -34,7 +34,7 @@ export interface BindConfig__Output { /** * Whether to set the *IP_FREEBIND* option when creating the socket. When this * flag is set to true, allows the :ref:`source_address - * ` to be an IP address + * ` to be an IP address * that is not configured on the system running Envoy. When this flag is set * to false, the option *IP_FREEBIND* is disabled on the socket. When this * flag is not set (default), the socket is not modified, i.e. the option is diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts index df4ff39ff..4e01fa354 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/CidrRange.ts @@ -12,7 +12,7 @@ export interface CidrRange { */ 'address_prefix'?: (string); /** - * Length of prefix, e.g. 0, 32. + * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. */ 'prefix_len'?: (_google_protobuf_UInt32Value | null); } @@ -27,7 +27,7 @@ export interface CidrRange__Output { */ 'address_prefix': (string); /** - * Length of prefix, e.g. 0, 32. + * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. */ 'prefix_len': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts index aaf4d9564..a39c0f077 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts @@ -10,7 +10,7 @@ import type { Authority as _xds_core_v3_Authority, Authority__Output as _xds_cor /** * Configuration for :ref:`listeners `, :ref:`clusters * `, :ref:`routes - * `, :ref:`endpoints + * `, :ref:`endpoints * ` etc. may either be sourced from the * filesystem or from an xDS API source. Filesystem configs are watched with * inotify for updates. @@ -19,7 +19,7 @@ import type { Authority as _xds_core_v3_Authority, Authority__Output as _xds_cor export interface ConfigSource { /** * Path on the filesystem to source and watch for configuration updates. - * When sourcing configuration for :ref:`secret `, + * When sourcing configuration for :ref:`secret `, * the certificate and key files are also watched for updates. * * .. note:: @@ -56,7 +56,7 @@ export interface ConfigSource { * [#not-implemented-hide:] * When set, the client will access the resources from the same server it got the * ConfigSource from, although not necessarily from the same stream. This is similar to the - * :ref:`ads` field, except that the client may use a + * :ref:`ads` field, except that the client may use a * different stream to the same server. As a result, this field can be used for things * like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) * LDS to RDS on the same server without requiring the management server to know its name @@ -85,7 +85,7 @@ export interface ConfigSource { /** * Configuration for :ref:`listeners `, :ref:`clusters * `, :ref:`routes - * `, :ref:`endpoints + * `, :ref:`endpoints * ` etc. may either be sourced from the * filesystem or from an xDS API source. Filesystem configs are watched with * inotify for updates. @@ -94,7 +94,7 @@ export interface ConfigSource { export interface ConfigSource__Output { /** * Path on the filesystem to source and watch for configuration updates. - * When sourcing configuration for :ref:`secret `, + * When sourcing configuration for :ref:`secret `, * the certificate and key files are also watched for updates. * * .. note:: @@ -131,7 +131,7 @@ export interface ConfigSource__Output { * [#not-implemented-hide:] * When set, the client will access the resources from the same server it got the * ConfigSource from, although not necessarily from the same stream. This is similar to the - * :ref:`ads` field, except that the client may use a + * :ref:`ads` field, except that the client may use a * different stream to the same server. As a result, this field can be used for things * like LRS that cannot be sent on an ADS stream. It can also be used to link from (e.g.) * LDS to RDS on the same server without requiring the management server to know its name diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts new file mode 100644 index 000000000..c87be12e2 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts @@ -0,0 +1,42 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/resolver.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { DnsResolverOptions as _envoy_config_core_v3_DnsResolverOptions, DnsResolverOptions__Output as _envoy_config_core_v3_DnsResolverOptions__Output } from '../../../../envoy/config/core/v3/DnsResolverOptions'; + +/** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + */ +export interface DnsResolutionConfig { + /** + * A list of dns resolver addresses. If specified, the DNS client library will perform resolution + * via the underlying DNS resolvers. Otherwise, the default system resolvers + * (e.g., /etc/resolv.conf) will be used. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. + */ + 'resolvers'?: (_envoy_config_core_v3_Address)[]; + /** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ + 'dns_resolver_options'?: (_envoy_config_core_v3_DnsResolverOptions | null); +} + +/** + * DNS resolution configuration which includes the underlying dns resolver addresses and options. + */ +export interface DnsResolutionConfig__Output { + /** + * A list of dns resolver addresses. If specified, the DNS client library will perform resolution + * via the underlying DNS resolvers. Otherwise, the default system resolvers + * (e.g., /etc/resolv.conf) will be used. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only allows overriding DNS resolvers via system settings. + */ + 'resolvers': (_envoy_config_core_v3_Address__Output)[]; + /** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ + 'dns_resolver_options': (_envoy_config_core_v3_DnsResolverOptions__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts new file mode 100644 index 000000000..11b68b150 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/resolver.proto + + +/** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ +export interface DnsResolverOptions { + /** + * Use TCP for all DNS queries instead of the default protocol UDP. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only uses UDP for DNS resolution. + */ + 'use_tcp_for_dns_lookups'?: (boolean); + /** + * Do not use the default search domains; only query hostnames as-is or as aliases. + */ + 'no_default_search_domain'?: (boolean); +} + +/** + * Configuration of DNS resolver option flags which control the behavior of the DNS resolver. + */ +export interface DnsResolverOptions__Output { + /** + * Use TCP for all DNS queries instead of the default protocol UDP. + * Setting this value causes failure if the + * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + * server startup. Apple's API only uses UDP for DNS resolution. + */ + 'use_tcp_for_dns_lookups': (boolean); + /** + * Do not use the default search domains; only query hostnames as-is or as aliases. + */ + 'no_default_search_domain': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts index dfe6a52b4..936e433db 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts @@ -8,7 +8,7 @@ */ export interface EnvoyInternalAddress { /** - * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. */ 'server_listener_name'?: (string); 'address_name_specifier'?: "server_listener_name"; @@ -21,7 +21,7 @@ export interface EnvoyInternalAddress { */ export interface EnvoyInternalAddress__Output { /** - * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. */ 'server_listener_name'?: (string); 'address_name_specifier': "server_listener_name"; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts index 0e55d018b..0d81f7340 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts @@ -148,8 +148,8 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc { /** * The name of the upstream gRPC cluster. SSL credentials will be supplied - * in the :ref:`Cluster ` :ref:`transport_socket - * `. + * in the :ref:`Cluster ` :ref:`transport_socket + * `. */ 'cluster_name'?: (string); /** @@ -162,8 +162,8 @@ export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc { export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc__Output { /** * The name of the upstream gRPC cluster. SSL credentials will be supplied - * in the :ref:`Cluster ` :ref:`transport_socket - * `. + * in the :ref:`Cluster ` :ref:`transport_socket + * `. */ 'cluster_name': (string); /** @@ -180,7 +180,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc { /** * The target URI when using the `Google C++ gRPC client * `_. SSL credentials will be supplied in - * :ref:`channel_credentials `. + * :ref:`channel_credentials `. */ 'target_uri'?: (string); 'channel_credentials'?: (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials | null); @@ -230,7 +230,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc__Output { /** * The target URI when using the `Google C++ gRPC client * `_. SSL credentials will be supplied in - * :ref:`channel_credentials `. + * :ref:`channel_credentials `. */ 'target_uri': (string); 'channel_credentials': (_envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelCredentials__Output | null); @@ -300,12 +300,18 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_GoogleLocalCredent export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin { 'name'?: (string); 'typed_config'?: (_google_protobuf_Any | null); + /** + * [#extension-category: envoy.grpc_credentials] + */ 'config_type'?: "typed_config"; } export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin__Output { 'name': (string); 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * [#extension-category: envoy.grpc_credentials] + */ 'config_type': "typed_config"; } @@ -485,7 +491,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_ChannelArgs_Value_ /** * gRPC service configuration. This is used by :ref:`ApiConfigSource - * ` and filter configurations. + * ` and filter configurations. * [#next-free-field: 6] */ export interface GrpcService { @@ -519,7 +525,7 @@ export interface GrpcService { /** * gRPC service configuration. This is used by :ref:`ApiConfigSource - * ` and filter configurations. + * ` and filter configurations. * [#next-free-field: 6] */ export interface GrpcService__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts index 29f9d6695..7ba7e9d8d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts @@ -3,6 +3,31 @@ import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + +/** + * Describes the supported actions types for header append action. + */ +export enum _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction { + /** + * This action will append the specified value to the existing values if the header + * already exists. If the header doesn't exist then this will add the header with + * specified key and value. + */ + APPEND_IF_EXISTS_OR_ADD = 0, + /** + * This action will add the header if it doesn't already exist. If the header + * already exists then this will be a no-op. + */ + ADD_IF_ABSENT = 1, + /** + * This action will overwrite the specified value by discarding any existing values if + * the header already exists. If the header doesn't exist then this will add the header + * with specified key and value. + */ + OVERWRITE_IF_EXISTS_OR_ADD = 2, +} + /** * Header name/value pair plus option to control append behavior. */ @@ -16,6 +41,11 @@ export interface HeaderValueOption { * existing values. Otherwise it replaces any existing values. */ 'append'?: (_google_protobuf_BoolValue | null); + /** + * [#not-implemented-hide:] Describes the action taken to append/overwrite the given value for an existing header + * or to only add this header if it's absent. Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD`. + */ + 'append_action'?: (_envoy_config_core_v3_HeaderValueOption_HeaderAppendAction | keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); } /** @@ -31,4 +61,9 @@ export interface HeaderValueOption__Output { * existing values. Otherwise it replaces any existing values. */ 'append': (_google_protobuf_BoolValue__Output | null); + /** + * [#not-implemented-hide:] Describes the action taken to append/overwrite the given value for an existing header + * or to only add this header if it's absent. Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD`. + */ + 'append_action': (keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts index 6dbbf6f4d..8882de614 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts @@ -24,6 +24,7 @@ export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck { /** * A custom health checker specific configuration which depends on the custom health checker * being instantiated. See :api:`envoy/config/health_checker` for reference. + * [#extension-category: envoy.health_checkers] */ 'config_type'?: "typed_config"; } @@ -40,6 +41,7 @@ export interface _envoy_config_core_v3_HealthCheck_CustomHealthCheck__Output { /** * A custom health checker specific configuration which depends on the custom health checker * being instantiated. See :api:`envoy/config/health_checker` for reference. + * [#extension-category: envoy.health_checkers] */ 'config_type': "typed_config"; } @@ -63,7 +65,7 @@ export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck { * The value of the :authority header in the gRPC health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The authority header can be customized for a specific endpoint by setting - * the :ref:`hostname ` field. + * the :ref:`hostname ` field. */ 'authority'?: (string); } @@ -87,20 +89,20 @@ export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output { * The value of the :authority header in the gRPC health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The authority header can be customized for a specific endpoint by setting - * the :ref:`hostname ` field. + * the :ref:`hostname ` field. */ 'authority': (string); } /** - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { /** * The value of the host header in the HTTP health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The host header can be customized for a specific endpoint by setting the - * :ref:`hostname ` field. + * :ref:`hostname ` field. */ 'host'?: (string); /** @@ -131,10 +133,23 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { /** * Specifies a list of HTTP response statuses considered healthy. If provided, replaces default * 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - * semantics of :ref:`Int64Range `. The start and end of each + * semantics of :ref:`Int64Range `. The start and end of each * range are required. Only statuses in the range [100, 600) are allowed. */ 'expected_statuses'?: (_envoy_type_v3_Int64Range)[]; + /** + * Specifies a list of HTTP response statuses considered retriable. If provided, responses in this range + * will count towards the configured :ref:`unhealthy_threshold `, + * but will not result in the host being considered immediately unhealthy. Ranges follow half-open semantics of + * :ref:`Int64Range `. The start and end of each range are required. + * Only statuses in the range [100, 600) are allowed. The :ref:`expected_statuses ` + * field takes precedence for any range overlaps with this field i.e. if status code 200 is both retriable and expected, a 200 response will + * be considered a successful health check. By default all responses not in + * :ref:`expected_statuses ` will result in + * the host being considered immediately unhealthy i.e. if status code 200 is expected and there are no configured retriable statuses, any + * non-200 response will result in the host being marked unhealthy. + */ + 'retriable_statuses'?: (_envoy_type_v3_Int64Range)[]; /** * Use specified application protocol for health checks. */ @@ -142,21 +157,21 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { /** * An optional service name parameter which is used to validate the identity of * the health checked cluster using a :ref:`StringMatcher - * `. See the :ref:`architecture overview + * `. See the :ref:`architecture overview * ` for more information. */ 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher | null); } /** - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { /** * The value of the host header in the HTTP health check request. If * left empty (default value), the name of the cluster this health check is associated * with will be used. The host header can be customized for a specific endpoint by setting the - * :ref:`hostname ` field. + * :ref:`hostname ` field. */ 'host': (string); /** @@ -187,10 +202,23 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { /** * Specifies a list of HTTP response statuses considered healthy. If provided, replaces default * 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open - * semantics of :ref:`Int64Range `. The start and end of each + * semantics of :ref:`Int64Range `. The start and end of each * range are required. Only statuses in the range [100, 600) are allowed. */ 'expected_statuses': (_envoy_type_v3_Int64Range__Output)[]; + /** + * Specifies a list of HTTP response statuses considered retriable. If provided, responses in this range + * will count towards the configured :ref:`unhealthy_threshold `, + * but will not result in the host being considered immediately unhealthy. Ranges follow half-open semantics of + * :ref:`Int64Range `. The start and end of each range are required. + * Only statuses in the range [100, 600) are allowed. The :ref:`expected_statuses ` + * field takes precedence for any range overlaps with this field i.e. if status code 200 is both retriable and expected, a 200 response will + * be considered a successful health check. By default all responses not in + * :ref:`expected_statuses ` will result in + * the host being considered immediately unhealthy i.e. if status code 200 is expected and there are no configured retriable statuses, any + * non-200 response will result in the host being marked unhealthy. + */ + 'retriable_statuses': (_envoy_type_v3_Int64Range__Output)[]; /** * Use specified application protocol for health checks. */ @@ -198,7 +226,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { /** * An optional service name parameter which is used to validate the identity of * the health checked cluster using a :ref:`StringMatcher - * `. See the :ref:`architecture overview + * `. See the :ref:`architecture overview * ` for more information. */ 'service_name_matcher': (_envoy_type_matcher_v3_StringMatcher__Output | null); @@ -290,7 +318,7 @@ export interface _envoy_config_core_v3_HealthCheck_TlsOptions { /** * Specifies the ALPN protocols for health check connections. This is useful if the * corresponding upstream is using ALPN-based :ref:`FilterChainMatch - * ` along with different protocols for health checks + * ` along with different protocols for health checks * versus data connections. If empty, no ALPN protocols will be set on health check connections. */ 'alpn_protocols'?: (string)[]; @@ -306,7 +334,7 @@ export interface _envoy_config_core_v3_HealthCheck_TlsOptions__Output { /** * Specifies the ALPN protocols for health check connections. This is useful if the * corresponding upstream is using ALPN-based :ref:`FilterChainMatch - * ` along with different protocols for health checks + * ` along with different protocols for health checks * versus data connections. If empty, no ALPN protocols will be set on health check connections. */ 'alpn_protocols': (string)[]; @@ -332,8 +360,10 @@ export interface HealthCheck { 'interval_jitter'?: (_google_protobuf_Duration | null); /** * The number of unhealthy health checks required before a host is marked - * unhealthy. Note that for *http* health checking if a host responds with 503 - * this threshold is ignored and the host is considered unhealthy immediately. + * unhealthy. Note that for *http* health checking if a host responds with a code not in + * :ref:`expected_statuses ` + * or :ref:`retriable_statuses `, + * this threshold is ignored and the host is considered immediately unhealthy. */ 'unhealthy_threshold'?: (_google_protobuf_UInt32Value | null); /** @@ -440,7 +470,7 @@ export interface HealthCheck { 'event_service'?: (_envoy_config_core_v3_EventServiceConfig | null); /** * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's - * :ref:`tranport socket matches `. + * :ref:`tranport socket matches `. * For example, the following match criteria * * .. code-block:: yaml @@ -448,7 +478,7 @@ export interface HealthCheck { * transport_socket_match_criteria: * useMTLS: true * - * Will match the following :ref:`cluster socket match ` + * Will match the following :ref:`cluster socket match ` * * .. code-block:: yaml * @@ -461,13 +491,13 @@ export interface HealthCheck { * config: { ... } # tls socket configuration * * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the - * :ref:`LbEndpoint.Metadata `. + * :ref:`LbEndpoint.Metadata `. * This allows using different transport socket capabilities for health checking versus proxying to the * endpoint. * * If the key/values pairs specified do not match any - * :ref:`transport socket matches `, - * the cluster's :ref:`transport socket ` + * :ref:`transport socket matches `, + * the cluster's :ref:`transport socket ` * will be used for health check socket configuration. */ 'transport_socket_match_criteria'?: (_google_protobuf_Struct | null); @@ -510,8 +540,10 @@ export interface HealthCheck__Output { 'interval_jitter': (_google_protobuf_Duration__Output | null); /** * The number of unhealthy health checks required before a host is marked - * unhealthy. Note that for *http* health checking if a host responds with 503 - * this threshold is ignored and the host is considered unhealthy immediately. + * unhealthy. Note that for *http* health checking if a host responds with a code not in + * :ref:`expected_statuses ` + * or :ref:`retriable_statuses `, + * this threshold is ignored and the host is considered immediately unhealthy. */ 'unhealthy_threshold': (_google_protobuf_UInt32Value__Output | null); /** @@ -618,7 +650,7 @@ export interface HealthCheck__Output { 'event_service': (_envoy_config_core_v3_EventServiceConfig__Output | null); /** * Optional key/value pairs that will be used to match a transport socket from those specified in the cluster's - * :ref:`tranport socket matches `. + * :ref:`tranport socket matches `. * For example, the following match criteria * * .. code-block:: yaml @@ -626,7 +658,7 @@ export interface HealthCheck__Output { * transport_socket_match_criteria: * useMTLS: true * - * Will match the following :ref:`cluster socket match ` + * Will match the following :ref:`cluster socket match ` * * .. code-block:: yaml * @@ -639,13 +671,13 @@ export interface HealthCheck__Output { * config: { ... } # tls socket configuration * * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the - * :ref:`LbEndpoint.Metadata `. + * :ref:`LbEndpoint.Metadata `. * This allows using different transport socket capabilities for health checking versus proxying to the * endpoint. * * If the key/values pairs specified do not match any - * :ref:`transport socket matches `, - * the cluster's :ref:`transport socket ` + * :ref:`transport socket matches `, + * the cluster's :ref:`transport socket ` * will be used for health check socket configuration. */ 'transport_socket_match_criteria': (_google_protobuf_Struct__Output | null); diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts index 89889d390..40b7408f6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts @@ -1,7 +1,11 @@ // Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +/** + * [#next-free-field: 9] + */ export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat { /** * Formats the header by proper casing words: the first character and any character following @@ -11,9 +15,18 @@ export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat { * are not covered. For example, the "TE" header will be formatted as "Te". */ 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords | null); - 'header_format'?: "proper_case_words"; + /** + * Configuration for stateful formatter extensions that allow using received headers to + * affect the output of encoding headers. E.g., preserving case during proxying. + * [#extension-category: envoy.http.stateful_header_formatters] + */ + 'stateful_formatter'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'header_format'?: "proper_case_words"|"stateful_formatter"; } +/** + * [#next-free-field: 9] + */ export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Output { /** * Formats the header by proper casing words: the first character and any character following @@ -23,7 +36,13 @@ export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat__Out * are not covered. For example, the "TE" header will be formatted as "Te". */ 'proper_case_words'?: (_envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords__Output | null); - 'header_format': "proper_case_words"; + /** + * Configuration for stateful formatter extensions that allow using received headers to + * affect the output of encoding headers. E.g., preserving case during proxying. + * [#extension-category: envoy.http.stateful_header_formatters] + */ + 'stateful_formatter'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'header_format': "proper_case_words"|"stateful_formatter"; } export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_ProperCaseWords { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts index 52908c961..786e2a00d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts @@ -53,6 +53,10 @@ export interface Http2ProtocolOptions { * For upstream connections, this also limits how many streams Envoy will initiate concurrently * on a single connection. If the limit is reached, Envoy may queue requests or establish * additional connections (as allowed per circuit breaker limits). + * + * This acts as an upper bound: Envoy will lower the max concurrent streams allowed on a given + * connection based on upstream settings. Config dumps will reflect the configured upper bound, + * not the per-connection negotiated limits. */ 'max_concurrent_streams'?: (_google_protobuf_UInt32Value | null); /** @@ -233,6 +237,10 @@ export interface Http2ProtocolOptions__Output { * For upstream connections, this also limits how many streams Envoy will initiate concurrently * on a single connection. If the limit is reached, Envoy may queue requests or establish * additional connections (as allowed per circuit breaker limits). + * + * This acts as an upper bound: Envoy will lower the max concurrent streams allowed on a given + * connection based on upstream settings. Config dumps will reflect the configured upper bound, + * not the per-connection negotiated limits. */ 'max_concurrent_streams': (_google_protobuf_UInt32Value__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts index 320285d87..51b31b8e7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http3ProtocolOptions.ts @@ -1,22 +1,56 @@ // Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto +import type { QuicProtocolOptions as _envoy_config_core_v3_QuicProtocolOptions, QuicProtocolOptions__Output as _envoy_config_core_v3_QuicProtocolOptions__Output } from '../../../../envoy/config/core/v3/QuicProtocolOptions'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; /** - * [#not-implemented-hide:] - * - * A message which allows using HTTP/3 as an upstream protocol. - * - * Eventually this will include configuration for tuning HTTP/3. + * A message which allows using HTTP/3. + * [#next-free-field: 6] */ export interface Http3ProtocolOptions { + 'quic_protocol_options'?: (_envoy_config_core_v3_QuicProtocolOptions | null); + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/3 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); + /** + * Allows proxying Websocket and other upgrades over HTTP/3 CONNECT using + * the header mechanisms from the `HTTP/2 extended connect RFC + * `_ + * and settings `proposed for HTTP/3 + * `_ + * Note that HTTP/3 CONNECT is not yet an RFC. + */ + 'allow_extended_connect'?: (boolean); } /** - * [#not-implemented-hide:] - * - * A message which allows using HTTP/3 as an upstream protocol. - * - * Eventually this will include configuration for tuning HTTP/3. + * A message which allows using HTTP/3. + * [#next-free-field: 6] */ export interface Http3ProtocolOptions__Output { + 'quic_protocol_options': (_envoy_config_core_v3_QuicProtocolOptions__Output | null); + /** + * Allows invalid HTTP messaging and headers. When this option is disabled (default), then + * the whole HTTP/3 connection is terminated upon receiving invalid HEADERS frame. However, + * when this option is enabled, only the offending stream is terminated. + * + * If set, this overrides any HCM :ref:`stream_error_on_invalid_http_messaging + * `. + */ + 'override_stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); + /** + * Allows proxying Websocket and other upgrades over HTTP/3 CONNECT using + * the header mechanisms from the `HTTP/2 extended connect RFC + * `_ + * and settings `proposed for HTTP/3 + * `_ + * Note that HTTP/3 CONNECT is not yet an RFC. + */ + 'allow_extended_connect': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts index f689ba802..34a4053dd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts @@ -32,7 +32,7 @@ export enum _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresActi } /** - * [#next-free-field: 6] + * [#next-free-field: 7] */ export interface HttpProtocolOptions { /** @@ -41,7 +41,7 @@ export interface HttpProtocolOptions { * idle timeout is reached the connection will be closed. If the connection is an HTTP/2 * downstream connection a drain sequence will occur prior to closing the connection, see * :ref:`drain_timeout - * `. + * `. * Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. * If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. * @@ -51,7 +51,7 @@ export interface HttpProtocolOptions { * * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" * is configured, this timeout is scaled for downstream connections according to the value for - * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. */ 'idle_timeout'?: (_google_protobuf_Duration | null); /** @@ -63,10 +63,11 @@ export interface HttpProtocolOptions { /** * The maximum duration of a connection. The duration is defined as a period since a connection * was established. If not set, there is no max duration. When max_connection_duration is reached - * the connection will be closed. Drain sequence will occur prior to closing the connection if - * if's applicable. See :ref:`drain_timeout - * `. - * Note: not implemented for upstream connections. + * and if there are no active streams, the connection will be closed. If there are any active streams, + * the drain sequence will kick-in, and the connection will be force-closed after the drain period. + * See :ref:`drain_timeout + * `. + * Note: This feature is not yet implemented for the upstream connections. */ 'max_connection_duration'?: (_google_protobuf_Duration | null); /** @@ -80,10 +81,17 @@ export interface HttpProtocolOptions { * Note: upstream responses are not affected by this setting. */ 'headers_with_underscores_action'?: (_envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction | keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); + /** + * Optional maximum requests for both upstream and downstream connections. + * If not specified, there is no limit. + * Setting this parameter to 1 will effectively disable keep alive. + * For HTTP/2 and HTTP/3, due to concurrent stream processing, the limit is approximate. + */ + 'max_requests_per_connection'?: (_google_protobuf_UInt32Value | null); } /** - * [#next-free-field: 6] + * [#next-free-field: 7] */ export interface HttpProtocolOptions__Output { /** @@ -92,7 +100,7 @@ export interface HttpProtocolOptions__Output { * idle timeout is reached the connection will be closed. If the connection is an HTTP/2 * downstream connection a drain sequence will occur prior to closing the connection, see * :ref:`drain_timeout - * `. + * `. * Note that request based timeouts mean that HTTP/2 PINGs will not keep the connection alive. * If not specified, this defaults to 1 hour. To disable idle timeouts explicitly set this to 0. * @@ -102,7 +110,7 @@ export interface HttpProtocolOptions__Output { * * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" * is configured, this timeout is scaled for downstream connections according to the value for - * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. + * :ref:`HTTP_DOWNSTREAM_CONNECTION_IDLE `. */ 'idle_timeout': (_google_protobuf_Duration__Output | null); /** @@ -114,10 +122,11 @@ export interface HttpProtocolOptions__Output { /** * The maximum duration of a connection. The duration is defined as a period since a connection * was established. If not set, there is no max duration. When max_connection_duration is reached - * the connection will be closed. Drain sequence will occur prior to closing the connection if - * if's applicable. See :ref:`drain_timeout - * `. - * Note: not implemented for upstream connections. + * and if there are no active streams, the connection will be closed. If there are any active streams, + * the drain sequence will kick-in, and the connection will be force-closed after the drain period. + * See :ref:`drain_timeout + * `. + * Note: This feature is not yet implemented for the upstream connections. */ 'max_connection_duration': (_google_protobuf_Duration__Output | null); /** @@ -131,4 +140,11 @@ export interface HttpProtocolOptions__Output { * Note: upstream responses are not affected by this setting. */ 'headers_with_underscores_action': (keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); + /** + * Optional maximum requests for both upstream and downstream connections. + * If not specified, there is no limit. + * Setting this parameter to 1 will effectively disable keep alive. + * For HTTP/2 and HTTP/3, due to concurrent stream processing, the limit is approximate. + */ + 'max_requests_per_connection': (_google_protobuf_UInt32Value__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts index 81344f864..2c274c6e1 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts @@ -6,6 +6,7 @@ import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_ export interface KeepaliveSettings { /** * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. + * If this is zero, interval PINGs will not be sent. */ 'interval'?: (_google_protobuf_Duration | null); /** @@ -19,11 +20,20 @@ export interface KeepaliveSettings { * The default value is 15%. */ 'interval_jitter'?: (_envoy_type_v3_Percent | null); + /** + * If the connection has been idle for this duration, send a HTTP/2 ping ahead + * of new stream creation, to quickly detect dead connections. + * If this is zero, this type of PING will not be sent. + * If an interval ping is outstanding, a second ping will not be sent as the + * interval ping will determine if the connection is dead. + */ + 'connection_idle_interval'?: (_google_protobuf_Duration | null); } export interface KeepaliveSettings__Output { /** * Send HTTP/2 PING frames at this period, in order to test that the connection is still alive. + * If this is zero, interval PINGs will not be sent. */ 'interval': (_google_protobuf_Duration__Output | null); /** @@ -37,4 +47,12 @@ export interface KeepaliveSettings__Output { * The default value is 15%. */ 'interval_jitter': (_envoy_type_v3_Percent__Output | null); + /** + * If the connection has been idle for this duration, send a HTTP/2 ping ahead + * of new stream creation, to quickly detect dead connections. + * If this is zero, this type of PING will not be sent. + * If an interval ping is outstanding, a second ping will not be sent as the + * interval ping will determine if the connection is dead. + */ + 'connection_idle_interval': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts index 2b4b42a75..b15b53832 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Locality.ts @@ -6,13 +6,13 @@ */ export interface Locality { /** - * Region this :ref:`zone ` belongs to. + * Region this :ref:`zone ` belongs to. */ 'region'?: (string); /** * Defines the local service zone where Envoy is running. Though optional, it * should be set if discovery service routing is used and the discovery - * service exposes :ref:`zone data `, + * service exposes :ref:`zone data `, * either in this message or via :option:`--service-zone`. The meaning of zone * is context dependent, e.g. `Availability Zone (AZ) * `_ @@ -33,13 +33,13 @@ export interface Locality { */ export interface Locality__Output { /** - * Region this :ref:`zone ` belongs to. + * Region this :ref:`zone ` belongs to. */ 'region': (string); /** * Defines the local service zone where Envoy is running. Though optional, it * should be set if discovery service routing is used and the discovery - * service exposes :ref:`zone data `, + * service exposes :ref:`zone data `, * either in this message or via :option:`--service-zone`. The meaning of zone * is context dependent, e.g. `Availability Zone (AZ) * `_ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts index 8d1811c67..fb603c2ba 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts @@ -1,6 +1,7 @@ // Original file: deps/envoy-api/envoy/config/core/v3/base.proto import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; /** * Metadata provides additional inputs to filters based on matched listeners, @@ -30,8 +31,21 @@ export interface Metadata { /** * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* * namespace is reserved for Envoy's built-in filters. + * If both *filter_metadata* and + * :ref:`typed_filter_metadata ` + * fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. */ 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct}); + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + * The value is encoded as google.protobuf.Any. + * If both :ref:`filter_metadata ` + * and *typed_filter_metadata* fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. + */ + 'typed_filter_metadata'?: ({[key: string]: _google_protobuf_Any}); } /** @@ -62,6 +76,19 @@ export interface Metadata__Output { /** * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* * namespace is reserved for Envoy's built-in filters. + * If both *filter_metadata* and + * :ref:`typed_filter_metadata ` + * fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. */ 'filter_metadata': ({[key: string]: _google_protobuf_Struct__Output}); + /** + * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * namespace is reserved for Envoy's built-in filters. + * The value is encoded as google.protobuf.Any. + * If both :ref:`filter_metadata ` + * and *typed_filter_metadata* fields are present in the metadata with same keys, + * only *typed_filter_metadata* field will be parsed. + */ + 'typed_filter_metadata': ({[key: string]: _google_protobuf_Any__Output}); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts index 7218b802e..addd47a68 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts @@ -5,12 +5,13 @@ import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _e import type { BuildVersion as _envoy_config_core_v3_BuildVersion, BuildVersion__Output as _envoy_config_core_v3_BuildVersion__Output } from '../../../../envoy/config/core/v3/BuildVersion'; import type { Extension as _envoy_config_core_v3_Extension, Extension__Output as _envoy_config_core_v3_Extension__Output } from '../../../../envoy/config/core/v3/Extension'; import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { ContextParams as _xds_core_v3_ContextParams, ContextParams__Output as _xds_core_v3_ContextParams__Output } from '../../../../xds/core/v3/ContextParams'; /** * Identifies a specific Envoy instance. The node identifier is presented to the * management server, which may use this identifier to distinguish per Envoy * configuration for serving. - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface Node { /** @@ -27,10 +28,10 @@ export interface Node { * optional, it should be set if any of the following features are used: * :ref:`statsd `, :ref:`health check cluster * verification - * `, - * :ref:`runtime override directory `, + * `, + * :ref:`runtime override directory `, * :ref:`user agent addition - * `, + * `, * :ref:`HTTP global rate limiting `, * :ref:`CDS `, and :ref:`HTTP tracing * `, either in this message or via @@ -79,6 +80,14 @@ export interface Node { * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. */ 'listening_addresses'?: (_envoy_config_core_v3_Address)[]; + /** + * Map from xDS resource type URL to dynamic context parameters. These may vary at runtime (unlike + * other fields in this message). For example, the xDS client may have a shard identifier that + * changes during the lifetime of the xDS client. In Envoy, this would be achieved by updating the + * dynamic context on the Server::Instance's LocalInfo context provider. The shard ID dynamic + * parameter then appears in this field during future discovery requests. + */ + 'dynamic_parameters'?: ({[key: string]: _xds_core_v3_ContextParams}); 'user_agent_version_type'?: "user_agent_version"|"user_agent_build_version"; } @@ -86,7 +95,7 @@ export interface Node { * Identifies a specific Envoy instance. The node identifier is presented to the * management server, which may use this identifier to distinguish per Envoy * configuration for serving. - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface Node__Output { /** @@ -103,10 +112,10 @@ export interface Node__Output { * optional, it should be set if any of the following features are used: * :ref:`statsd `, :ref:`health check cluster * verification - * `, - * :ref:`runtime override directory `, + * `, + * :ref:`runtime override directory `, * :ref:`user agent addition - * `, + * `, * :ref:`HTTP global rate limiting `, * :ref:`CDS `, and :ref:`HTTP tracing * `, either in this message or via @@ -155,5 +164,13 @@ export interface Node__Output { * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. */ 'listening_addresses': (_envoy_config_core_v3_Address__Output)[]; + /** + * Map from xDS resource type URL to dynamic context parameters. These may vary at runtime (unlike + * other fields in this message). For example, the xDS client may have a shard identifier that + * changes during the lifetime of the xDS client. In Envoy, this would be achieved by updating the + * dynamic context on the Server::Instance's LocalInfo context provider. The shard ID dynamic + * parameter then appears in this field during future discovery requests. + */ + 'dynamic_parameters': ({[key: string]: _xds_core_v3_ContextParams__Output}); 'user_agent_version_type': "user_agent_version"|"user_agent_build_version"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QueryParameter.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QueryParameter.ts new file mode 100644 index 000000000..4cf7952fb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QueryParameter.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/base.proto + + +/** + * Query parameter name/value pair. + */ +export interface QueryParameter { + /** + * The key of the query parameter. Case sensitive. + */ + 'key'?: (string); + /** + * The value of the query parameter. + */ + 'value'?: (string); +} + +/** + * Query parameter name/value pair. + */ +export interface QueryParameter__Output { + /** + * The key of the query parameter. Case sensitive. + */ + 'key': (string); + /** + * The value of the query parameter. + */ + 'value': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts new file mode 100644 index 000000000..6a653b54d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts @@ -0,0 +1,69 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +/** + * QUIC protocol options which apply to both downstream and upstream connections. + */ +export interface QuicProtocolOptions { + /** + * Maximum number of streams that the client can negotiate per connection. 100 + * if not specified. + */ + 'max_concurrent_streams'?: (_google_protobuf_UInt32Value | null); + /** + * `Initial stream-level flow-control receive window + * `_ size. Valid values range from + * 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + * QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + * + * This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + * QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + * stop the flow of data to the stream buffers. + */ + 'initial_stream_window_size'?: (_google_protobuf_UInt32Value | null); + /** + * Similar to *initial_stream_window_size*, but for connection-level + * flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + * window. Currently, this has the same minimum/default as *initial_stream_window_size*. + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + * window size now, so it's also the minimum. + */ + 'initial_connection_window_size'?: (_google_protobuf_UInt32Value | null); +} + +/** + * QUIC protocol options which apply to both downstream and upstream connections. + */ +export interface QuicProtocolOptions__Output { + /** + * Maximum number of streams that the client can negotiate per connection. 100 + * if not specified. + */ + 'max_concurrent_streams': (_google_protobuf_UInt32Value__Output | null); + /** + * `Initial stream-level flow-control receive window + * `_ size. Valid values range from + * 1 to 16777216 (2^24, maximum supported by QUICHE) and defaults to 65536 (2^16). + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. If configured smaller than it, we will use 16384 instead. + * QUICHE IETF Quic implementation supports 1 bytes window. We only support increasing the default window size now, so it's also the minimum. + * + * This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + * QUIC stream send and receive buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + * stop the flow of data to the stream buffers. + */ + 'initial_stream_window_size': (_google_protobuf_UInt32Value__Output | null); + /** + * Similar to *initial_stream_window_size*, but for connection-level + * flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). + * window. Currently, this has the same minimum/default as *initial_stream_window_size*. + * + * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default + * window size now, so it's also the minimum. + */ + 'initial_connection_window_size': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts index def6f7311..6e2af23e6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RetryPolicy.ts @@ -8,7 +8,7 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a */ export interface RetryPolicy { /** - * Specifies parameters that control :ref:`retry backoff strategy `. + * Specifies parameters that control :ref:`retry backoff strategy `. * This parameter is optional, in which case the default base interval is 1000 milliseconds. The * default maximum interval is 10 times the base interval. */ @@ -25,7 +25,7 @@ export interface RetryPolicy { */ export interface RetryPolicy__Output { /** - * Specifies parameters that control :ref:`retry backoff strategy `. + * Specifies parameters that control :ref:`retry backoff strategy `. * This parameter is optional, in which case the default base interval is 1000 milliseconds. The * default maximum interval is 10 times the base interval. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts index 619bfd484..3a2073294 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts @@ -9,7 +9,7 @@ import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalP * .. note:: * * Parsing of the runtime key's data is implemented such that it may be represented as a - * :ref:`FractionalPercent ` proto represented as JSON/YAML + * :ref:`FractionalPercent ` proto represented as JSON/YAML * and may also be represented as an integer with the assumption that the value is an integral * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. @@ -32,7 +32,7 @@ export interface RuntimeFractionalPercent { * .. note:: * * Parsing of the runtime key's data is implemented such that it may be represented as a - * :ref:`FractionalPercent ` proto represented as JSON/YAML + * :ref:`FractionalPercent ` proto represented as JSON/YAML * and may also be represented as an integer with the assumption that the value is an integral * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SchemeHeaderTransformation.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SchemeHeaderTransformation.ts new file mode 100644 index 000000000..95bb4e400 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SchemeHeaderTransformation.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + + +/** + * A message to control transformations to the :scheme header + */ +export interface SchemeHeaderTransformation { + /** + * Overwrite any Scheme header with the contents of this string. + */ + 'scheme_to_overwrite'?: (string); + 'transformation'?: "scheme_to_overwrite"; +} + +/** + * A message to control transformations to the :scheme header + */ +export interface SchemeHeaderTransformation__Output { + /** + * Overwrite any Scheme header with the contents of this string. + */ + 'scheme_to_overwrite'?: (string); + 'transformation': "scheme_to_overwrite"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts index e387adca2..3912fd1cf 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts @@ -5,7 +5,7 @@ import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../.. /** * [#not-implemented-hide:] * Self-referencing config source options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to + * set in :ref:`ConfigSource ` can be used to * specify that other data can be obtained from the same server. */ export interface SelfConfigSource { @@ -19,7 +19,7 @@ export interface SelfConfigSource { /** * [#not-implemented-hide:] * Self-referencing config source options. This is currently empty, but when - * set in :ref:`ConfigSource ` can be used to + * set in :ref:`ConfigSource ` can be used to * specify that other data can be obtained from the same server. */ export interface SelfConfigSource__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts index e3f342d16..3966dc04c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts @@ -18,19 +18,19 @@ export interface SocketAddress { * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: * It is possible to distinguish a Listener address via the prefix/suffix matching - * in :ref:`FilterChainMatch `.] When used - * within an upstream :ref:`BindConfig `, the address + * in :ref:`FilterChainMatch `.] When used + * within an upstream :ref:`BindConfig `, the address * controls the source address of outbound connections. For :ref:`clusters - * `, the cluster type determines whether the + * `, the cluster type determines whether the * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - * via :ref:`resolver_name `. + * via :ref:`resolver_name `. */ 'address'?: (string); 'port_value'?: (number); /** * This is only valid if :ref:`resolver_name - * ` is specified below and the + * ` is specified below and the * named resolver is capable of named port resolution. */ 'named_port'?: (string); @@ -62,19 +62,19 @@ export interface SocketAddress__Output { * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: * It is possible to distinguish a Listener address via the prefix/suffix matching - * in :ref:`FilterChainMatch `.] When used - * within an upstream :ref:`BindConfig `, the address + * in :ref:`FilterChainMatch `.] When used + * within an upstream :ref:`BindConfig `, the address * controls the source address of outbound connections. For :ref:`clusters - * `, the cluster type determines whether the + * `, the cluster type determines whether the * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - * via :ref:`resolver_name `. + * via :ref:`resolver_name `. */ 'address': (string); 'port_value'?: (number); /** * This is only valid if :ref:`resolver_name - * ` is specified below and the + * ` is specified below and the * named resolver is capable of named port resolution. */ 'named_port'?: (string); diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts index ae3003189..a935fc1ec 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts @@ -95,6 +95,7 @@ export interface SubstitutionFormatString { /** * Specifies a collection of Formatter plugins that can be called from the access log configuration. * See the formatters extensions documentation for details. + * [#extension-category: envoy.formatter] */ 'formatters'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; 'format'?: "text_format"|"json_format"|"text_format_source"; @@ -191,6 +192,7 @@ export interface SubstitutionFormatString__Output { /** * Specifies a collection of Formatter plugins that can be called from the access log configuration. * See the formatters extensions documentation for details. + * [#extension-category: envoy.formatter] */ 'formatters': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; 'format': "text_format"|"json_format"|"text_format_source"; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts index 0031ad52f..ff05991ad 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TransportSocket.ts @@ -4,7 +4,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ /** * Configuration for transport socket in :ref:`listeners ` and - * :ref:`clusters `. If the configuration is + * :ref:`clusters `. If the configuration is * empty, a default transport socket implementation and configuration will be * chosen based on the platform and existence of tls_context. */ @@ -24,7 +24,7 @@ export interface TransportSocket { /** * Configuration for transport socket in :ref:`listeners ` and - * :ref:`clusters `. If the configuration is + * :ref:`clusters `. If the configuration is * empty, a default transport socket implementation and configuration will be * chosen based on the platform and existence of tls_context. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts new file mode 100644 index 000000000..fe1b038db --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts @@ -0,0 +1,45 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/udp_socket_config.proto + +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Generic UDP socket configuration. + */ +export interface UdpSocketConfig { + /** + * The maximum size of received UDP datagrams. Using a larger size will cause Envoy to allocate + * more memory per socket. Received datagrams above this size will be dropped. If not set + * defaults to 1500 bytes. + */ + 'max_rx_datagram_size'?: (_google_protobuf_UInt64Value | null); + /** + * Configures whether Generic Receive Offload (GRO) + * _ is preferred when reading from the + * UDP socket. The default is context dependent and is documented where UdpSocketConfig is used. + * This option affects performance but not functionality. If GRO is not supported by the operating + * system, non-GRO receive will be used. + */ + 'prefer_gro'?: (_google_protobuf_BoolValue | null); +} + +/** + * Generic UDP socket configuration. + */ +export interface UdpSocketConfig__Output { + /** + * The maximum size of received UDP datagrams. Using a larger size will cause Envoy to allocate + * more memory per socket. Received datagrams above this size will be dropped. If not set + * defaults to 1500 bytes. + */ + 'max_rx_datagram_size': (_google_protobuf_UInt64Value__Output | null); + /** + * Configures whether Generic Receive Offload (GRO) + * _ is preferred when reading from the + * UDP socket. The default is context dependent and is documented where UdpSocketConfig is used. + * This option affects performance but not functionality. If GRO is not supported by the operating + * system, non-GRO receive will be used. + */ + 'prefer_gro': (_google_protobuf_BoolValue__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts index b765ec5a7..c0da4159f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts @@ -4,31 +4,53 @@ export interface UpstreamHttpProtocolOptions { /** * Set transport socket `SNI `_ for new - * upstream connections based on the downstream HTTP host/authority header, as seen by the - * :ref:`router filter `. + * upstream connections based on the downstream HTTP host/authority header or any other arbitrary + * header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. */ 'auto_sni'?: (boolean); /** * Automatic validate upstream presented certificate for new upstream connections based on the - * downstream HTTP host/authority header, as seen by the - * :ref:`router filter `. - * This field is intended to set with `auto_sni` field. + * downstream HTTP host/authority header or any other arbitrary header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. + * This field is intended to be set with `auto_sni` field. */ 'auto_san_validation'?: (boolean); + /** + * An optional alternative to the host/authority header to be used for setting the SNI value. + * It should be a valid downstream HTTP header, as seen by the + * :ref:`router filter `. + * If unset, host/authority header will be used for populating the SNI. If the specified header + * is not found or the value is empty, host/authority header will be used instead. + * This field is intended to be set with `auto_sni` and/or `auto_san_validation` fields. + * If none of these fields are set then setting this would be a no-op. + */ + 'override_auto_sni_header'?: (string); } export interface UpstreamHttpProtocolOptions__Output { /** * Set transport socket `SNI `_ for new - * upstream connections based on the downstream HTTP host/authority header, as seen by the - * :ref:`router filter `. + * upstream connections based on the downstream HTTP host/authority header or any other arbitrary + * header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. */ 'auto_sni': (boolean); /** * Automatic validate upstream presented certificate for new upstream connections based on the - * downstream HTTP host/authority header, as seen by the - * :ref:`router filter `. - * This field is intended to set with `auto_sni` field. + * downstream HTTP host/authority header or any other arbitrary header when :ref:`override_auto_sni_header ` + * is set, as seen by the :ref:`router filter `. + * This field is intended to be set with `auto_sni` field. */ 'auto_san_validation': (boolean); + /** + * An optional alternative to the host/authority header to be used for setting the SNI value. + * It should be a valid downstream HTTP header, as seen by the + * :ref:`router filter `. + * If unset, host/authority header will be used for populating the SNI. If the specified header + * is not found or the value is empty, host/authority header will be used instead. + * This field is intended to be set with `auto_sni` and/or `auto_san_validation` fields. + * If none of these fields are set then setting this would be a no-op. + */ + 'override_auto_sni_header': (string); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts index 03ceb570e..91ce2e0c8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment.ts @@ -157,9 +157,9 @@ export interface _envoy_config_endpoint_v3_ClusterLoadAssignment_Policy__Output export interface ClusterLoadAssignment { /** * Name of the cluster. This will be the :ref:`service_name - * ` value if specified + * ` value if specified * in the cluster :ref:`EdsClusterConfig - * `. + * `. */ 'cluster_name'?: (string); /** @@ -192,9 +192,9 @@ export interface ClusterLoadAssignment { export interface ClusterLoadAssignment__Output { /** * Name of the cluster. This will be the :ref:`service_name - * ` value if specified + * ` value if specified * in the cluster :ref:`EdsClusterConfig - * `. + * `. */ 'cluster_name': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts index db820b0b3..c160333b2 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts @@ -28,7 +28,7 @@ export interface _envoy_config_endpoint_v3_ClusterStats_DroppedRequests__Output /** * Per cluster load stats. Envoy reports these stats a management server in a - * :ref:`LoadStatsRequest` + * :ref:`LoadStatsRequest` * Next ID: 7 * [#next-free-field: 7] */ @@ -72,7 +72,7 @@ export interface ClusterStats { /** * Per cluster load stats. Envoy reports these stats a management server in a - * :ref:`LoadStatsRequest` + * :ref:`LoadStatsRequest` * Next ID: 7 * [#next-free-field: 7] */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts index c01987cce..31eb09055 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts @@ -17,8 +17,8 @@ export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig { 'port_value'?: (number); /** * By default, the host header for L7 health checks is controlled by cluster level configuration - * (see: :ref:`host ` and - * :ref:`authority `). Setting this + * (see: :ref:`host ` and + * :ref:`authority `). Setting this * to a non-empty value allows overriding the cluster level configuration for a specific * endpoint. */ @@ -40,8 +40,8 @@ export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output { 'port_value': (number); /** * By default, the host header for L7 health checks is controlled by cluster level configuration - * (see: :ref:`host ` and - * :ref:`authority `). Setting this + * (see: :ref:`host ` and + * :ref:`authority `). Setting this * to a non-empty value allows overriding the cluster level configuration for a specific * endpoint. */ @@ -59,7 +59,7 @@ export interface Endpoint { * * The form of host address depends on the given cluster type. For STATIC or EDS, * it is expected to be a direct IP address (or something resolvable by the - * specified :ref:`resolver ` + * specified :ref:`resolver ` * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, * and will be resolved via DNS. */ @@ -78,7 +78,7 @@ export interface Endpoint { * The hostname associated with this endpoint. This hostname is not used for routing or address * resolution. If provided, it will be associated with the endpoint, and can be used for features * that require a hostname, like - * :ref:`auto_host_rewrite `. + * :ref:`auto_host_rewrite `. */ 'hostname'?: (string); } @@ -94,7 +94,7 @@ export interface Endpoint__Output { * * The form of host address depends on the given cluster type. For STATIC or EDS, * it is expected to be a direct IP address (or something resolvable by the - * specified :ref:`resolver ` + * specified :ref:`resolver ` * in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, * and will be resolved via DNS. */ @@ -113,7 +113,7 @@ export interface Endpoint__Output { * The hostname associated with this endpoint. This hostname is not used for routing or address * resolution. If provided, it will be associated with the endpoint, and can be used for features * that require a hostname, like - * :ref:`auto_host_rewrite `. + * :ref:`auto_host_rewrite `. */ 'hostname': (string); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts index 3952a6928..6025ae3a7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts @@ -21,7 +21,7 @@ export interface LbEndpoint { * name should be specified as *envoy.lb*. An example boolean key-value pair * is *canary*, providing the optional canary status of the upstream host. * This may be matched against in a route's - * :ref:`RouteAction ` metadata_match field + * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. */ 'metadata'?: (_envoy_config_core_v3_Metadata | null); @@ -63,7 +63,7 @@ export interface LbEndpoint__Output { * name should be specified as *envoy.lb*. An example boolean key-value pair * is *canary*, providing the optional canary status of the upstream host. * This may be matched against in a route's - * :ref:`RouteAction ` metadata_match field + * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. */ 'metadata': (_envoy_config_core_v3_Metadata__Output | null); diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LedsClusterLocalityConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LedsClusterLocalityConfig.ts new file mode 100644 index 000000000..1229d33a3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LedsClusterLocalityConfig.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; + +/** + * [#not-implemented-hide:] + * A configuration for a LEDS collection. + */ +export interface LedsClusterLocalityConfig { + /** + * Configuration for the source of LEDS updates for a Locality. + */ + 'leds_config'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * The xDS transport protocol glob collection resource name. + * The service is only supported in delta xDS (incremental) mode. + */ + 'leds_collection_name'?: (string); +} + +/** + * [#not-implemented-hide:] + * A configuration for a LEDS collection. + */ +export interface LedsClusterLocalityConfig__Output { + /** + * Configuration for the source of LEDS updates for a Locality. + */ + 'leds_config': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * The xDS transport protocol glob collection resource name. + * The service is only supported in delta xDS (incremental) mode. + */ + 'leds_collection_name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts index 22f053ed0..182e27c9c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts @@ -3,13 +3,30 @@ import type { Locality as _envoy_config_core_v3_Locality, Locality__Output as _envoy_config_core_v3_Locality__Output } from '../../../../envoy/config/core/v3/Locality'; import type { LbEndpoint as _envoy_config_endpoint_v3_LbEndpoint, LbEndpoint__Output as _envoy_config_endpoint_v3_LbEndpoint__Output } from '../../../../envoy/config/endpoint/v3/LbEndpoint'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { LedsClusterLocalityConfig as _envoy_config_endpoint_v3_LedsClusterLocalityConfig, LedsClusterLocalityConfig__Output as _envoy_config_endpoint_v3_LedsClusterLocalityConfig__Output } from '../../../../envoy/config/endpoint/v3/LedsClusterLocalityConfig'; + +/** + * [#not-implemented-hide:] + * A list of endpoints of a specific locality. + */ +export interface _envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList { + 'lb_endpoints'?: (_envoy_config_endpoint_v3_LbEndpoint)[]; +} + +/** + * [#not-implemented-hide:] + * A list of endpoints of a specific locality. + */ +export interface _envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList__Output { + 'lb_endpoints': (_envoy_config_endpoint_v3_LbEndpoint__Output)[]; +} /** * A group of endpoints belonging to a Locality. * One can have multiple LocalityLbEndpoints for a locality, but this is * generally only done if the different groups need to have different load * balancing weights or different priorities. - * [#next-free-field: 7] + * [#next-free-field: 9] */ export interface LocalityLbEndpoints { /** @@ -18,6 +35,8 @@ export interface LocalityLbEndpoints { 'locality'?: (_envoy_config_core_v3_Locality | null); /** * The group of endpoints belonging to the locality specified. + * [#comment:TODO(adisuissa): Once LEDS is implemented this field needs to be + * deprecated and replaced by *load_balancer_endpoints*.] */ 'lb_endpoints'?: (_envoy_config_endpoint_v3_LbEndpoint)[]; /** @@ -55,6 +74,20 @@ export interface LocalityLbEndpoints { * [#not-implemented-hide:] */ 'proximity'?: (_google_protobuf_UInt32Value | null); + /** + * The group of endpoints belonging to the locality. + * [#comment:TODO(adisuissa): Once LEDS is implemented the *lb_endpoints* field + * needs to be deprecated.] + */ + 'load_balancer_endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList | null); + /** + * LEDS Configuration for the current locality. + */ + 'leds_cluster_locality_config'?: (_envoy_config_endpoint_v3_LedsClusterLocalityConfig | null); + /** + * [#not-implemented-hide:] + */ + 'lb_config'?: "load_balancer_endpoints"|"leds_cluster_locality_config"; } /** @@ -62,7 +95,7 @@ export interface LocalityLbEndpoints { * One can have multiple LocalityLbEndpoints for a locality, but this is * generally only done if the different groups need to have different load * balancing weights or different priorities. - * [#next-free-field: 7] + * [#next-free-field: 9] */ export interface LocalityLbEndpoints__Output { /** @@ -71,6 +104,8 @@ export interface LocalityLbEndpoints__Output { 'locality': (_envoy_config_core_v3_Locality__Output | null); /** * The group of endpoints belonging to the locality specified. + * [#comment:TODO(adisuissa): Once LEDS is implemented this field needs to be + * deprecated and replaced by *load_balancer_endpoints*.] */ 'lb_endpoints': (_envoy_config_endpoint_v3_LbEndpoint__Output)[]; /** @@ -108,4 +143,18 @@ export interface LocalityLbEndpoints__Output { * [#not-implemented-hide:] */ 'proximity': (_google_protobuf_UInt32Value__Output | null); + /** + * The group of endpoints belonging to the locality. + * [#comment:TODO(adisuissa): Once LEDS is implemented the *lb_endpoints* field + * needs to be deprecated.] + */ + 'load_balancer_endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList__Output | null); + /** + * LEDS Configuration for the current locality. + */ + 'leds_cluster_locality_config'?: (_envoy_config_endpoint_v3_LedsClusterLocalityConfig__Output | null); + /** + * [#not-implemented-hide:] + */ + 'lb_config': "load_balancer_endpoints"|"leds_cluster_locality_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts index f00607d00..fbfb05ed6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/UpstreamLocalityStats.ts @@ -7,7 +7,7 @@ import type { Long } from '@grpc/proto-loader'; /** * These are stats Envoy reports to the management server at a frequency defined by - * :ref:`LoadStatsResponse.load_reporting_interval`. + * :ref:`LoadStatsResponse.load_reporting_interval`. * Stats per upstream region/zone and optionally per subzone. * [#next-free-field: 9] */ @@ -43,7 +43,7 @@ export interface UpstreamLocalityStats { /** * Endpoint granularity stats information for this locality. This information * is populated if the Server requests it by setting - * :ref:`LoadStatsResponse.report_endpoint_granularity`. + * :ref:`LoadStatsResponse.report_endpoint_granularity`. */ 'upstream_endpoint_stats'?: (_envoy_config_endpoint_v3_UpstreamEndpointStats)[]; /** @@ -56,7 +56,7 @@ export interface UpstreamLocalityStats { /** * These are stats Envoy reports to the management server at a frequency defined by - * :ref:`LoadStatsResponse.load_reporting_interval`. + * :ref:`LoadStatsResponse.load_reporting_interval`. * Stats per upstream region/zone and optionally per subzone. * [#next-free-field: 9] */ @@ -92,7 +92,7 @@ export interface UpstreamLocalityStats__Output { /** * Endpoint granularity stats information for this locality. This information * is populated if the Server requests it by setting - * :ref:`LoadStatsResponse.report_endpoint_granularity`. + * :ref:`LoadStatsResponse.report_endpoint_granularity`. */ 'upstream_endpoint_stats': (_envoy_config_endpoint_v3_UpstreamEndpointStats__Output)[]; /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts index 4977911fb..5a8e7f37f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListener.ts @@ -10,7 +10,8 @@ export interface ApiListener { /** * The type in this field determines the type of API listener. At present, the following * types are supported: - * envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager (HTTP) + * envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager (HTTP) + * envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager (HTTP) * [#next-major-version: In the v3 API, replace this Any field with a oneof containing the * specific config message for each type of API listener. We could not do this in v2 because * it would have caused circular dependencies for go protos: lds.proto depends on this file, @@ -28,7 +29,8 @@ export interface ApiListener__Output { /** * The type in this field determines the type of API listener. At present, the following * types are supported: - * envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager (HTTP) + * envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager (HTTP) + * envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager (HTTP) * [#next-major-version: In the v3 API, replace this Any field with a oneof containing the * specific config message for each type of API listener. We could not do this in v2 because * it would have caused circular dependencies for go protos: lds.proto depends on this file, diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts index 3e38fbc00..66e903b28 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts @@ -15,6 +15,7 @@ export interface Filter { /** * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.network] */ 'typed_config'?: (_google_protobuf_Any | null); /** @@ -39,6 +40,7 @@ export interface Filter__Output { /** * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.network] */ 'typed_config'?: (_google_protobuf_Any__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts index d72fa824c..e65f433c4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts @@ -81,10 +81,11 @@ export interface FilterChain { 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * Optional custom transport socket implementation to use for downstream connections. - * To setup TLS, set a transport socket with name `tls` and - * :ref:`DownstreamTlsContext ` in the `typed_config`. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`DownstreamTlsContext ` in the `typed_config`. * If no transport socket configuration is specified, new connections * will be set up with plaintext. + * [#extension-category: envoy.transport_sockets.downstream] */ 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); /** @@ -143,10 +144,11 @@ export interface FilterChain__Output { 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * Optional custom transport socket implementation to use for downstream connections. - * To setup TLS, set a transport socket with name `tls` and - * :ref:`DownstreamTlsContext ` in the `typed_config`. + * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and + * :ref:`DownstreamTlsContext ` in the `typed_config`. * If no transport socket configuration is specified, new connections * will be set up with plaintext. + * [#extension-category: envoy.transport_sockets.downstream] */ 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts index 042df6dea..259888217 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts @@ -35,9 +35,12 @@ export enum _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType { * 3. Server name (e.g. SNI for TLS protocol), * 4. Transport protocol. * 5. Application protocols (e.g. ALPN for TLS protocol). - * 6. Source type (e.g. any, local or external network). - * 7. Source IP address. - * 8. Source port. + * 6. Directly connected source IP address (this will only be different from the source IP address + * when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol + * listener filter `). + * 7. Source type (e.g. any, local or external network). + * 8. Source IP address. + * 9. Source port. * * For criteria that allow ranges or wildcards, the most specific value in any * of the configured filter chains that matches the incoming connection is going @@ -61,7 +64,7 @@ export enum _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType { * listed at the end, because that's how we want to list them in the docs. * * [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface FilterChainMatch { /** @@ -151,6 +154,12 @@ export interface FilterChainMatch { * Specifies the connection source IP match type. Can be any, local or external network. */ 'source_type'?: (_envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType | keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); + /** + * The criteria is satisfied if the directly connected source IP address of the downstream + * connection is contained in at least one of the specified subnets. If the parameter is not + * specified or the list is empty, the directly connected source IP address is ignored. + */ + 'direct_source_prefix_ranges'?: (_envoy_config_core_v3_CidrRange)[]; } /** @@ -168,9 +177,12 @@ export interface FilterChainMatch { * 3. Server name (e.g. SNI for TLS protocol), * 4. Transport protocol. * 5. Application protocols (e.g. ALPN for TLS protocol). - * 6. Source type (e.g. any, local or external network). - * 7. Source IP address. - * 8. Source port. + * 6. Directly connected source IP address (this will only be different from the source IP address + * when using a listener filter that overrides the source address, such as the :ref:`Proxy Protocol + * listener filter `). + * 7. Source type (e.g. any, local or external network). + * 8. Source IP address. + * 9. Source port. * * For criteria that allow ranges or wildcards, the most specific value in any * of the configured filter chains that matches the incoming connection is going @@ -194,7 +206,7 @@ export interface FilterChainMatch { * listed at the end, because that's how we want to list them in the docs. * * [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface FilterChainMatch__Output { /** @@ -284,4 +296,10 @@ export interface FilterChainMatch__Output { * Specifies the connection source IP match type. Can be any, local or external network. */ 'source_type': (keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); + /** + * The criteria is satisfied if the directly connected source IP address of the downstream + * connection is contained in at least one of the specified subnets. If the parameter is not + * specified or the list is empty, the directly connected source IP address is ignored. + */ + 'direct_source_prefix_ranges': (_envoy_config_core_v3_CidrRange__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts index 8e0fc55f6..3df1006b7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts @@ -12,7 +12,6 @@ import type { TrafficDirection as _envoy_config_core_v3_TrafficDirection } from import type { UdpListenerConfig as _envoy_config_listener_v3_UdpListenerConfig, UdpListenerConfig__Output as _envoy_config_listener_v3_UdpListenerConfig__Output } from '../../../../envoy/config/listener/v3/UdpListenerConfig'; import type { ApiListener as _envoy_config_listener_v3_ApiListener, ApiListener__Output as _envoy_config_listener_v3_ApiListener__Output } from '../../../../envoy/config/listener/v3/ApiListener'; import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; -import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; /** * Configuration for listener connection balancing. @@ -46,7 +45,7 @@ export interface _envoy_config_listener_v3_Listener_DeprecatedV1 { * set use_original_dst parameter to true. Default is true. * * This is deprecated. Use :ref:`Listener.bind_to_port - * ` + * ` */ 'bind_to_port'?: (_google_protobuf_BoolValue | null); } @@ -61,7 +60,7 @@ export interface _envoy_config_listener_v3_Listener_DeprecatedV1__Output { * set use_original_dst parameter to true. Default is true. * * This is deprecated. Use :ref:`Listener.bind_to_port - * ` + * ` */ 'bind_to_port': (_google_protobuf_BoolValue__Output | null); } @@ -105,7 +104,21 @@ export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig_Exac } /** - * [#next-free-field: 27] + * Configuration for envoy internal listener. All the future internal listener features should be added here. + * [#not-implemented-hide:] + */ +export interface _envoy_config_listener_v3_Listener_InternalListenerConfig { +} + +/** + * Configuration for envoy internal listener. All the future internal listener features should be added here. + * [#not-implemented-hide:] + */ +export interface _envoy_config_listener_v3_Listener_InternalListenerConfig__Output { +} + +/** + * [#next-free-field: 30] */ export interface Listener { /** @@ -122,8 +135,8 @@ export interface Listener { 'address'?: (_envoy_config_core_v3_Address | null); /** * A list of filter chains to consider for this listener. The - * :ref:`FilterChain ` with the most specific - * :ref:`FilterChainMatch ` criteria is used on a + * :ref:`FilterChain ` with the most specific + * :ref:`FilterChainMatch ` criteria is used on a * connection. * * Example using SNI for filter chain selection can be found in the @@ -158,12 +171,12 @@ export interface Listener { /** * Listener filters have the opportunity to manipulate and augment the connection metadata that * is used in connection filter chain matching, for example. These filters are run before any in - * :ref:`filter_chains `. Order matters as the + * :ref:`filter_chains `. Order matters as the * filters are processed sequentially right after a socket has been accepted by the listener, and * before a connection is created. * UDP Listener filters can be specified when the protocol in the listener socket address in - * :ref:`protocol ` is :ref:`UDP - * `. + * :ref:`protocol ` is :ref:`UDP + * `. * UDP listeners currently support a single filter. */ 'listener_filters'?: (_envoy_config_listener_v3_ListenerFilter)[]; @@ -173,7 +186,7 @@ export interface Listener { * *iptables* *TPROXY* target, in which case the original source and destination addresses and * ports are preserved on accepted connections. This flag should be used in combination with * :ref:`an original_dst ` :ref:`listener filter - * ` to mark the connections' local addresses as + * ` to mark the connections' local addresses as * "restored." This can be used to hand off each redirected connection to another listener * associated with the connection's destination address. Direct connections to the socket without * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -224,6 +237,8 @@ export interface Listener { 'listener_filters_timeout'?: (_google_protobuf_Duration | null); /** * Specifies the intended direction of the traffic relative to the local Envoy. + * This property is required on Windows for listeners using the original destination filter, + * see :ref:`Original Destination `. */ 'traffic_direction'?: (_envoy_config_core_v3_TrafficDirection | keyof typeof _envoy_config_core_v3_TrafficDirection); /** @@ -238,17 +253,15 @@ export interface Listener { 'continue_on_listener_filters_timeout'?: (boolean); /** * If the protocol in the listener socket address in :ref:`protocol - * ` is :ref:`UDP - * `, this field specifies the actual udp - * listener to create, i.e. :ref:`udp_listener_name - * ` = "raw_udp_listener" for - * creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". + * ` is :ref:`UDP + * `, this field specifies UDP + * listener specific configuration. */ 'udp_listener_config'?: (_envoy_config_listener_v3_UdpListenerConfig | null); /** * Used to represent an API listener, which is used in non-proxy clients. The type of API * exposed to the non-proxy application depends on the type of API listener. - * When this field is set, no other field except for :ref:`name` + * When this field is set, no other field except for :ref:`name` * should be set. * * .. note:: @@ -268,19 +281,16 @@ export interface Listener { * The listener's connection balancer configuration, currently only applicable to TCP listeners. * If no configuration is specified, Envoy will not attempt to balance active connections between * worker threads. + * + * In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + * by setting :ref:`use_original_dst ` in X + * and :ref:`bind_to_port ` to false in Y1 and Y2, + * it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + * enable the balance config in Y1 and Y2 to balance the connections among the workers. */ 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig | null); /** - * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and - * create one socket for each worker thread. This makes inbound connections - * distribute among worker threads roughly evenly in cases where there are a high number - * of connections. When this flag is set to false, all worker threads share one socket. - * - * Before Linux v4.19-rc1, new TCP connections may be rejected during hot restart - * (see `3rd paragraph in 'soreuseport' commit message - * `_). - * This issue was fixed by `tcp: Avoid TCP syncookie rejected by SO_REUSEPORT socket - * `_. + * Deprecated. Use `enable_reuse_port` instead. */ 'reuse_port'?: (boolean); /** @@ -288,17 +298,6 @@ export interface Listener { * emitted by this listener. */ 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; - /** - * If the protocol in the listener socket address in :ref:`protocol - * ` is :ref:`UDP - * `, this field specifies the actual udp - * writer to create, i.e. :ref:`name ` - * = "udp_default_writer" for creating a udp writer with writing in passthrough mode, - * = "udp_gso_batch_writer" for creating a udp writer with writing in batch mode. - * If not present, treat it as "udp_default_writer". - * [#not-implemented-hide:] - */ - 'udp_writer_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); /** * The maximum length a tcp listener's pending connections queue can grow to. If no value is * provided net.core.somaxconn will be used on Linux and 128 otherwise. @@ -312,14 +311,67 @@ export interface Listener { /** * Whether the listener should bind to the port. A listener that doesn't * bind can only receive connections redirected from other listeners that set - * :ref:`use_original_dst ` + * :ref:`use_original_dst ` * to true. Default is true. */ 'bind_to_port'?: (_google_protobuf_BoolValue | null); + /** + * Used to represent an internal listener which does not listen on OSI L4 address but can be used by the + * :ref:`envoy cluster ` to create a user space connection to. + * The internal listener acts as a tcp listener. It supports listener filters and network filter chains. + * The internal listener require :ref:`address ` has + * field `envoy_internal_address`. + * + * There are some limitations are derived from the implementation. The known limitations include + * + * * :ref:`ConnectionBalanceConfig ` is not + * allowed because both cluster connection and listener connection must be owned by the same dispatcher. + * * :ref:`tcp_backlog_size ` + * * :ref:`freebind ` + * * :ref:`transparent ` + * [#not-implemented-hide:] + */ + 'internal_listener'?: (_envoy_config_listener_v3_Listener_InternalListenerConfig | null); + /** + * Optional prefix to use on listener stats. If empty, the stats will be rooted at + * `listener.
.`. If non-empty, stats will be rooted at + * `listener..`. + */ + 'stat_prefix'?: (string); + /** + * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and + * create one socket for each worker thread. This makes inbound connections + * distribute among worker threads roughly evenly in cases where there are a high number + * of connections. When this flag is set to false, all worker threads share one socket. This field + * defaults to true. + * + * .. attention:: + * + * Although this field defaults to true, it has different behavior on different platforms. See + * the following text for more information. + * + * * On Linux, reuse_port is respected for both TCP and UDP listeners. It also works correctly + * with hot restart. + * * On macOS, reuse_port for TCP does not do what it does on Linux. Instead of load balancing, + * the last socket wins and receives all connections/packets. For TCP, reuse_port is force + * disabled and the user is warned. For UDP, it is enabled, but only one worker will receive + * packets. For QUIC/H3, SW routing will send packets to other workers. For "raw" UDP, only + * a single worker will currently receive packets. + * * On Windows, reuse_port for TCP has undefined behavior. It is force disabled and the user + * is warned similar to macOS. It is left enabled for UDP with undefined behavior currently. + */ + 'enable_reuse_port'?: (_google_protobuf_BoolValue | null); + /** + * The exclusive listener type and the corresponding config. + * TODO(lambdai): https://github.com/envoyproxy/envoy/issues/15372 + * Will create and add TcpListenerConfig. Will add UdpListenerConfig and ApiListener. + * [#not-implemented-hide:] + */ + 'listener_specifier'?: "internal_listener"; } /** - * [#next-free-field: 27] + * [#next-free-field: 30] */ export interface Listener__Output { /** @@ -336,8 +388,8 @@ export interface Listener__Output { 'address': (_envoy_config_core_v3_Address__Output | null); /** * A list of filter chains to consider for this listener. The - * :ref:`FilterChain ` with the most specific - * :ref:`FilterChainMatch ` criteria is used on a + * :ref:`FilterChain ` with the most specific + * :ref:`FilterChainMatch ` criteria is used on a * connection. * * Example using SNI for filter chain selection can be found in the @@ -372,12 +424,12 @@ export interface Listener__Output { /** * Listener filters have the opportunity to manipulate and augment the connection metadata that * is used in connection filter chain matching, for example. These filters are run before any in - * :ref:`filter_chains `. Order matters as the + * :ref:`filter_chains `. Order matters as the * filters are processed sequentially right after a socket has been accepted by the listener, and * before a connection is created. * UDP Listener filters can be specified when the protocol in the listener socket address in - * :ref:`protocol ` is :ref:`UDP - * `. + * :ref:`protocol ` is :ref:`UDP + * `. * UDP listeners currently support a single filter. */ 'listener_filters': (_envoy_config_listener_v3_ListenerFilter__Output)[]; @@ -387,7 +439,7 @@ export interface Listener__Output { * *iptables* *TPROXY* target, in which case the original source and destination addresses and * ports are preserved on accepted connections. This flag should be used in combination with * :ref:`an original_dst ` :ref:`listener filter - * ` to mark the connections' local addresses as + * ` to mark the connections' local addresses as * "restored." This can be used to hand off each redirected connection to another listener * associated with the connection's destination address. Direct connections to the socket without * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are @@ -438,6 +490,8 @@ export interface Listener__Output { 'listener_filters_timeout': (_google_protobuf_Duration__Output | null); /** * Specifies the intended direction of the traffic relative to the local Envoy. + * This property is required on Windows for listeners using the original destination filter, + * see :ref:`Original Destination `. */ 'traffic_direction': (keyof typeof _envoy_config_core_v3_TrafficDirection); /** @@ -452,17 +506,15 @@ export interface Listener__Output { 'continue_on_listener_filters_timeout': (boolean); /** * If the protocol in the listener socket address in :ref:`protocol - * ` is :ref:`UDP - * `, this field specifies the actual udp - * listener to create, i.e. :ref:`udp_listener_name - * ` = "raw_udp_listener" for - * creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". + * ` is :ref:`UDP + * `, this field specifies UDP + * listener specific configuration. */ 'udp_listener_config': (_envoy_config_listener_v3_UdpListenerConfig__Output | null); /** * Used to represent an API listener, which is used in non-proxy clients. The type of API * exposed to the non-proxy application depends on the type of API listener. - * When this field is set, no other field except for :ref:`name` + * When this field is set, no other field except for :ref:`name` * should be set. * * .. note:: @@ -482,19 +534,16 @@ export interface Listener__Output { * The listener's connection balancer configuration, currently only applicable to TCP listeners. * If no configuration is specified, Envoy will not attempt to balance active connections between * worker threads. + * + * In the scenario that the listener X redirects all the connections to the listeners Y1 and Y2 + * by setting :ref:`use_original_dst ` in X + * and :ref:`bind_to_port ` to false in Y1 and Y2, + * it is recommended to disable the balance config in listener X to avoid the cost of balancing, and + * enable the balance config in Y1 and Y2 to balance the connections among the workers. */ 'connection_balance_config': (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output | null); /** - * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and - * create one socket for each worker thread. This makes inbound connections - * distribute among worker threads roughly evenly in cases where there are a high number - * of connections. When this flag is set to false, all worker threads share one socket. - * - * Before Linux v4.19-rc1, new TCP connections may be rejected during hot restart - * (see `3rd paragraph in 'soreuseport' commit message - * `_). - * This issue was fixed by `tcp: Avoid TCP syncookie rejected by SO_REUSEPORT socket - * `_. + * Deprecated. Use `enable_reuse_port` instead. */ 'reuse_port': (boolean); /** @@ -502,17 +551,6 @@ export interface Listener__Output { * emitted by this listener. */ 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; - /** - * If the protocol in the listener socket address in :ref:`protocol - * ` is :ref:`UDP - * `, this field specifies the actual udp - * writer to create, i.e. :ref:`name ` - * = "udp_default_writer" for creating a udp writer with writing in passthrough mode, - * = "udp_gso_batch_writer" for creating a udp writer with writing in batch mode. - * If not present, treat it as "udp_default_writer". - * [#not-implemented-hide:] - */ - 'udp_writer_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); /** * The maximum length a tcp listener's pending connections queue can grow to. If no value is * provided net.core.somaxconn will be used on Linux and 128 otherwise. @@ -526,8 +564,61 @@ export interface Listener__Output { /** * Whether the listener should bind to the port. A listener that doesn't * bind can only receive connections redirected from other listeners that set - * :ref:`use_original_dst ` + * :ref:`use_original_dst ` * to true. Default is true. */ 'bind_to_port': (_google_protobuf_BoolValue__Output | null); + /** + * Used to represent an internal listener which does not listen on OSI L4 address but can be used by the + * :ref:`envoy cluster ` to create a user space connection to. + * The internal listener acts as a tcp listener. It supports listener filters and network filter chains. + * The internal listener require :ref:`address ` has + * field `envoy_internal_address`. + * + * There are some limitations are derived from the implementation. The known limitations include + * + * * :ref:`ConnectionBalanceConfig ` is not + * allowed because both cluster connection and listener connection must be owned by the same dispatcher. + * * :ref:`tcp_backlog_size ` + * * :ref:`freebind ` + * * :ref:`transparent ` + * [#not-implemented-hide:] + */ + 'internal_listener'?: (_envoy_config_listener_v3_Listener_InternalListenerConfig__Output | null); + /** + * Optional prefix to use on listener stats. If empty, the stats will be rooted at + * `listener.
.`. If non-empty, stats will be rooted at + * `listener..`. + */ + 'stat_prefix': (string); + /** + * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and + * create one socket for each worker thread. This makes inbound connections + * distribute among worker threads roughly evenly in cases where there are a high number + * of connections. When this flag is set to false, all worker threads share one socket. This field + * defaults to true. + * + * .. attention:: + * + * Although this field defaults to true, it has different behavior on different platforms. See + * the following text for more information. + * + * * On Linux, reuse_port is respected for both TCP and UDP listeners. It also works correctly + * with hot restart. + * * On macOS, reuse_port for TCP does not do what it does on Linux. Instead of load balancing, + * the last socket wins and receives all connections/packets. For TCP, reuse_port is force + * disabled and the user is warned. For UDP, it is enabled, but only one worker will receive + * packets. For QUIC/H3, SW routing will send packets to other workers. For "raw" UDP, only + * a single worker will currently receive packets. + * * On Windows, reuse_port for TCP has undefined behavior. It is force disabled and the user + * is warned similar to macOS. It is left enabled for UDP with undefined behavior currently. + */ + 'enable_reuse_port': (_google_protobuf_BoolValue__Output | null); + /** + * The exclusive listener type and the corresponding config. + * TODO(lambdai): https://github.com/envoyproxy/envoy/issues/15372 + * Will create and add TcpListenerConfig. Will add UdpListenerConfig and ApiListener. + * [#not-implemented-hide:] + */ + 'listener_specifier': "internal_listener"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts index beba60dce..be7242849 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts @@ -9,17 +9,18 @@ export interface ListenerFilter { * :ref:`supported filter `. */ 'name'?: (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.listener,envoy.filters.udp_listener] + */ 'typed_config'?: (_google_protobuf_Any | null); /** * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - * See :ref:`ListenerFilterChainMatchPredicate ` + * See :ref:`ListenerFilterChainMatchPredicate ` * for further examples. */ 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate | null); - /** - * Filter specific configuration which depends on the filter being instantiated. - * See the supported filters for further documentation. - */ 'config_type'?: "typed_config"; } @@ -29,16 +30,17 @@ export interface ListenerFilter__Output { * :ref:`supported filter `. */ 'name': (string); + /** + * Filter specific configuration which depends on the filter being + * instantiated. See the supported filters for further documentation. + * [#extension-category: envoy.filters.listener,envoy.filters.udp_listener] + */ 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Optional match predicate used to disable the filter. The filter is enabled when this field is empty. - * See :ref:`ListenerFilterChainMatchPredicate ` + * See :ref:`ListenerFilterChainMatchPredicate ` * for further examples. */ 'filter_disabled': (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output | null); - /** - * Filter specific configuration which depends on the filter being instantiated. - * See the supported filters for further documentation. - */ 'config_type': "typed_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts index 34f937fa1..bb743a29d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilterChainMatchPredicate.ts @@ -45,7 +45,7 @@ export interface _envoy_config_listener_v3_ListenerFilterChainMatchPredicate_Mat * rules: * - destination_port_range: * start: 3306 - * end: 3306 + * end: 3307 * - destination_port_range: * start: 15000 * end: 15001 @@ -101,7 +101,7 @@ export interface ListenerFilterChainMatchPredicate { * rules: * - destination_port_range: * start: 3306 - * end: 3306 + * end: 3307 * - destination_port_range: * start: 15000 * end: 15001 diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts new file mode 100644 index 000000000..5e01a772f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts @@ -0,0 +1,97 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/quic_config.proto + +import type { QuicProtocolOptions as _envoy_config_core_v3_QuicProtocolOptions, QuicProtocolOptions__Output as _envoy_config_core_v3_QuicProtocolOptions__Output } from '../../../../envoy/config/core/v3/QuicProtocolOptions'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { RuntimeFeatureFlag as _envoy_config_core_v3_RuntimeFeatureFlag, RuntimeFeatureFlag__Output as _envoy_config_core_v3_RuntimeFeatureFlag__Output } from '../../../../envoy/config/core/v3/RuntimeFeatureFlag'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configuration specific to the UDP QUIC listener. + * [#next-free-field: 8] + */ +export interface QuicProtocolOptions { + 'quic_protocol_options'?: (_envoy_config_core_v3_QuicProtocolOptions | null); + /** + * Maximum number of milliseconds that connection will be alive when there is + * no network activity. 300000ms if not specified. + */ + 'idle_timeout'?: (_google_protobuf_Duration | null); + /** + * Connection timeout in milliseconds before the crypto handshake is finished. + * 20000ms if not specified. + */ + 'crypto_handshake_timeout'?: (_google_protobuf_Duration | null); + /** + * Runtime flag that controls whether the listener is enabled or not. If not specified, defaults + * to enabled. + */ + 'enabled'?: (_envoy_config_core_v3_RuntimeFeatureFlag | null); + /** + * A multiplier to number of connections which is used to determine how many packets to read per + * event loop. A reasonable number should allow the listener to process enough payload but not + * starve TCP and other UDP sockets and also prevent long event loop duration. + * The default value is 32. This means if there are N QUIC connections, the total number of + * packets to read in each read event will be 32 * N. + * The actual number of packets to read in total by the UDP listener is also + * bound by 6000, regardless of this field or how many connections there are. + */ + 'packets_to_read_to_connection_count_ratio'?: (_google_protobuf_UInt32Value | null); + /** + * Configure which implementation of `quic::QuicCryptoClientStreamBase` to be used for this listener. + * If not specified the :ref:`QUICHE default one configured by ` will be used. + * [#extension-category: envoy.quic.server.crypto_stream] + */ + 'crypto_stream_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Configure which implementation of `quic::ProofSource` to be used for this listener. + * If not specified the :ref:`default one configured by ` will be used. + * [#extension-category: envoy.quic.proof_source] + */ + 'proof_source_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Configuration specific to the UDP QUIC listener. + * [#next-free-field: 8] + */ +export interface QuicProtocolOptions__Output { + 'quic_protocol_options': (_envoy_config_core_v3_QuicProtocolOptions__Output | null); + /** + * Maximum number of milliseconds that connection will be alive when there is + * no network activity. 300000ms if not specified. + */ + 'idle_timeout': (_google_protobuf_Duration__Output | null); + /** + * Connection timeout in milliseconds before the crypto handshake is finished. + * 20000ms if not specified. + */ + 'crypto_handshake_timeout': (_google_protobuf_Duration__Output | null); + /** + * Runtime flag that controls whether the listener is enabled or not. If not specified, defaults + * to enabled. + */ + 'enabled': (_envoy_config_core_v3_RuntimeFeatureFlag__Output | null); + /** + * A multiplier to number of connections which is used to determine how many packets to read per + * event loop. A reasonable number should allow the listener to process enough payload but not + * starve TCP and other UDP sockets and also prevent long event loop duration. + * The default value is 32. This means if there are N QUIC connections, the total number of + * packets to read in each read event will be 32 * N. + * The actual number of packets to read in total by the UDP listener is also + * bound by 6000, regardless of this field or how many connections there are. + */ + 'packets_to_read_to_connection_count_ratio': (_google_protobuf_UInt32Value__Output | null); + /** + * Configure which implementation of `quic::QuicCryptoClientStreamBase` to be used for this listener. + * If not specified the :ref:`QUICHE default one configured by ` will be used. + * [#extension-category: envoy.quic.server.crypto_stream] + */ + 'crypto_stream_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Configure which implementation of `quic::ProofSource` to be used for this listener. + * If not specified the :ref:`default one configured by ` will be used. + * [#extension-category: envoy.quic.proof_source] + */ + 'proof_source_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts index 2ac4463fb..f4c220e20 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts @@ -1,33 +1,48 @@ // Original file: deps/envoy-api/envoy/config/listener/v3/udp_listener_config.proto -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { UdpSocketConfig as _envoy_config_core_v3_UdpSocketConfig, UdpSocketConfig__Output as _envoy_config_core_v3_UdpSocketConfig__Output } from '../../../../envoy/config/core/v3/UdpSocketConfig'; +import type { QuicProtocolOptions as _envoy_config_listener_v3_QuicProtocolOptions, QuicProtocolOptions__Output as _envoy_config_listener_v3_QuicProtocolOptions__Output } from '../../../../envoy/config/listener/v3/QuicProtocolOptions'; +/** + * [#next-free-field: 8] + */ export interface UdpListenerConfig { /** - * Used to look up UDP listener factory, matches "raw_udp_listener" or - * "quic_listener" to create a specific udp listener. - * If not specified, treat as "raw_udp_listener". + * UDP socket configuration for the listener. The default for + * :ref:`prefer_gro ` is false for + * listener sockets. If receiving a large amount of datagrams from a small number of sources, it + * may be worthwhile to enable this option after performance testing. */ - 'udp_listener_name'?: (string); - 'typed_config'?: (_google_protobuf_Any | null); + 'downstream_socket_config'?: (_envoy_config_core_v3_UdpSocketConfig | null); /** - * Used to create a specific listener factory. To some factory, e.g. - * "raw_udp_listener", config is not needed. + * Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set + * to the default object to enable QUIC without modifying any additional options. + * + * .. warning:: + * QUIC support is currently alpha and should be used with caution. Please + * see :ref:`here ` for details. */ - 'config_type'?: "typed_config"; + 'quic_options'?: (_envoy_config_listener_v3_QuicProtocolOptions | null); } +/** + * [#next-free-field: 8] + */ export interface UdpListenerConfig__Output { /** - * Used to look up UDP listener factory, matches "raw_udp_listener" or - * "quic_listener" to create a specific udp listener. - * If not specified, treat as "raw_udp_listener". + * UDP socket configuration for the listener. The default for + * :ref:`prefer_gro ` is false for + * listener sockets. If receiving a large amount of datagrams from a small number of sources, it + * may be worthwhile to enable this option after performance testing. */ - 'udp_listener_name': (string); - 'typed_config'?: (_google_protobuf_Any__Output | null); + 'downstream_socket_config': (_envoy_config_core_v3_UdpSocketConfig__Output | null); /** - * Used to create a specific listener factory. To some factory, e.g. - * "raw_udp_listener", config is not needed. + * Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set + * to the default object to enable QUIC without modifying any additional options. + * + * .. warning:: + * QUIC support is currently alpha and should be used with caution. Please + * see :ref:`here ` for details. */ - 'config_type': "typed_config"; + 'quic_options': (_envoy_config_listener_v3_QuicProtocolOptions__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts new file mode 100644 index 000000000..4cf705f10 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts @@ -0,0 +1,65 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. + * The sink emits stats with `DogStatsD `_ + * compatible tags. Tags are configurable via :ref:`StatsConfig + * `. + * [#extension: envoy.stat_sinks.dog_statsd] + */ +export interface DogStatsdSink { + /** + * The UDP address of a running DogStatsD compliant listener. If specified, + * statistics will be flushed to this address. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * Optional custom metric name prefix. See :ref:`StatsdSink's prefix field + * ` for more details. + */ + 'prefix'?: (string); + /** + * Optional max datagram size to use when sending UDP messages. By default Envoy + * will emit one metric per datagram. By specifying a max-size larger than a single + * metric, Envoy will emit multiple, new-line separated metrics. The max datagram + * size should not exceed your network's MTU. + * + * Note that this value may not be respected if smaller than a single metric. + */ + 'max_bytes_per_datagram'?: (_google_protobuf_UInt64Value | null); + 'dog_statsd_specifier'?: "address"; +} + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. + * The sink emits stats with `DogStatsD `_ + * compatible tags. Tags are configurable via :ref:`StatsConfig + * `. + * [#extension: envoy.stat_sinks.dog_statsd] + */ +export interface DogStatsdSink__Output { + /** + * The UDP address of a running DogStatsD compliant listener. If specified, + * statistics will be flushed to this address. + */ + 'address'?: (_envoy_config_core_v3_Address__Output | null); + /** + * Optional custom metric name prefix. See :ref:`StatsdSink's prefix field + * ` for more details. + */ + 'prefix': (string); + /** + * Optional max datagram size to use when sending UDP messages. By default Envoy + * will emit one metric per datagram. By specifying a max-size larger than a single + * metric, Envoy will emit multiple, new-line separated metrics. The max datagram + * size should not exceed your network's MTU. + * + * Note that this value may not be respected if smaller than a single metric. + */ + 'max_bytes_per_datagram': (_google_protobuf_UInt64Value__Output | null); + 'dog_statsd_specifier': "address"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts new file mode 100644 index 000000000..036958a49 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts @@ -0,0 +1,35 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; + +/** + * Specifies a matcher for stats and the buckets that matching stats should use. + */ +export interface HistogramBucketSettings { + /** + * The stats that this rule applies to. The match is applied to the original stat name + * before tag-extraction, for example `cluster.exampleclustername.upstream_cx_length_ms`. + */ + 'match'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * Each value is the upper bound of a bucket. Each bucket must be greater than 0 and unique. + * The order of the buckets does not matter. + */ + 'buckets'?: (number | string)[]; +} + +/** + * Specifies a matcher for stats and the buckets that matching stats should use. + */ +export interface HistogramBucketSettings__Output { + /** + * The stats that this rule applies to. The match is applied to the original stat name + * before tag-extraction, for example `cluster.exampleclustername.upstream_cx_length_ms`. + */ + 'match': (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * Each value is the upper bound of a bucket. Each bucket must be greater than 0 and unique. + * The order of the buckets does not matter. + */ + 'buckets': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts new file mode 100644 index 000000000..b8fb2ed8e --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts @@ -0,0 +1,61 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.hystrix* sink. + * The sink emits stats in `text/event-stream + * `_ + * formatted stream for use by `Hystrix dashboard + * `_. + * + * Note that only a single HystrixSink should be configured. + * + * Streaming is started through an admin endpoint :http:get:`/hystrix_event_stream`. + * [#extension: envoy.stat_sinks.hystrix] + */ +export interface HystrixSink { + /** + * The number of buckets the rolling statistical window is divided into. + * + * Each time the sink is flushed, all relevant Envoy statistics are sampled and + * added to the rolling window (removing the oldest samples in the window + * in the process). The sink then outputs the aggregate statistics across the + * current rolling window to the event stream(s). + * + * rolling_window(ms) = stats_flush_interval(ms) * num_of_buckets + * + * More detailed explanation can be found in `Hystrix wiki + * `_. + */ + 'num_buckets'?: (number | string | Long); +} + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.hystrix* sink. + * The sink emits stats in `text/event-stream + * `_ + * formatted stream for use by `Hystrix dashboard + * `_. + * + * Note that only a single HystrixSink should be configured. + * + * Streaming is started through an admin endpoint :http:get:`/hystrix_event_stream`. + * [#extension: envoy.stat_sinks.hystrix] + */ +export interface HystrixSink__Output { + /** + * The number of buckets the rolling statistical window is divided into. + * + * Each time the sink is flushed, all relevant Envoy statistics are sampled and + * added to the rolling window (removing the oldest samples in the window + * in the process). The sink then outputs the aggregate statistics across the + * current rolling window to the event stream(s). + * + * rolling_window(ms) = stats_flush_interval(ms) * num_of_buckets + * + * More detailed explanation can be found in `Hystrix wiki + * `_. + */ + 'num_buckets': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts new file mode 100644 index 000000000..df5d7c7e4 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts @@ -0,0 +1,148 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { TagSpecifier as _envoy_config_metrics_v3_TagSpecifier, TagSpecifier__Output as _envoy_config_metrics_v3_TagSpecifier__Output } from '../../../../envoy/config/metrics/v3/TagSpecifier'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; +import type { StatsMatcher as _envoy_config_metrics_v3_StatsMatcher, StatsMatcher__Output as _envoy_config_metrics_v3_StatsMatcher__Output } from '../../../../envoy/config/metrics/v3/StatsMatcher'; +import type { HistogramBucketSettings as _envoy_config_metrics_v3_HistogramBucketSettings, HistogramBucketSettings__Output as _envoy_config_metrics_v3_HistogramBucketSettings__Output } from '../../../../envoy/config/metrics/v3/HistogramBucketSettings'; + +/** + * Statistics configuration such as tagging. + */ +export interface StatsConfig { + /** + * Each stat name is iteratively processed through these tag specifiers. + * When a tag is matched, the first capture group is removed from the name so + * later :ref:`TagSpecifiers ` cannot match that + * same portion of the match. + */ + 'stats_tags'?: (_envoy_config_metrics_v3_TagSpecifier)[]; + /** + * Use all default tag regexes specified in Envoy. These can be combined with + * custom tags specified in :ref:`stats_tags + * `. They will be processed before + * the custom tags. + * + * .. note:: + * + * If any default tags are specified twice, the config will be considered + * invalid. + * + * See :repo:`well_known_names.h ` for a list of the + * default tags in Envoy. + * + * If not provided, the value is assumed to be true. + */ + 'use_all_default_tags'?: (_google_protobuf_BoolValue | null); + /** + * Inclusion/exclusion matcher for stat name creation. If not provided, all stats are instantiated + * as normal. Preventing the instantiation of certain families of stats can improve memory + * performance for Envoys running especially large configs. + * + * .. warning:: + * Excluding stats may affect Envoy's behavior in undocumented ways. See + * `issue #8771 `_ for more information. + * If any unexpected behavior changes are observed, please open a new issue immediately. + */ + 'stats_matcher'?: (_envoy_config_metrics_v3_StatsMatcher | null); + /** + * Defines rules for setting the histogram buckets. Rules are evaluated in order, and the first + * match is applied. If no match is found (or if no rules are set), the following default buckets + * are used: + * + * .. code-block:: json + * + * [ + * 0.5, + * 1, + * 5, + * 10, + * 25, + * 50, + * 100, + * 250, + * 500, + * 1000, + * 2500, + * 5000, + * 10000, + * 30000, + * 60000, + * 300000, + * 600000, + * 1800000, + * 3600000 + * ] + */ + 'histogram_bucket_settings'?: (_envoy_config_metrics_v3_HistogramBucketSettings)[]; +} + +/** + * Statistics configuration such as tagging. + */ +export interface StatsConfig__Output { + /** + * Each stat name is iteratively processed through these tag specifiers. + * When a tag is matched, the first capture group is removed from the name so + * later :ref:`TagSpecifiers ` cannot match that + * same portion of the match. + */ + 'stats_tags': (_envoy_config_metrics_v3_TagSpecifier__Output)[]; + /** + * Use all default tag regexes specified in Envoy. These can be combined with + * custom tags specified in :ref:`stats_tags + * `. They will be processed before + * the custom tags. + * + * .. note:: + * + * If any default tags are specified twice, the config will be considered + * invalid. + * + * See :repo:`well_known_names.h ` for a list of the + * default tags in Envoy. + * + * If not provided, the value is assumed to be true. + */ + 'use_all_default_tags': (_google_protobuf_BoolValue__Output | null); + /** + * Inclusion/exclusion matcher for stat name creation. If not provided, all stats are instantiated + * as normal. Preventing the instantiation of certain families of stats can improve memory + * performance for Envoys running especially large configs. + * + * .. warning:: + * Excluding stats may affect Envoy's behavior in undocumented ways. See + * `issue #8771 `_ for more information. + * If any unexpected behavior changes are observed, please open a new issue immediately. + */ + 'stats_matcher': (_envoy_config_metrics_v3_StatsMatcher__Output | null); + /** + * Defines rules for setting the histogram buckets. Rules are evaluated in order, and the first + * match is applied. If no match is found (or if no rules are set), the following default buckets + * are used: + * + * .. code-block:: json + * + * [ + * 0.5, + * 1, + * 5, + * 10, + * 25, + * 50, + * 100, + * 250, + * 500, + * 1000, + * 2500, + * 5000, + * 10000, + * 30000, + * 60000, + * 300000, + * 600000, + * 1800000, + * 3600000 + * ] + */ + 'histogram_bucket_settings': (_envoy_config_metrics_v3_HistogramBucketSettings__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts new file mode 100644 index 000000000..9df9e9529 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts @@ -0,0 +1,47 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { ListStringMatcher as _envoy_type_matcher_v3_ListStringMatcher, ListStringMatcher__Output as _envoy_type_matcher_v3_ListStringMatcher__Output } from '../../../../envoy/type/matcher/v3/ListStringMatcher'; + +/** + * Configuration for disabling stat instantiation. + */ +export interface StatsMatcher { + /** + * If `reject_all` is true, then all stats are disabled. If `reject_all` is false, then all + * stats are enabled. + */ + 'reject_all'?: (boolean); + /** + * Exclusive match. All stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'exclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher | null); + /** + * Inclusive match. No stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'inclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher | null); + 'stats_matcher'?: "reject_all"|"exclusion_list"|"inclusion_list"; +} + +/** + * Configuration for disabling stat instantiation. + */ +export interface StatsMatcher__Output { + /** + * If `reject_all` is true, then all stats are disabled. If `reject_all` is false, then all + * stats are enabled. + */ + 'reject_all'?: (boolean); + /** + * Exclusive match. All stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'exclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher__Output | null); + /** + * Inclusive match. No stats are enabled except for those matching one of the supplied + * StringMatcher protos. + */ + 'inclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher__Output | null); + 'stats_matcher': "reject_all"|"exclusion_list"|"inclusion_list"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts new file mode 100644 index 000000000..3eb8926fa --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +/** + * Configuration for pluggable stats sinks. + */ +export interface StatsSink { + /** + * The name of the stats sink to instantiate. The name must match a supported + * stats sink. + * See the :ref:`extensions listed in typed_config below ` for the default list of available stats sink. + * Sinks optionally support tagged/multiple dimensional metrics. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Stats sink specific configuration which depends on the sink being instantiated. See + * :ref:`StatsdSink ` for an example. + * [#extension-category: envoy.stats_sinks] + */ + 'config_type'?: "typed_config"; +} + +/** + * Configuration for pluggable stats sinks. + */ +export interface StatsSink__Output { + /** + * The name of the stats sink to instantiate. The name must match a supported + * stats sink. + * See the :ref:`extensions listed in typed_config below ` for the default list of available stats sink. + * Sinks optionally support tagged/multiple dimensional metrics. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Stats sink specific configuration which depends on the sink being instantiated. See + * :ref:`StatsdSink ` for an example. + * [#extension-category: envoy.stats_sinks] + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts new file mode 100644 index 000000000..69d978920 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts @@ -0,0 +1,103 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.statsd* sink. This sink does not support + * tagged metrics. + * [#extension: envoy.stat_sinks.statsd] + */ +export interface StatsdSink { + /** + * The UDP address of a running `statsd `_ + * compliant listener. If specified, statistics will be flushed to this + * address. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * The name of a cluster that is running a TCP `statsd + * `_ compliant listener. If specified, + * Envoy will connect to this cluster to flush statistics. + */ + 'tcp_cluster_name'?: (string); + /** + * Optional custom prefix for StatsdSink. If + * specified, this will override the default prefix. + * For example: + * + * .. code-block:: json + * + * { + * "prefix" : "envoy-prod" + * } + * + * will change emitted stats to + * + * .. code-block:: cpp + * + * envoy-prod.test_counter:1|c + * envoy-prod.test_timer:5|ms + * + * Note that the default prefix, "envoy", will be used if a prefix is not + * specified. + * + * Stats with default prefix: + * + * .. code-block:: cpp + * + * envoy.test_counter:1|c + * envoy.test_timer:5|ms + */ + 'prefix'?: (string); + 'statsd_specifier'?: "address"|"tcp_cluster_name"; +} + +/** + * Stats configuration proto schema for built-in *envoy.stat_sinks.statsd* sink. This sink does not support + * tagged metrics. + * [#extension: envoy.stat_sinks.statsd] + */ +export interface StatsdSink__Output { + /** + * The UDP address of a running `statsd `_ + * compliant listener. If specified, statistics will be flushed to this + * address. + */ + 'address'?: (_envoy_config_core_v3_Address__Output | null); + /** + * The name of a cluster that is running a TCP `statsd + * `_ compliant listener. If specified, + * Envoy will connect to this cluster to flush statistics. + */ + 'tcp_cluster_name'?: (string); + /** + * Optional custom prefix for StatsdSink. If + * specified, this will override the default prefix. + * For example: + * + * .. code-block:: json + * + * { + * "prefix" : "envoy-prod" + * } + * + * will change emitted stats to + * + * .. code-block:: cpp + * + * envoy-prod.test_counter:1|c + * envoy-prod.test_timer:5|ms + * + * Note that the default prefix, "envoy", will be used if a prefix is not + * specified. + * + * Stats with default prefix: + * + * .. code-block:: cpp + * + * envoy.test_counter:1|c + * envoy.test_timer:5|ms + */ + 'prefix': (string); + 'statsd_specifier': "address"|"tcp_cluster_name"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts new file mode 100644 index 000000000..9b0bb2467 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts @@ -0,0 +1,174 @@ +// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto + + +/** + * Designates a tag name and value pair. The value may be either a fixed value + * or a regex providing the value via capture groups. The specified tag will be + * unconditionally set if a fixed value, otherwise it will only be set if one + * or more capture groups in the regex match. + */ +export interface TagSpecifier { + /** + * Attaches an identifier to the tag values to identify the tag being in the + * sink. Envoy has a set of default names and regexes to extract dynamic + * portions of existing stats, which can be found in :repo:`well_known_names.h + * ` in the Envoy repository. If a :ref:`tag_name + * ` is provided in the config and + * neither :ref:`regex ` or + * :ref:`fixed_value ` were specified, + * Envoy will attempt to find that name in its set of defaults and use the accompanying regex. + * + * .. note:: + * + * It is invalid to specify the same tag name twice in a config. + */ + 'tag_name'?: (string); + /** + * Designates a tag to strip from the tag extracted name and provide as a named + * tag value for all statistics. This will only occur if any part of the name + * matches the regex provided with one or more capture groups. + * + * The first capture group identifies the portion of the name to remove. The + * second capture group (which will normally be nested inside the first) will + * designate the value of the tag for the statistic. If no second capture + * group is provided, the first will also be used to set the value of the tag. + * All other capture groups will be ignored. + * + * Example 1. a stat name ``cluster.foo_cluster.upstream_rq_timeout`` and + * one tag specifier: + * + * .. code-block:: json + * + * { + * "tag_name": "envoy.cluster_name", + * "regex": "^cluster\\.((.+?)\\.)" + * } + * + * Note that the regex will remove ``foo_cluster.`` making the tag extracted + * name ``cluster.upstream_rq_timeout`` and the tag value for + * ``envoy.cluster_name`` will be ``foo_cluster`` (note: there will be no + * ``.`` character because of the second capture group). + * + * Example 2. a stat name + * ``http.connection_manager_1.user_agent.ios.downstream_cx_total`` and two + * tag specifiers: + * + * .. code-block:: json + * + * [ + * { + * "tag_name": "envoy.http_user_agent", + * "regex": "^http(?=\\.).*?\\.user_agent\\.((.+?)\\.)\\w+?$" + * }, + * { + * "tag_name": "envoy.http_conn_manager_prefix", + * "regex": "^http\\.((.*?)\\.)" + * } + * ] + * + * The two regexes of the specifiers will be processed in the definition order. + * + * The first regex will remove ``ios.``, leaving the tag extracted name + * ``http.connection_manager_1.user_agent.downstream_cx_total``. The tag + * ``envoy.http_user_agent`` will be added with tag value ``ios``. + * + * The second regex will remove ``connection_manager_1.`` from the tag + * extracted name produced by the first regex + * ``http.connection_manager_1.user_agent.downstream_cx_total``, leaving + * ``http.user_agent.downstream_cx_total`` as the tag extracted name. The tag + * ``envoy.http_conn_manager_prefix`` will be added with the tag value + * ``connection_manager_1``. + */ + 'regex'?: (string); + /** + * Specifies a fixed tag value for the ``tag_name``. + */ + 'fixed_value'?: (string); + 'tag_value'?: "regex"|"fixed_value"; +} + +/** + * Designates a tag name and value pair. The value may be either a fixed value + * or a regex providing the value via capture groups. The specified tag will be + * unconditionally set if a fixed value, otherwise it will only be set if one + * or more capture groups in the regex match. + */ +export interface TagSpecifier__Output { + /** + * Attaches an identifier to the tag values to identify the tag being in the + * sink. Envoy has a set of default names and regexes to extract dynamic + * portions of existing stats, which can be found in :repo:`well_known_names.h + * ` in the Envoy repository. If a :ref:`tag_name + * ` is provided in the config and + * neither :ref:`regex ` or + * :ref:`fixed_value ` were specified, + * Envoy will attempt to find that name in its set of defaults and use the accompanying regex. + * + * .. note:: + * + * It is invalid to specify the same tag name twice in a config. + */ + 'tag_name': (string); + /** + * Designates a tag to strip from the tag extracted name and provide as a named + * tag value for all statistics. This will only occur if any part of the name + * matches the regex provided with one or more capture groups. + * + * The first capture group identifies the portion of the name to remove. The + * second capture group (which will normally be nested inside the first) will + * designate the value of the tag for the statistic. If no second capture + * group is provided, the first will also be used to set the value of the tag. + * All other capture groups will be ignored. + * + * Example 1. a stat name ``cluster.foo_cluster.upstream_rq_timeout`` and + * one tag specifier: + * + * .. code-block:: json + * + * { + * "tag_name": "envoy.cluster_name", + * "regex": "^cluster\\.((.+?)\\.)" + * } + * + * Note that the regex will remove ``foo_cluster.`` making the tag extracted + * name ``cluster.upstream_rq_timeout`` and the tag value for + * ``envoy.cluster_name`` will be ``foo_cluster`` (note: there will be no + * ``.`` character because of the second capture group). + * + * Example 2. a stat name + * ``http.connection_manager_1.user_agent.ios.downstream_cx_total`` and two + * tag specifiers: + * + * .. code-block:: json + * + * [ + * { + * "tag_name": "envoy.http_user_agent", + * "regex": "^http(?=\\.).*?\\.user_agent\\.((.+?)\\.)\\w+?$" + * }, + * { + * "tag_name": "envoy.http_conn_manager_prefix", + * "regex": "^http\\.((.*?)\\.)" + * } + * ] + * + * The two regexes of the specifiers will be processed in the definition order. + * + * The first regex will remove ``ios.``, leaving the tag extracted name + * ``http.connection_manager_1.user_agent.downstream_cx_total``. The tag + * ``envoy.http_user_agent`` will be added with tag value ``ios``. + * + * The second regex will remove ``connection_manager_1.`` from the tag + * extracted name produced by the first regex + * ``http.connection_manager_1.user_agent.downstream_cx_total``, leaving + * ``http.user_agent.downstream_cx_total`` as the tag extracted name. The tag + * ``envoy.http_conn_manager_prefix`` will be added with the tag value + * ``connection_manager_1``. + */ + 'regex'?: (string); + /** + * Specifies a fixed tag value for the ``tag_name``. + */ + 'fixed_value'?: (string); + 'tag_value': "regex"|"fixed_value"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts new file mode 100644 index 000000000..b3fbe1459 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts @@ -0,0 +1,50 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + + +/** + * Configuration for which accounts the WatermarkBuffer Factories should + * track. + */ +export interface BufferFactoryConfig { + /** + * The minimum power of two at which Envoy starts tracking an account. + * + * Envoy has 8 power of two buckets starting with the provided exponent below. + * Concretely the 1st bucket contains accounts for streams that use + * [2^minimum_account_to_track_power_of_two, + * 2^(minimum_account_to_track_power_of_two + 1)) bytes. + * With the 8th bucket tracking accounts + * >= 128 * 2^minimum_account_to_track_power_of_two. + * + * The maximum value is 56, since we're using uint64_t for bytes counting, + * and that's the last value that would use the 8 buckets. In practice, + * we don't expect the proxy to be holding 2^56 bytes. + * + * If omitted, Envoy should not do any tracking. + */ + 'minimum_account_to_track_power_of_two'?: (number); +} + +/** + * Configuration for which accounts the WatermarkBuffer Factories should + * track. + */ +export interface BufferFactoryConfig__Output { + /** + * The minimum power of two at which Envoy starts tracking an account. + * + * Envoy has 8 power of two buckets starting with the provided exponent below. + * Concretely the 1st bucket contains accounts for streams that use + * [2^minimum_account_to_track_power_of_two, + * 2^(minimum_account_to_track_power_of_two + 1)) bytes. + * With the 8th bucket tracking accounts + * >= 128 * 2^minimum_account_to_track_power_of_two. + * + * The maximum value is 56, since we're using uint64_t for bytes counting, + * and that's the last value that would use the 8 buckets. In practice, + * we don't expect the proxy to be holding 2^56 bytes. + * + * If omitted, Envoy should not do any tracking. + */ + 'minimum_account_to_track_power_of_two': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts new file mode 100644 index 000000000..84f4db34b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts @@ -0,0 +1,42 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Trigger as _envoy_config_overload_v3_Trigger, Trigger__Output as _envoy_config_overload_v3_Trigger__Output } from '../../../../envoy/config/overload/v3/Trigger'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +export interface OverloadAction { + /** + * The name of the overload action. This is just a well-known string that listeners can + * use for registering callbacks. Custom overload actions should be named using reverse + * DNS to ensure uniqueness. + */ + 'name'?: (string); + /** + * A set of triggers for this action. The state of the action is the maximum + * state of all triggers, which can be scaling between 0 and 1 or saturated. Listeners + * are notified when the overload action changes state. + */ + 'triggers'?: (_envoy_config_overload_v3_Trigger)[]; + /** + * Configuration for the action being instantiated. + */ + 'typed_config'?: (_google_protobuf_Any | null); +} + +export interface OverloadAction__Output { + /** + * The name of the overload action. This is just a well-known string that listeners can + * use for registering callbacks. Custom overload actions should be named using reverse + * DNS to ensure uniqueness. + */ + 'name': (string); + /** + * A set of triggers for this action. The state of the action is the maximum + * state of all triggers, which can be scaling between 0 and 1 or saturated. Listeners + * are notified when the overload action changes state. + */ + 'triggers': (_envoy_config_overload_v3_Trigger__Output)[]; + /** + * Configuration for the action being instantiated. + */ + 'typed_config': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts new file mode 100644 index 000000000..e7f75b8e9 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts @@ -0,0 +1,44 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { ResourceMonitor as _envoy_config_overload_v3_ResourceMonitor, ResourceMonitor__Output as _envoy_config_overload_v3_ResourceMonitor__Output } from '../../../../envoy/config/overload/v3/ResourceMonitor'; +import type { OverloadAction as _envoy_config_overload_v3_OverloadAction, OverloadAction__Output as _envoy_config_overload_v3_OverloadAction__Output } from '../../../../envoy/config/overload/v3/OverloadAction'; +import type { BufferFactoryConfig as _envoy_config_overload_v3_BufferFactoryConfig, BufferFactoryConfig__Output as _envoy_config_overload_v3_BufferFactoryConfig__Output } from '../../../../envoy/config/overload/v3/BufferFactoryConfig'; + +export interface OverloadManager { + /** + * The interval for refreshing resource usage. + */ + 'refresh_interval'?: (_google_protobuf_Duration | null); + /** + * The set of resources to monitor. + */ + 'resource_monitors'?: (_envoy_config_overload_v3_ResourceMonitor)[]; + /** + * The set of overload actions. + */ + 'actions'?: (_envoy_config_overload_v3_OverloadAction)[]; + /** + * Configuration for buffer factory. + */ + 'buffer_factory_config'?: (_envoy_config_overload_v3_BufferFactoryConfig | null); +} + +export interface OverloadManager__Output { + /** + * The interval for refreshing resource usage. + */ + 'refresh_interval': (_google_protobuf_Duration__Output | null); + /** + * The set of resources to monitor. + */ + 'resource_monitors': (_envoy_config_overload_v3_ResourceMonitor__Output)[]; + /** + * The set of overload actions. + */ + 'actions': (_envoy_config_overload_v3_OverloadAction__Output)[]; + /** + * Configuration for buffer factory. + */ + 'buffer_factory_config': (_envoy_config_overload_v3_BufferFactoryConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts new file mode 100644 index 000000000..02fde2411 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts @@ -0,0 +1,33 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; + +export interface ResourceMonitor { + /** + * The name of the resource monitor to instantiate. Must match a registered + * resource monitor type. + * See the :ref:`extensions listed in typed_config below ` for the default list of available resource monitor. + */ + 'name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Configuration for the resource monitor being instantiated. + * [#extension-category: envoy.resource_monitors] + */ + 'config_type'?: "typed_config"; +} + +export interface ResourceMonitor__Output { + /** + * The name of the resource monitor to instantiate. Must match a registered + * resource monitor type. + * See the :ref:`extensions listed in typed_config below ` for the default list of available resource monitor. + */ + 'name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Configuration for the resource monitor being instantiated. + * [#extension-category: envoy.resource_monitors] + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts new file mode 100644 index 000000000..bb48fe3f6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts @@ -0,0 +1,89 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; + +export interface _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer { + /** + * The type of timer this minimum applies to. + */ + 'timer'?: (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType | keyof typeof _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType); + /** + * Sets the minimum duration as an absolute value. + */ + 'min_timeout'?: (_google_protobuf_Duration | null); + /** + * Sets the minimum duration as a percentage of the maximum value. + */ + 'min_scale'?: (_envoy_type_v3_Percent | null); + 'overload_adjust'?: "min_timeout"|"min_scale"; +} + +export interface _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer__Output { + /** + * The type of timer this minimum applies to. + */ + 'timer': (keyof typeof _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType); + /** + * Sets the minimum duration as an absolute value. + */ + 'min_timeout'?: (_google_protobuf_Duration__Output | null); + /** + * Sets the minimum duration as a percentage of the maximum value. + */ + 'min_scale'?: (_envoy_type_v3_Percent__Output | null); + 'overload_adjust': "min_timeout"|"min_scale"; +} + +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +export enum _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType { + /** + * Unsupported value; users must explicitly specify the timer they want scaled. + */ + UNSPECIFIED = 0, + /** + * Adjusts the idle timer for downstream HTTP connections that takes effect when there are no active streams. + * This affects the value of :ref:`HttpConnectionManager.common_http_protocol_options.idle_timeout + * ` + */ + HTTP_DOWNSTREAM_CONNECTION_IDLE = 1, + /** + * Adjusts the idle timer for HTTP streams initiated by downstream clients. + * This affects the value of :ref:`RouteAction.idle_timeout ` and + * :ref:`HttpConnectionManager.stream_idle_timeout + * ` + */ + HTTP_DOWNSTREAM_STREAM_IDLE = 2, + /** + * Adjusts the timer for how long downstream clients have to finish transport-level negotiations + * before the connection is closed. + * This affects the value of + * :ref:`FilterChain.transport_socket_connect_timeout `. + */ + TRANSPORT_SOCKET_CONNECT = 3, +} + +/** + * Typed configuration for the "envoy.overload_actions.reduce_timeouts" action. See + * :ref:`the docs ` for an example of how to configure + * the action with different timeouts and minimum values. + */ +export interface ScaleTimersOverloadActionConfig { + /** + * A set of timer scaling rules to be applied. + */ + 'timer_scale_factors'?: (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer)[]; +} + +/** + * Typed configuration for the "envoy.overload_actions.reduce_timeouts" action. See + * :ref:`the docs ` for an example of how to configure + * the action with different timeouts and minimum values. + */ +export interface ScaleTimersOverloadActionConfig__Output { + /** + * A set of timer scaling rules to be applied. + */ + 'timer_scale_factors': (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts new file mode 100644 index 000000000..8c6574f56 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts @@ -0,0 +1,28 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + + +export interface ScaledTrigger { + /** + * If the resource pressure is greater than this value, the trigger will be in the + * :ref:`scaling ` state with value + * `(pressure - scaling_threshold) / (saturation_threshold - scaling_threshold)`. + */ + 'scaling_threshold'?: (number | string); + /** + * If the resource pressure is greater than this value, the trigger will enter saturation. + */ + 'saturation_threshold'?: (number | string); +} + +export interface ScaledTrigger__Output { + /** + * If the resource pressure is greater than this value, the trigger will be in the + * :ref:`scaling ` state with value + * `(pressure - scaling_threshold) / (saturation_threshold - scaling_threshold)`. + */ + 'scaling_threshold': (number); + /** + * If the resource pressure is greater than this value, the trigger will enter saturation. + */ + 'saturation_threshold': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts new file mode 100644 index 000000000..b02ddd47d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts @@ -0,0 +1,18 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + + +export interface ThresholdTrigger { + /** + * If the resource pressure is greater than or equal to this value, the trigger + * will enter saturation. + */ + 'value'?: (number | string); +} + +export interface ThresholdTrigger__Output { + /** + * If the resource pressure is greater than or equal to this value, the trigger + * will enter saturation. + */ + 'value': (number); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts new file mode 100644 index 000000000..38f360ee7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts @@ -0,0 +1,24 @@ +// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto + +import type { ThresholdTrigger as _envoy_config_overload_v3_ThresholdTrigger, ThresholdTrigger__Output as _envoy_config_overload_v3_ThresholdTrigger__Output } from '../../../../envoy/config/overload/v3/ThresholdTrigger'; +import type { ScaledTrigger as _envoy_config_overload_v3_ScaledTrigger, ScaledTrigger__Output as _envoy_config_overload_v3_ScaledTrigger__Output } from '../../../../envoy/config/overload/v3/ScaledTrigger'; + +export interface Trigger { + /** + * The name of the resource this is a trigger for. + */ + 'name'?: (string); + 'threshold'?: (_envoy_config_overload_v3_ThresholdTrigger | null); + 'scaled'?: (_envoy_config_overload_v3_ScaledTrigger | null); + 'trigger_oneof'?: "threshold"|"scaled"; +} + +export interface Trigger__Output { + /** + * The name of the resource this is a trigger for. + */ + 'name': (string); + 'threshold'?: (_envoy_config_overload_v3_ThresholdTrigger__Output | null); + 'scaled'?: (_envoy_config_overload_v3_ScaledTrigger__Output | null); + 'trigger_oneof': "threshold"|"scaled"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts new file mode 100644 index 000000000..14724412a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route.proto + +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; + +/** + * Configuration for a cluster specifier plugin. + */ +export interface ClusterSpecifierPlugin { + /** + * The name of the plugin and its opaque configuration. + */ + 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig | null); +} + +/** + * Configuration for a cluster specifier plugin. + */ +export interface ClusterSpecifierPlugin__Output { + /** + * The name of the plugin and its opaque configuration. + */ + 'extension': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts index 03ec3218d..40634448a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts @@ -34,7 +34,7 @@ export interface CorsPolicy { * If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS * filter will be enabled for 100% of the requests. * - * If :ref:`runtime_key ` is + * If :ref:`runtime_key ` is * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. */ 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); @@ -45,7 +45,7 @@ export interface CorsPolicy { * This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those * fields have to explicitly disable the filter in order for this setting to take effect. * - * If :ref:`runtime_key ` is specified, + * If :ref:`runtime_key ` is specified, * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate * and track the request's *Origin* to determine if it's valid but will not enforce any policies. */ @@ -88,7 +88,7 @@ export interface CorsPolicy__Output { * If neither ``enabled``, ``filter_enabled``, nor ``shadow_enabled`` are specified, the CORS * filter will be enabled for 100% of the requests. * - * If :ref:`runtime_key ` is + * If :ref:`runtime_key ` is * specified, Envoy will lookup the runtime key to get the percentage of requests to filter. */ 'filter_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); @@ -99,7 +99,7 @@ export interface CorsPolicy__Output { * This field is intended to be used when ``filter_enabled`` and ``enabled`` are off. One of those * fields have to explicitly disable the filter in order for this setting to take effect. * - * If :ref:`runtime_key ` is specified, + * If :ref:`runtime_key ` is specified, * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate * and track the request's *Origin* to determine if it's valid but will not enforce any policies. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts index 7e0c3a1b7..794ae510a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts @@ -14,8 +14,8 @@ export interface DirectResponseAction { * .. note:: * * Headers can be specified using *response_headers_to_add* in the enclosing - * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or - * :ref:`envoy_api_msg_config.route.v3.VirtualHost`. + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. */ 'body'?: (_envoy_config_core_v3_DataSource | null); } @@ -32,8 +32,8 @@ export interface DirectResponseAction__Output { * .. note:: * * Headers can be specified using *response_headers_to_add* in the enclosing - * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` or - * :ref:`envoy_api_msg_config.route.v3.VirtualHost`. + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or + * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. */ 'body': (_envoy_config_core_v3_DataSource__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts index 9a566c2bb..2c960419c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts @@ -5,9 +5,9 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ /** * A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the * map value in - * :ref:`VirtualHost.typed_per_filter_config`, - * :ref:`Route.typed_per_filter_config`, - * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * :ref:`VirtualHost.typed_per_filter_config`, + * :ref:`Route.typed_per_filter_config`, + * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` * to add additional flags to the filter. * [#not-implemented-hide:] */ @@ -27,9 +27,9 @@ export interface FilterConfig { /** * A simple wrapper for an HTTP filter config. This is intended to be used as a wrapper for the * map value in - * :ref:`VirtualHost.typed_per_filter_config`, - * :ref:`Route.typed_per_filter_config`, - * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * :ref:`VirtualHost.typed_per_filter_config`, + * :ref:`Route.typed_per_filter_config`, + * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` * to add additional flags to the filter. * [#not-implemented-hide:] */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts index ee03d1fe7..bde8f28cd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts @@ -2,6 +2,7 @@ import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; import type { Long } from '@grpc/proto-loader'; /** @@ -24,12 +25,12 @@ import type { Long } from '@grpc/proto-loader'; * * .. attention:: * In the absence of any header match specifier, match will default to :ref:`present_match - * `. i.e, a request that has the :ref:`name - * ` header will match, regardless of the header's + * `. i.e, a request that has the :ref:`name + * ` header will match, regardless of the header's * value. * * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface HeaderMatcher { /** @@ -38,6 +39,7 @@ export interface HeaderMatcher { 'name'?: (string); /** * If specified, header match will be performed based on the value of the header. + * This field is deprecated. Please use :ref:`string_match `. */ 'exact_match'?: (string); /** @@ -55,8 +57,8 @@ export interface HeaderMatcher { */ 'range_match'?: (_envoy_type_v3_Int64Range | null); /** - * If specified, header match will be performed based on whether the header is in the - * request. + * If specified as true, header match will be performed based on whether the header is in the + * request. If specified as false, header match will be performed based on whether the header is absent. */ 'present_match'?: (boolean); /** @@ -71,6 +73,7 @@ export interface HeaderMatcher { /** * If specified, header match will be performed based on the prefix of the header value. * Note: empty prefix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. * * Examples: * @@ -80,6 +83,7 @@ export interface HeaderMatcher { /** * If specified, header match will be performed based on the suffix of the header value. * Note: empty suffix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. * * Examples: * @@ -90,22 +94,28 @@ export interface HeaderMatcher { * If specified, this regex string is a regular expression rule which implies the entire request * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. + * This field is deprecated. Please use :ref:`string_match `. */ 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher | null); /** * If specified, header match will be performed based on whether the header value contains * the given value or not. * Note: empty contains match is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. * * Examples: * * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. */ 'contains_match'?: (string); + /** + * If specified, header match will be performed based on the string match of the header value. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null); /** * Specifies how the header match will be performed to route the request. */ - 'header_match_specifier'?: "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"; + 'header_match_specifier'?: "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"|"string_match"; } /** @@ -128,12 +138,12 @@ export interface HeaderMatcher { * * .. attention:: * In the absence of any header match specifier, match will default to :ref:`present_match - * `. i.e, a request that has the :ref:`name - * ` header will match, regardless of the header's + * `. i.e, a request that has the :ref:`name + * ` header will match, regardless of the header's * value. * * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface HeaderMatcher__Output { /** @@ -142,6 +152,7 @@ export interface HeaderMatcher__Output { 'name': (string); /** * If specified, header match will be performed based on the value of the header. + * This field is deprecated. Please use :ref:`string_match `. */ 'exact_match'?: (string); /** @@ -159,8 +170,8 @@ export interface HeaderMatcher__Output { */ 'range_match'?: (_envoy_type_v3_Int64Range__Output | null); /** - * If specified, header match will be performed based on whether the header is in the - * request. + * If specified as true, header match will be performed based on whether the header is in the + * request. If specified as false, header match will be performed based on whether the header is absent. */ 'present_match'?: (boolean); /** @@ -175,6 +186,7 @@ export interface HeaderMatcher__Output { /** * If specified, header match will be performed based on the prefix of the header value. * Note: empty prefix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. * * Examples: * @@ -184,6 +196,7 @@ export interface HeaderMatcher__Output { /** * If specified, header match will be performed based on the suffix of the header value. * Note: empty suffix is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. * * Examples: * @@ -194,20 +207,26 @@ export interface HeaderMatcher__Output { * If specified, this regex string is a regular expression rule which implies the entire request * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. + * This field is deprecated. Please use :ref:`string_match `. */ 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); /** * If specified, header match will be performed based on whether the header value contains * the given value or not. * Note: empty contains match is not allowed, please use present_match instead. + * This field is deprecated. Please use :ref:`string_match `. * * Examples: * * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. */ 'contains_match'?: (string); + /** + * If specified, header match will be performed based on the string match of the header value. + */ + 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null); /** * Specifies how the header match will be performed to route the request. */ - 'header_match_specifier': "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"; + 'header_match_specifier': "exact_match"|"safe_regex_match"|"range_match"|"present_match"|"prefix_match"|"suffix_match"|"contains_match"|"string_match"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts index 413e93b27..302b6d284 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HedgePolicy.ts @@ -31,7 +31,7 @@ export interface HedgePolicy { * if there are no more retries left. * * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. * - * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least * one error code and specifies a maximum number of retries. * * Defaults to false. @@ -67,7 +67,7 @@ export interface HedgePolicy__Output { * if there are no more retries left. * * After per-try timeout, an error response would be discarded, as a retry in the form of a hedged request is already in progress. * - * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least + * Note: For this to have effect, you must have a :ref:`RetryPolicy ` that retries at least * one error code and specifies a maximum number of retries. * * Defaults to false. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts index 93a96c247..ab74df94e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/InternalRedirectPolicy.ts @@ -12,7 +12,7 @@ export interface InternalRedirectPolicy { * downstream request has encountered is lower than this value. * In the case where a downstream request is bounced among multiple routes by internal redirect, * the first route that hits this threshold, or does not set :ref:`internal_redirect_policy - * ` + * ` * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. @@ -28,6 +28,7 @@ export interface InternalRedirectPolicy { * Specifies a list of predicates that are queried when an upstream response is deemed * to trigger an internal redirect by all other criteria. Any predicate in the list can reject * the redirect, causing the response to be proxied to downstream. + * [#extension-category: envoy.internal_redirect_predicates] */ 'predicates'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; /** @@ -46,7 +47,7 @@ export interface InternalRedirectPolicy__Output { * downstream request has encountered is lower than this value. * In the case where a downstream request is bounced among multiple routes by internal redirect, * the first route that hits this threshold, or does not set :ref:`internal_redirect_policy - * ` + * ` * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. @@ -62,6 +63,7 @@ export interface InternalRedirectPolicy__Output { * Specifies a list of predicates that are queried when an upstream response is deemed * to trigger an internal redirect by all other criteria. Any predicate in the list can reject * the redirect, causing the response to be proxied to downstream. + * [#extension-category: envoy.internal_redirect_predicates] */ 'predicates': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/NonForwardingAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/NonForwardingAction.ts new file mode 100644 index 000000000..e9c67d44f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/NonForwardingAction.ts @@ -0,0 +1,14 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + + +/** + * [#not-implemented-hide:] + */ +export interface NonForwardingAction { +} + +/** + * [#not-implemented-hide:] + */ +export interface NonForwardingAction__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts index 484044bdf..f1d49537c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts @@ -38,7 +38,7 @@ export interface _envoy_config_route_v3_RateLimit_Action { * Rate limit on dynamic metadata. * * .. attention:: - * This field has been deprecated in favor of the :ref:`metadata ` field + * This field has been deprecated in favor of the :ref:`metadata ` field */ 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData | null); /** @@ -47,6 +47,7 @@ export interface _envoy_config_route_v3_RateLimit_Action { 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData | null); /** * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + * [#extension-category: envoy.rate_limit_descriptors] */ 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig | null); 'action_specifier'?: "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; @@ -84,7 +85,7 @@ export interface _envoy_config_route_v3_RateLimit_Action__Output { * Rate limit on dynamic metadata. * * .. attention:: - * This field has been deprecated in favor of the :ref:`metadata ` field + * This field has been deprecated in favor of the :ref:`metadata ` field */ 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output | null); /** @@ -93,6 +94,7 @@ export interface _envoy_config_route_v3_RateLimit_Action__Output { 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData__Output | null); /** * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + * [#extension-category: envoy.rate_limit_descriptors] */ 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); 'action_specifier': "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; @@ -106,14 +108,14 @@ export interface _envoy_config_route_v3_RateLimit_Action__Output { * ("destination_cluster", "") * * Once a request matches against a route table rule, a routed cluster is determined by one of - * the following :ref:`route table configuration ` + * the following :ref:`route table configuration ` * settings: * - * * :ref:`cluster ` indicates the upstream cluster + * * :ref:`cluster ` indicates the upstream cluster * to route to. - * * :ref:`weighted_clusters ` + * * :ref:`weighted_clusters ` * chooses a cluster randomly from a set of clusters with attributed weight. - * * :ref:`cluster_header ` indicates which + * * :ref:`cluster_header ` indicates which * header in the request contains the target cluster. */ export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster { @@ -127,14 +129,14 @@ export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster { * ("destination_cluster", "") * * Once a request matches against a route table rule, a routed cluster is determined by one of - * the following :ref:`route table configuration ` + * the following :ref:`route table configuration ` * settings: * - * * :ref:`cluster ` indicates the upstream cluster + * * :ref:`cluster ` indicates the upstream cluster * to route to. - * * :ref:`weighted_clusters ` + * * :ref:`weighted_clusters ` * chooses a cluster randomly from a set of clusters with attributed weight. - * * :ref:`cluster_header ` indicates which + * * :ref:`cluster_header ` indicates which * header in the request contains the target cluster. */ export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster__Output { @@ -149,7 +151,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_DestinationCluster__Out * ("", "") * * .. attention:: - * This action has been deprecated in favor of the :ref:`metadata ` action + * This action has been deprecated in favor of the :ref:`metadata ` action */ export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData { /** @@ -177,7 +179,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData { * ("", "") * * .. attention:: - * This action has been deprecated in favor of the :ref:`metadata ` action + * This action has been deprecated in favor of the :ref:`metadata ` action */ export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output { /** @@ -204,7 +206,7 @@ export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata { * Metadata struct that defines the key and path to retrieve the struct value. * The value must be a struct containing an integer "requests_per_unit" property * and a "unit" property with a value parseable to :ref:`RateLimitUnit - * enum ` + * enum ` */ 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); } @@ -217,7 +219,7 @@ export interface _envoy_config_route_v3_RateLimit_Override_DynamicMetadata__Outp * Metadata struct that defines the key and path to retrieve the struct value. * The value must be a struct containing an integer "requests_per_unit" property * and a "unit" property with a value parseable to :ref:`RateLimitUnit - * enum ` + * enum ` */ 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); } @@ -474,7 +476,7 @@ export enum _envoy_config_route_v3_RateLimit_Action_MetaData_Source { */ DYNAMIC = 0, /** - * Query :ref:`route entry metadata ` + * Query :ref:`route entry metadata ` */ ROUTE_ENTRY = 1, } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts index 750946934..e6d41fd7b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts @@ -71,7 +71,7 @@ export interface RedirectAction { * .. attention:: * * Pay attention to the use of trailing slashes as mentioned in - * :ref:`RouteAction's prefix_rewrite `. + * :ref:`RouteAction's prefix_rewrite `. */ 'prefix_rewrite'?: (string); /** @@ -168,7 +168,7 @@ export interface RedirectAction__Output { * .. attention:: * * Pay attention to the use of trailing slashes as mentioned in - * :ref:`RouteAction's prefix_rewrite `. + * :ref:`RouteAction's prefix_rewrite `. */ 'prefix_rewrite'?: (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts index d4712b2df..0d523b52e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts @@ -3,6 +3,7 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { Long } from '@grpc/proto-loader'; @@ -202,30 +203,42 @@ export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff__Output { export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate { 'name'?: (string); 'typed_config'?: (_google_protobuf_Any | null); + /** + * [#extension-category: envoy.retry_host_predicates] + */ 'config_type'?: "typed_config"; } export interface _envoy_config_route_v3_RetryPolicy_RetryHostPredicate__Output { 'name': (string); 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * [#extension-category: envoy.retry_host_predicates] + */ 'config_type': "typed_config"; } export interface _envoy_config_route_v3_RetryPolicy_RetryPriority { 'name'?: (string); 'typed_config'?: (_google_protobuf_Any | null); + /** + * [#extension-category: envoy.retry_priorities] + */ 'config_type'?: "typed_config"; } export interface _envoy_config_route_v3_RetryPolicy_RetryPriority__Output { 'name': (string); 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * [#extension-category: envoy.retry_priorities] + */ 'config_type': "typed_config"; } /** * HTTP retry :ref:`architecture overview `. - * [#next-free-field: 12] + * [#next-free-field: 14] */ export interface RetryPolicy { /** @@ -241,14 +254,14 @@ export interface RetryPolicy { */ 'num_retries'?: (_google_protobuf_UInt32Value | null); /** - * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The - * same conditions documented for + * Specifies a non-zero upstream timeout per retry attempt (including the initial attempt). This + * parameter is optional. The same conditions documented for * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. * * .. note:: * * If left unspecified, Envoy will use the global - * :ref:`route timeout ` for the request. + * :ref:`route timeout ` for the request. * Consequently, when using a :ref:`5xx ` based * retry policy, a request that times out will not be retried as the total timeout budget * would have been exhausted. @@ -305,11 +318,39 @@ export interface RetryPolicy { * whenever a response includes the matching headers. */ 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff | null); + /** + * Retry options predicates that will be applied prior to retrying a request. These predicates + * allow customizing request behavior between retries. + * [#comment: add [#extension-category: envoy.retry_options_predicates] when there are built-in extensions] + */ + 'retry_options_predicates'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Specifies an upstream idle timeout per retry attempt (including the initial attempt). This + * parameter is optional and if absent there is no per try idle timeout. The semantics of the per + * try idle timeout are similar to the + * :ref:`route idle timeout ` and + * :ref:`stream idle timeout + * ` + * both enforced by the HTTP connection manager. The difference is that this idle timeout + * is enforced by the router for each individual attempt and thus after all previous filters have + * run, as opposed to *before* all previous filters run for the other idle timeouts. This timeout + * is useful in cases in which total request timeout is bounded by a number of retries and a + * :ref:`per_try_timeout `, but + * there is a desire to ensure each try is making incremental progress. Note also that similar + * to :ref:`per_try_timeout `, + * this idle timeout does not start until after both the entire request has been received by the + * router *and* a connection pool connection has been obtained. Unlike + * :ref:`per_try_timeout `, + * the idle timer continues once the response starts streaming back to the downstream client. + * This ensures that response data continues to make progress without using one of the HTTP + * connection manager idle timeouts. + */ + 'per_try_idle_timeout'?: (_google_protobuf_Duration | null); } /** * HTTP retry :ref:`architecture overview `. - * [#next-free-field: 12] + * [#next-free-field: 14] */ export interface RetryPolicy__Output { /** @@ -325,14 +366,14 @@ export interface RetryPolicy__Output { */ 'num_retries': (_google_protobuf_UInt32Value__Output | null); /** - * Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The - * same conditions documented for + * Specifies a non-zero upstream timeout per retry attempt (including the initial attempt). This + * parameter is optional. The same conditions documented for * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. * * .. note:: * * If left unspecified, Envoy will use the global - * :ref:`route timeout ` for the request. + * :ref:`route timeout ` for the request. * Consequently, when using a :ref:`5xx ` based * retry policy, a request that times out will not be retried as the total timeout budget * would have been exhausted. @@ -389,4 +430,32 @@ export interface RetryPolicy__Output { * whenever a response includes the matching headers. */ 'rate_limited_retry_back_off': (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output | null); + /** + * Retry options predicates that will be applied prior to retrying a request. These predicates + * allow customizing request behavior between retries. + * [#comment: add [#extension-category: envoy.retry_options_predicates] when there are built-in extensions] + */ + 'retry_options_predicates': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Specifies an upstream idle timeout per retry attempt (including the initial attempt). This + * parameter is optional and if absent there is no per try idle timeout. The semantics of the per + * try idle timeout are similar to the + * :ref:`route idle timeout ` and + * :ref:`stream idle timeout + * ` + * both enforced by the HTTP connection manager. The difference is that this idle timeout + * is enforced by the router for each individual attempt and thus after all previous filters have + * run, as opposed to *before* all previous filters run for the other idle timeouts. This timeout + * is useful in cases in which total request timeout is bounded by a number of retries and a + * :ref:`per_try_timeout `, but + * there is a desire to ensure each try is making incremental progress. Note also that similar + * to :ref:`per_try_timeout `, + * this idle timeout does not start until after both the entire request has been received by the + * router *and* a connection pool connection has been obtained. Unlike + * :ref:`per_try_timeout `, + * the idle timer continues once the response starts streaming back to the downstream client. + * This ensures that response data continues to make progress without using one of the HTTP + * connection manager idle timeouts. + */ + 'per_try_idle_timeout': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts index 7bc223f82..d48b554d0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts @@ -11,6 +11,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ import type { Tracing as _envoy_config_route_v3_Tracing, Tracing__Output as _envoy_config_route_v3_Tracing__Output } from '../../../../envoy/config/route/v3/Tracing'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { FilterAction as _envoy_config_route_v3_FilterAction, FilterAction__Output as _envoy_config_route_v3_FilterAction__Output } from '../../../../envoy/config/route/v3/FilterAction'; +import type { NonForwardingAction as _envoy_config_route_v3_NonForwardingAction, NonForwardingAction__Output as _envoy_config_route_v3_NonForwardingAction__Output } from '../../../../envoy/config/route/v3/NonForwardingAction'; /** * A route is both a specification of how to match a request as well as an indication of what to do @@ -19,8 +20,8 @@ import type { FilterAction as _envoy_config_route_v3_FilterAction, FilterAction_ * .. attention:: * * Envoy supports routing on HTTP method via :ref:`header matching - * `. - * [#next-free-field: 18] + * `. + * [#next-free-field: 19] */ export interface Route { /** @@ -54,8 +55,8 @@ export interface Route { /** * Specifies a set of headers that will be added to requests matching this * route. Headers specified at this level are applied before headers from the - * enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -63,8 +64,8 @@ export interface Route { /** * Specifies a set of headers that will be added to responses to requests * matching this route. Headers specified at this level are applied before - * headers from the enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on * :ref:`custom request headers `. */ @@ -86,7 +87,7 @@ export interface Route { * specific; see the :ref:`HTTP filter documentation ` for * if and how it is utilized. * [#comment: An entry's value may be wrapped in a - * :ref:`FilterConfig` + * :ref:`FilterConfig` * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); @@ -107,13 +108,20 @@ export interface Route { 'per_request_buffer_limit_bytes'?: (_google_protobuf_UInt32Value | null); /** * [#not-implemented-hide:] - * If true, a filter will define the action (e.g., it could dynamically generate the - * RouteAction). + * A filter-defined action (e.g., it could dynamically generate the RouteAction). * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when * implemented] */ 'filter_action'?: (_envoy_config_route_v3_FilterAction | null); - 'action'?: "route"|"redirect"|"direct_response"|"filter_action"; + /** + * [#not-implemented-hide:] + * An action used when the route will generate a response directly, + * without forwarding to an upstream host. This will be used in non-proxy + * xDS clients like the gRPC server. It could also be used in the future + * in Envoy for a filter that directly generates responses for requests. + */ + 'non_forwarding_action'?: (_envoy_config_route_v3_NonForwardingAction | null); + 'action'?: "route"|"redirect"|"direct_response"|"filter_action"|"non_forwarding_action"; } /** @@ -123,8 +131,8 @@ export interface Route { * .. attention:: * * Envoy supports routing on HTTP method via :ref:`header matching - * `. - * [#next-free-field: 18] + * `. + * [#next-free-field: 19] */ export interface Route__Output { /** @@ -158,8 +166,8 @@ export interface Route__Output { /** * Specifies a set of headers that will be added to requests matching this * route. Headers specified at this level are applied before headers from the - * enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -167,8 +175,8 @@ export interface Route__Output { /** * Specifies a set of headers that will be added to responses to requests * matching this route. Headers specified at this level are applied before - * headers from the enclosing :ref:`envoy_api_msg_config.route.v3.VirtualHost` and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * headers from the enclosing :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on * :ref:`custom request headers `. */ @@ -190,7 +198,7 @@ export interface Route__Output { * specific; see the :ref:`HTTP filter documentation ` for * if and how it is utilized. * [#comment: An entry's value may be wrapped in a - * :ref:`FilterConfig` + * :ref:`FilterConfig` * message to specify additional options.] */ 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); @@ -211,11 +219,18 @@ export interface Route__Output { 'per_request_buffer_limit_bytes': (_google_protobuf_UInt32Value__Output | null); /** * [#not-implemented-hide:] - * If true, a filter will define the action (e.g., it could dynamically generate the - * RouteAction). + * A filter-defined action (e.g., it could dynamically generate the RouteAction). * [#comment: TODO(samflattery): Remove cleanup in route_fuzz_test.cc when * implemented] */ 'filter_action'?: (_envoy_config_route_v3_FilterAction__Output | null); - 'action': "route"|"redirect"|"direct_response"|"filter_action"; + /** + * [#not-implemented-hide:] + * An action used when the route will generate a response directly, + * without forwarding to an upstream host. This will be used in non-proxy + * xDS clients like the gRPC server. It could also be used in the future + * in Envoy for a filter that directly generates responses for requests. + */ + 'non_forwarding_action'?: (_envoy_config_route_v3_NonForwardingAction__Output | null); + 'action': "route"|"redirect"|"direct_response"|"filter_action"|"non_forwarding_action"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts index 234aa25e0..43bd51723 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts @@ -306,9 +306,9 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration { /** * Specifies the maximum duration allowed for streams on the route. If not specified, the value * from the :ref:`max_stream_duration - * ` field in + * ` field in * :ref:`HttpConnectionManager.common_http_protocol_options - * ` + * ` * is used. If this field is set explicitly to zero, any * HttpConnectionManager max_stream_duration timeout will be disabled for * this route. @@ -336,9 +336,9 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration__Output { /** * Specifies the maximum duration allowed for streams on the route. If not specified, the value * from the :ref:`max_stream_duration - * ` field in + * ` field in * :ref:`HttpConnectionManager.common_http_protocol_options - * ` + * ` * is used. If this field is set explicitly to zero, any * HttpConnectionManager max_stream_duration timeout will be disabled for * this route. @@ -457,7 +457,7 @@ export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output * This overrides any enabled/disabled upgrade filter chain specified in the * HttpConnectionManager * :ref:`upgrade_configs - * ` + * ` * but does not affect any custom filter chain specified there. */ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig { @@ -475,7 +475,7 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig { * Configuration for sending data upstream as a raw data payload. This is used for * CONNECT requests, when forwarding CONNECT payload as raw TCP. * Note that CONNECT support is currently considered alpha in Envoy. - * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] */ 'connect_config'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig | null); } @@ -485,7 +485,7 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig { * This overrides any enabled/disabled upgrade filter chain specified in the * HttpConnectionManager * :ref:`upgrade_configs - * ` + * ` * but does not affect any custom filter chain specified there. */ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig__Output { @@ -503,13 +503,13 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig__Output { * Configuration for sending data upstream as a raw data payload. This is used for * CONNECT requests, when forwarding CONNECT payload as raw TCP. * Note that CONNECT support is currently considered alpha in Envoy. - * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] */ 'connect_config': (_envoy_config_route_v3_RouteAction_UpgradeConfig_ConnectConfig__Output | null); } /** - * [#next-free-field: 37] + * [#next-free-field: 38] */ export interface RouteAction { /** @@ -545,7 +545,7 @@ export interface RouteAction { * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints * in the upstream cluster with metadata matching what's set in this field will be considered * for load balancing. If using :ref:`weighted_clusters - * `, metadata will be merged, with values + * `, metadata will be merged, with values * provided there taking precedence. The filter name should be specified as *envoy.lb*. */ 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); @@ -557,16 +557,16 @@ export interface RouteAction { * ` header. * * Only one of *prefix_rewrite* or - * :ref:`regex_rewrite ` + * :ref:`regex_rewrite ` * may be specified. * * .. attention:: * * Pay careful attention to the use of trailing slashes in the - * :ref:`route's match ` prefix value. + * :ref:`route's match ` prefix value. * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single - * :ref:`Route `, as shown by the below config entries: + * :ref:`Route `, as shown by the below config entries: * * .. code-block:: yaml * @@ -628,7 +628,7 @@ export interface RouteAction { /** * Specifies if the rate limit filter should include the virtual host rate * limits. By default, if the route configured rate limits, the virtual host - * :ref:`rate_limits ` are not applied to the + * :ref:`rate_limits ` are not applied to the * request. * * This field is deprecated. Please use :ref:`vh_rate_limits ` @@ -659,15 +659,15 @@ export interface RouteAction { */ 'cluster_not_found_response_code'?: (_envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode | keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); /** - * Deprecated by :ref:`grpc_timeout_header_max ` + * Deprecated by :ref:`grpc_timeout_header_max ` * If present, and the request is a gRPC request, use the * `grpc-timeout header `_, * or its default value (infinity) instead of - * :ref:`timeout `, but limit the applied timeout + * :ref:`timeout `, but limit the applied timeout * to the maximum value specified here. If configured as 0, the maximum allowed timeout for * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used * and gRPC requests time out like any other requests using - * :ref:`timeout ` or its default. + * :ref:`timeout ` or its default. * This can be used to prevent unexpected upstream request timeouts due to potentially long * time gaps between gRPC request and response in gRPC streaming mode. * @@ -684,14 +684,14 @@ export interface RouteAction { /** * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, * although the connection manager wide :ref:`stream_idle_timeout - * ` + * ` * will still apply. A value of 0 will completely disable the route's idle timeout, even if a * connection manager stream idle timeout is configured. * * The idle timeout is distinct to :ref:`timeout - * `, which provides an upper bound + * `, which provides an upper bound * on the upstream response time; :ref:`idle_timeout - * ` instead bounds the amount + * ` instead bounds the amount * of time the request's stream may be idle. * * After header decoding, the idle timeout will apply on downstream and @@ -703,7 +703,7 @@ export interface RouteAction { * * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" * is configured, this timeout is scaled according to the value for - * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. */ 'idle_timeout'?: (_google_protobuf_Duration | null); 'upgrade_configs'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig)[]; @@ -715,7 +715,7 @@ export interface RouteAction { */ 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy | null); /** - * Deprecated by :ref:`grpc_timeout_header_offset `. + * Deprecated by :ref:`grpc_timeout_header_offset `. * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting * the provided duration from the header. This is useful in allowing Envoy to set its global * timeout to be less than that of the deadline imposed by the calling client, which makes it more @@ -747,15 +747,15 @@ export interface RouteAction { /** * An internal redirect is handled, iff the number of previous internal redirects that a * downstream request has encountered is lower than this value, and - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * is set to :ref:`HANDLE_INTERNAL_REDIRECT - * ` + * ` * In the case where a downstream request is bounced among multiple routes by internal redirect, * the first route that hits this threshold, or has - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * set to * :ref:`PASS_THROUGH_INTERNAL_REDIRECT - * ` + * ` * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. @@ -771,7 +771,7 @@ export interface RouteAction { * before the rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of :ref:`prefix_rewrite ` + * Only one of :ref:`prefix_rewrite ` * or *regex_rewrite* may be specified. * * Examples using Google's `RE2 `_ engine: @@ -796,7 +796,7 @@ export interface RouteAction { * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that if this is set, it'll take * precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - * most internal one becomes the enforced policy). :ref:`Retry policy ` + * most internal one becomes the enforced policy). :ref:`Retry policy ` * should not be set if this field is used. */ 'retry_policy_typed_config'?: (_google_protobuf_Any | null); @@ -804,7 +804,7 @@ export interface RouteAction { * If present, Envoy will try to follow an upstream redirect response instead of proxying the * response back to the downstream. An upstream redirect response is defined * by :ref:`redirect_response_codes - * `. + * `. */ 'internal_redirect_policy'?: (_envoy_config_route_v3_InternalRedirectPolicy | null); /** @@ -829,12 +829,21 @@ export interface RouteAction { * Specifies the maximum stream duration for this route. */ 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration | null); - 'cluster_specifier'?: "cluster"|"cluster_header"|"weighted_clusters"; + /** + * [#not-implemented-hide:] + * Name of the cluster specifier plugin to use to determine the cluster for + * requests on this route. The plugin name must be defined in the associated + * :ref:`envoy_v3_api_field_config.route.v3.RouteConfiguration.cluster_specifier_plugins` + * in the + * :ref:`envoy_v3_api_field_config.core.v3.TypedExtensionConfig.name` field. + */ + 'cluster_specifier_plugin'?: (string); + 'cluster_specifier'?: "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"; 'host_rewrite_specifier'?: "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } /** - * [#next-free-field: 37] + * [#next-free-field: 38] */ export interface RouteAction__Output { /** @@ -870,7 +879,7 @@ export interface RouteAction__Output { * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints * in the upstream cluster with metadata matching what's set in this field will be considered * for load balancing. If using :ref:`weighted_clusters - * `, metadata will be merged, with values + * `, metadata will be merged, with values * provided there taking precedence. The filter name should be specified as *envoy.lb*. */ 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); @@ -882,16 +891,16 @@ export interface RouteAction__Output { * ` header. * * Only one of *prefix_rewrite* or - * :ref:`regex_rewrite ` + * :ref:`regex_rewrite ` * may be specified. * * .. attention:: * * Pay careful attention to the use of trailing slashes in the - * :ref:`route's match ` prefix value. + * :ref:`route's match ` prefix value. * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single - * :ref:`Route `, as shown by the below config entries: + * :ref:`Route `, as shown by the below config entries: * * .. code-block:: yaml * @@ -953,7 +962,7 @@ export interface RouteAction__Output { /** * Specifies if the rate limit filter should include the virtual host rate * limits. By default, if the route configured rate limits, the virtual host - * :ref:`rate_limits ` are not applied to the + * :ref:`rate_limits ` are not applied to the * request. * * This field is deprecated. Please use :ref:`vh_rate_limits ` @@ -984,15 +993,15 @@ export interface RouteAction__Output { */ 'cluster_not_found_response_code': (keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); /** - * Deprecated by :ref:`grpc_timeout_header_max ` + * Deprecated by :ref:`grpc_timeout_header_max ` * If present, and the request is a gRPC request, use the * `grpc-timeout header `_, * or its default value (infinity) instead of - * :ref:`timeout `, but limit the applied timeout + * :ref:`timeout `, but limit the applied timeout * to the maximum value specified here. If configured as 0, the maximum allowed timeout for * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used * and gRPC requests time out like any other requests using - * :ref:`timeout ` or its default. + * :ref:`timeout ` or its default. * This can be used to prevent unexpected upstream request timeouts due to potentially long * time gaps between gRPC request and response in gRPC streaming mode. * @@ -1009,14 +1018,14 @@ export interface RouteAction__Output { /** * Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, * although the connection manager wide :ref:`stream_idle_timeout - * ` + * ` * will still apply. A value of 0 will completely disable the route's idle timeout, even if a * connection manager stream idle timeout is configured. * * The idle timeout is distinct to :ref:`timeout - * `, which provides an upper bound + * `, which provides an upper bound * on the upstream response time; :ref:`idle_timeout - * ` instead bounds the amount + * ` instead bounds the amount * of time the request's stream may be idle. * * After header decoding, the idle timeout will apply on downstream and @@ -1028,7 +1037,7 @@ export interface RouteAction__Output { * * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" * is configured, this timeout is scaled according to the value for - * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. */ 'idle_timeout': (_google_protobuf_Duration__Output | null); 'upgrade_configs': (_envoy_config_route_v3_RouteAction_UpgradeConfig__Output)[]; @@ -1040,7 +1049,7 @@ export interface RouteAction__Output { */ 'hedge_policy': (_envoy_config_route_v3_HedgePolicy__Output | null); /** - * Deprecated by :ref:`grpc_timeout_header_offset `. + * Deprecated by :ref:`grpc_timeout_header_offset `. * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting * the provided duration from the header. This is useful in allowing Envoy to set its global * timeout to be less than that of the deadline imposed by the calling client, which makes it more @@ -1072,15 +1081,15 @@ export interface RouteAction__Output { /** * An internal redirect is handled, iff the number of previous internal redirects that a * downstream request has encountered is lower than this value, and - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * is set to :ref:`HANDLE_INTERNAL_REDIRECT - * ` + * ` * In the case where a downstream request is bounced among multiple routes by internal redirect, * the first route that hits this threshold, or has - * :ref:`internal_redirect_action ` + * :ref:`internal_redirect_action ` * set to * :ref:`PASS_THROUGH_INTERNAL_REDIRECT - * ` + * ` * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. @@ -1096,7 +1105,7 @@ export interface RouteAction__Output { * before the rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of :ref:`prefix_rewrite ` + * Only one of :ref:`prefix_rewrite ` * or *regex_rewrite* may be specified. * * Examples using Google's `RE2 `_ engine: @@ -1121,7 +1130,7 @@ export interface RouteAction__Output { * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that if this is set, it'll take * precedence over the virtual host level retry policy entirely (e.g.: policies are not merged, - * most internal one becomes the enforced policy). :ref:`Retry policy ` + * most internal one becomes the enforced policy). :ref:`Retry policy ` * should not be set if this field is used. */ 'retry_policy_typed_config': (_google_protobuf_Any__Output | null); @@ -1129,7 +1138,7 @@ export interface RouteAction__Output { * If present, Envoy will try to follow an upstream redirect response instead of proxying the * response back to the downstream. An upstream redirect response is defined * by :ref:`redirect_response_codes - * `. + * `. */ 'internal_redirect_policy': (_envoy_config_route_v3_InternalRedirectPolicy__Output | null); /** @@ -1154,6 +1163,15 @@ export interface RouteAction__Output { * Specifies the maximum stream duration for this route. */ 'max_stream_duration': (_envoy_config_route_v3_RouteAction_MaxStreamDuration__Output | null); - 'cluster_specifier': "cluster"|"cluster_header"|"weighted_clusters"; + /** + * [#not-implemented-hide:] + * Name of the cluster specifier plugin to use to determine the cluster for + * requests on this route. The plugin name must be defined in the associated + * :ref:`envoy_v3_api_field_config.route.v3.RouteConfiguration.cluster_specifier_plugins` + * in the + * :ref:`envoy_v3_api_field_config.core.v3.TypedExtensionConfig.name` field. + */ + 'cluster_specifier_plugin'?: (string); + 'cluster_specifier': "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"; 'host_rewrite_specifier': "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts index ebb3c3419..516f4b06b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts @@ -5,16 +5,17 @@ import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, Head import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { Vhds as _envoy_config_route_v3_Vhds, Vhds__Output as _envoy_config_route_v3_Vhds__Output } from '../../../../envoy/config/route/v3/Vhds'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { ClusterSpecifierPlugin as _envoy_config_route_v3_ClusterSpecifierPlugin, ClusterSpecifierPlugin__Output as _envoy_config_route_v3_ClusterSpecifierPlugin__Output } from '../../../../envoy/config/route/v3/ClusterSpecifierPlugin'; /** - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface RouteConfiguration { /** * The name of the route configuration. For example, it might match * :ref:`route_config_name - * ` in - * :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. + * ` in + * :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. */ 'name'?: (string); /** @@ -31,8 +32,8 @@ export interface RouteConfiguration { /** * Specifies a list of HTTP headers that should be added to each response that * the connection manager encodes. Headers specified at this level are applied - * after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + * after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -45,8 +46,8 @@ export interface RouteConfiguration { /** * Specifies a list of HTTP headers that should be added to each request * routed by the HTTP connection manager. Headers specified at this level are - * applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + * applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -59,10 +60,10 @@ export interface RouteConfiguration { * route table will load and the router filter will return a 404 if the route * is selected at runtime. This setting defaults to true if the route table * is statically defined via the :ref:`route_config - * ` + * ` * option. This setting default to false if the route table is loaded dynamically via the * :ref:`rds - * ` + * ` * option. Users may wish to override the default behavior in certain cases (for example when * using CDS with a static route table). */ @@ -96,28 +97,35 @@ export interface RouteConfiguration { 'most_specific_header_mutations_wins'?: (boolean); /** * The maximum bytes of the response :ref:`direct response body - * ` size. If not specified the default + * ` size. If not specified the default * is 4096. * * .. warning:: * * Envoy currently holds the content of :ref:`direct response body - * ` in memory. Be careful setting + * ` in memory. Be careful setting * this to be larger than the default 4KB, since the allocated memory for direct response body * is not subject to data plane buffering controls. */ 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value | null); + /** + * [#not-implemented-hide:] + * A list of plugins and their configurations which may be used by a + * :ref:`envoy_v3_api_field_config.route.v3.RouteAction.cluster_specifier_plugin` + * within the route. All *extension.name* fields in this list must be unique. + */ + 'cluster_specifier_plugins'?: (_envoy_config_route_v3_ClusterSpecifierPlugin)[]; } /** - * [#next-free-field: 12] + * [#next-free-field: 13] */ export interface RouteConfiguration__Output { /** * The name of the route configuration. For example, it might match * :ref:`route_config_name - * ` in - * :ref:`envoy_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. + * ` in + * :ref:`envoy_v3_api_msg_extensions.filters.network.http_connection_manager.v3.Rds`. */ 'name': (string); /** @@ -134,8 +142,8 @@ export interface RouteConfiguration__Output { /** * Specifies a list of HTTP headers that should be added to each response that * the connection manager encodes. Headers specified at this level are applied - * after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + * after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -148,8 +156,8 @@ export interface RouteConfiguration__Output { /** * Specifies a list of HTTP headers that should be added to each request * routed by the HTTP connection manager. Headers specified at this level are - * applied after headers from any enclosed :ref:`envoy_api_msg_config.route.v3.VirtualHost` or - * :ref:`envoy_api_msg_config.route.v3.RouteAction`. For more information, including details on + * applied after headers from any enclosed :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost` or + * :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -162,10 +170,10 @@ export interface RouteConfiguration__Output { * route table will load and the router filter will return a 404 if the route * is selected at runtime. This setting defaults to true if the route table * is statically defined via the :ref:`route_config - * ` + * ` * option. This setting default to false if the route table is loaded dynamically via the * :ref:`rds - * ` + * ` * option. Users may wish to override the default behavior in certain cases (for example when * using CDS with a static route table). */ @@ -199,15 +207,22 @@ export interface RouteConfiguration__Output { 'most_specific_header_mutations_wins': (boolean); /** * The maximum bytes of the response :ref:`direct response body - * ` size. If not specified the default + * ` size. If not specified the default * is 4096. * * .. warning:: * * Envoy currently holds the content of :ref:`direct response body - * ` in memory. Be careful setting + * ` in memory. Be careful setting * this to be larger than the default 4KB, since the allocated memory for direct response body * is not subject to data plane buffering controls. */ 'max_direct_response_body_size_bytes': (_google_protobuf_UInt32Value__Output | null); + /** + * [#not-implemented-hide:] + * A list of plugins and their configurations which may be used by a + * :ref:`envoy_v3_api_field_config.route.v3.RouteAction.cluster_specifier_plugin` + * within the route. All *extension.name* fields in this list must be unique. + */ + 'cluster_specifier_plugins': (_envoy_config_route_v3_ClusterSpecifierPlugin__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts index ddf44da2f..9d872ed18 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts @@ -5,6 +5,7 @@ import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatch import type { QueryParameterMatcher as _envoy_config_route_v3_QueryParameterMatcher, QueryParameterMatcher__Output as _envoy_config_route_v3_QueryParameterMatcher__Output } from '../../../../envoy/config/route/v3/QueryParameterMatcher'; import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; +import type { MetadataMatcher as _envoy_type_matcher_v3_MetadataMatcher, MetadataMatcher__Output as _envoy_type_matcher_v3_MetadataMatcher__Output } from '../../../../envoy/type/matcher/v3/MetadataMatcher'; /** * An extensible message for matching CONNECT requests. @@ -51,7 +52,7 @@ export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Outpu } /** - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface RouteMatch { /** @@ -66,7 +67,7 @@ export interface RouteMatch { 'path'?: (string); /** * Indicates that prefix/path matching should be case sensitive. The default - * is true. + * is true. Ignored for safe_regex matching. */ 'case_sensitive'?: (_google_protobuf_BoolValue | null); /** @@ -83,6 +84,14 @@ export interface RouteMatch { * against all the specified query parameters. If the number of specified * query parameters is nonzero, they all must match the *path* header's * query string for a match to occur. + * + * .. note:: + * + * If query parameters are used to pass request message fields when + * `grpc_json_transcoder `_ + * is used, the transcoded message fields maybe different. The query parameters are + * url encoded, but the message fields are not. For example, if a query + * parameter is "foo%20bar", the message field will be "foo bar". */ 'query_parameters'?: (_envoy_config_route_v3_QueryParameterMatcher)[]; /** @@ -141,14 +150,21 @@ export interface RouteMatch { * where Extended CONNECT requests may have a path, the path matchers will work if * there is a path present. * Note that CONNECT support is currently considered alpha in Envoy. - * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] */ 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher | null); + /** + * Specifies a set of dynamic metadata matchers on which the route should match. + * The router will check the dynamic metadata against all the specified dynamic metadata matchers. + * If the number of specified dynamic metadata matchers is nonzero, they all must match the + * dynamic metadata for a match to occur. + */ + 'dynamic_metadata'?: (_envoy_type_matcher_v3_MetadataMatcher)[]; 'path_specifier'?: "prefix"|"path"|"safe_regex"|"connect_matcher"; } /** - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface RouteMatch__Output { /** @@ -163,7 +179,7 @@ export interface RouteMatch__Output { 'path'?: (string); /** * Indicates that prefix/path matching should be case sensitive. The default - * is true. + * is true. Ignored for safe_regex matching. */ 'case_sensitive': (_google_protobuf_BoolValue__Output | null); /** @@ -180,6 +196,14 @@ export interface RouteMatch__Output { * against all the specified query parameters. If the number of specified * query parameters is nonzero, they all must match the *path* header's * query string for a match to occur. + * + * .. note:: + * + * If query parameters are used to pass request message fields when + * `grpc_json_transcoder `_ + * is used, the transcoded message fields maybe different. The query parameters are + * url encoded, but the message fields are not. For example, if a query + * parameter is "foo%20bar", the message field will be "foo bar". */ 'query_parameters': (_envoy_config_route_v3_QueryParameterMatcher__Output)[]; /** @@ -238,8 +262,15 @@ export interface RouteMatch__Output { * where Extended CONNECT requests may have a path, the path matchers will work if * there is a path present. * Note that CONNECT support is currently considered alpha in Envoy. - * [#comment:TODO(htuch): Replace the above comment with an alpha tag. + * [#comment: TODO(htuch): Replace the above comment with an alpha tag.] */ 'connect_matcher'?: (_envoy_config_route_v3_RouteMatch_ConnectMatcher__Output | null); + /** + * Specifies a set of dynamic metadata matchers on which the route should match. + * The router will check the dynamic metadata against all the specified dynamic metadata matchers. + * If the number of specified dynamic metadata matchers is nonzero, they all must match the + * dynamic metadata for a match to occur. + */ + 'dynamic_metadata': (_envoy_type_matcher_v3_MetadataMatcher__Output)[]; 'path_specifier': "prefix"|"path"|"safe_regex"|"connect_matcher"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts index 74b5fe43d..5865eadd3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts @@ -19,7 +19,7 @@ export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment__O /** * Specifies a key which is matched against the output of the - * :ref:`scope_key_builder` + * :ref:`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. @@ -28,14 +28,14 @@ export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key { /** * The ordered set of fragments to match against. The order must match the * fragments in the corresponding - * :ref:`scope_key_builder`. + * :ref:`scope_key_builder`. */ 'fragments'?: (_envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment)[]; } /** * Specifies a key which is matched against the output of the - * :ref:`scope_key_builder` + * :ref:`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. @@ -44,20 +44,20 @@ export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key__Output { /** * The ordered set of fragments to match against. The order must match the * fragments in the corresponding - * :ref:`scope_key_builder`. + * :ref:`scope_key_builder`. */ 'fragments': (_envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment__Output)[]; } /** * Specifies a routing scope, which associates a - * :ref:`Key` to a - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). + * :ref:`Key` to a + * :ref:`envoy_v3_api_msg_config.route.v3.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` + * :ref:`scope_key_builder` * assigned to the HttpConnectionManager. * * For example, with the following configurations (in YAML): @@ -79,7 +79,7 @@ export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key__Output { * key: vip * * ScopedRouteConfiguration resources (specified statically via - * :ref:`scoped_route_configurations_list` + * :ref:`scoped_route_configurations_list` * or obtained dynamically via SRDS): * * .. code:: @@ -115,8 +115,8 @@ export interface ScopedRouteConfiguration { */ 'name'?: (string); /** - * The resource name to use for a :ref:`envoy_api_msg_service.discovery.v3.DiscoveryRequest` to an - * RDS server to fetch the :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` associated + * The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + * RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated * with this scope. */ 'route_configuration_name'?: (string); @@ -132,13 +132,13 @@ export interface ScopedRouteConfiguration { /** * Specifies a routing scope, which associates a - * :ref:`Key` to a - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). + * :ref:`Key` to a + * :ref:`envoy_v3_api_msg_config.route.v3.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` + * :ref:`scope_key_builder` * assigned to the HttpConnectionManager. * * For example, with the following configurations (in YAML): @@ -160,7 +160,7 @@ export interface ScopedRouteConfiguration { * key: vip * * ScopedRouteConfiguration resources (specified statically via - * :ref:`scoped_route_configurations_list` + * :ref:`scoped_route_configurations_list` * or obtained dynamically via SRDS): * * .. code:: @@ -196,8 +196,8 @@ export interface ScopedRouteConfiguration__Output { */ 'name': (string); /** - * The resource name to use for a :ref:`envoy_api_msg_service.discovery.v3.DiscoveryRequest` to an - * RDS server to fetch the :ref:`envoy_api_msg_config.route.v3.RouteConfiguration` associated + * The resource name to use for a :ref:`envoy_v3_api_msg_service.discovery.v3.DiscoveryRequest` to an + * RDS server to fetch the :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated * with this scope. */ 'route_configuration_name': (string); diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts index e1a9220d7..962e9d51a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts @@ -35,7 +35,7 @@ export interface Tracing { /** * A list of custom tags with unique tag name to create tags for the active span. * It will take effect after merging with the :ref:`corresponding configuration - * ` + * ` * configured in the HTTP connection manager. If two tags with the same name are configured * each in the HTTP connection manager and the route level, the one configured here takes * priority. @@ -75,7 +75,7 @@ export interface Tracing__Output { /** * A list of custom tags with unique tag name to create tags for the active span. * It will take effect after merging with the :ref:`corresponding configuration - * ` + * ` * configured in the HTTP connection manager. If two tags with the same name are configured * each in the HTTP connection manager and the route level, the one configured here takes * priority. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts index 1d3fb2309..017900ed9 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts @@ -87,8 +87,8 @@ export interface VirtualHost { /** * Specifies a list of HTTP headers that should be added to each request * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -100,8 +100,8 @@ export interface VirtualHost { /** * Specifies a list of HTTP headers that should be added to each response * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -124,7 +124,7 @@ export interface VirtualHost { * will see the attempt count as perceived by the second Envoy. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. * * [#next-major-version: rename to include_attempt_count_in_request.] */ @@ -136,7 +136,7 @@ export interface VirtualHost { * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. * [#comment: An entry's value may be wrapped in a - * :ref:`FilterConfig` + * :ref:`FilterConfig` * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); @@ -166,14 +166,14 @@ export interface VirtualHost { * will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. */ 'include_attempt_count_in_response'?: (boolean); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that setting a route level entry * will take precedence over this config and it'll be treated independently (e.g.: values are not - * inherited). :ref:`Retry policy ` should not be + * inherited). :ref:`Retry policy ` should not be * set if this field is used. */ 'retry_policy_typed_config'?: (_google_protobuf_Any | null); @@ -237,8 +237,8 @@ export interface VirtualHost__Output { /** * Specifies a list of HTTP headers that should be added to each request * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -250,8 +250,8 @@ export interface VirtualHost__Output { /** * Specifies a list of HTTP headers that should be added to each response * handled by this virtual host. Headers specified at this level are applied - * after headers from enclosed :ref:`envoy_api_msg_config.route.v3.Route` and before headers from the - * enclosing :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including + * after headers from enclosed :ref:`envoy_v3_api_msg_config.route.v3.Route` and before headers from the + * enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including * details on header value syntax, see the documentation on :ref:`custom request headers * `. */ @@ -274,7 +274,7 @@ export interface VirtualHost__Output { * will see the attempt count as perceived by the second Envoy. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. * * [#next-major-version: rename to include_attempt_count_in_request.] */ @@ -286,7 +286,7 @@ export interface VirtualHost__Output { * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. * [#comment: An entry's value may be wrapped in a - * :ref:`FilterConfig` + * :ref:`FilterConfig` * message to specify additional options.] */ 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); @@ -316,14 +316,14 @@ export interface VirtualHost__Output { * will see the attempt count as perceived by the Envoy closest upstream from itself. Defaults to false. * This header is unaffected by the * :ref:`suppress_envoy_headers - * ` flag. + * ` flag. */ 'include_attempt_count_in_response': (boolean); /** * [#not-implemented-hide:] * Specifies the configuration for retry policy extension. Note that setting a route level entry * will take precedence over this config and it'll be treated independently (e.g.: values are not - * inherited). :ref:`Retry policy ` should not be + * inherited). :ref:`Retry policy ` should not be * set if this field is used. */ 'retry_policy_typed_config': (_google_protobuf_Any__Output | null); diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts index 02edc0243..e734073be 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts @@ -6,17 +6,37 @@ import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, Head import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; /** - * [#next-free-field: 11] + * [#next-free-field: 13] */ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] * Name of the upstream cluster. The cluster must exist in the * :ref:`cluster manager configuration `. */ 'name'?: (string); + /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1 }] + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. If the + * header is not found or the referenced cluster does not exist, Envoy will + * return a 404 response. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 + * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header'?: (string); /** * An integer between 0 and :ref:`total_weight - * `. When a request matches the route, + * `. When a request matches the route, * the choice of an upstream cluster is determined by its weight. The sum of weights across all * entries in the clusters array must add up to the total_weight, which defaults to 100. */ @@ -25,38 +45,38 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in * the upstream cluster with metadata matching what is set in this field will be considered for * load balancing. Note that this will be merged with what's provided in - * :ref:`RouteAction.metadata_match `, with + * :ref:`RouteAction.metadata_match `, with * values here taking precedence. The filter name should be specified as *envoy.lb*. */ 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); /** * Specifies a list of headers to be added to requests when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of HTTP headers that should be removed from each request when - * this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. */ 'request_headers_to_remove'?: (string)[]; /** * Specifies a list of headers to be added to responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ 'response_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** * Specifies a list of headers to be removed from responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. */ 'response_headers_to_remove'?: (string)[]; /** @@ -66,24 +86,50 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. * [#comment: An entry's value may be wrapped in a - * :ref:`FilterConfig` + * :ref:`FilterConfig` * message to specify additional options.] */ 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); + /** + * Indicates that during forwarding, the host header will be swapped with + * this value. + */ + 'host_rewrite_literal'?: (string); + 'host_rewrite_specifier'?: "host_rewrite_literal"; } /** - * [#next-free-field: 11] + * [#next-free-field: 13] */ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] * Name of the upstream cluster. The cluster must exist in the * :ref:`cluster manager configuration `. */ 'name': (string); + /** + * Only one of *name* and *cluster_header* may be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1 }] + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. If the + * header is not found or the referenced cluster does not exist, Envoy will + * return a 404 response. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 + * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header': (string); /** * An integer between 0 and :ref:`total_weight - * `. When a request matches the route, + * `. When a request matches the route, * the choice of an upstream cluster is determined by its weight. The sum of weights across all * entries in the clusters array must add up to the total_weight, which defaults to 100. */ @@ -92,38 +138,38 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { * Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in * the upstream cluster with metadata matching what is set in this field will be considered for * load balancing. Note that this will be merged with what's provided in - * :ref:`RouteAction.metadata_match `, with + * :ref:`RouteAction.metadata_match `, with * values here taking precedence. The filter name should be specified as *envoy.lb*. */ 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); /** * Specifies a list of headers to be added to requests when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of HTTP headers that should be removed from each request when - * this cluster is selected through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * this cluster is selected through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. */ 'request_headers_to_remove': (string)[]; /** * Specifies a list of headers to be added to responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. * Headers specified at this level are applied before headers from the enclosing - * :ref:`envoy_api_msg_config.route.v3.Route`, :ref:`envoy_api_msg_config.route.v3.VirtualHost`, and - * :ref:`envoy_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on + * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`, and + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. For more information, including details on * header value syntax, see the documentation on :ref:`custom request headers * `. */ 'response_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** * Specifies a list of headers to be removed from responses when this cluster is selected - * through the enclosing :ref:`envoy_api_msg_config.route.v3.RouteAction`. + * through the enclosing :ref:`envoy_v3_api_msg_config.route.v3.RouteAction`. */ 'response_headers_to_remove': (string)[]; /** @@ -133,16 +179,22 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { * specific; see the :ref:`HTTP filter documentation ` * for if and how it is utilized. * [#comment: An entry's value may be wrapped in a - * :ref:`FilterConfig` + * :ref:`FilterConfig` * message to specify additional options.] */ 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); + /** + * Indicates that during forwarding, the host header will be swapped with + * this value. + */ + 'host_rewrite_literal'?: (string); + 'host_rewrite_specifier': "host_rewrite_literal"; } /** - * Compared to the :ref:`cluster ` field that specifies a + * Compared to the :ref:`cluster ` field that specifies a * single upstream cluster as the target of a request, the :ref:`weighted_clusters - * ` option allows for specification of + * ` option allows for specification of * multiple upstream clusters along with weights that indicate the percentage of * traffic to be forwarded to each cluster. The router selects an upstream cluster based on the * weights. @@ -171,9 +223,9 @@ export interface WeightedCluster { } /** - * Compared to the :ref:`cluster ` field that specifies a + * Compared to the :ref:`cluster ` field that specifies a * single upstream cluster as the target of a request, the :ref:`weighted_clusters - * ` option allows for specification of + * ` option allows for specification of * multiple upstream clusters along with weights that indicate the percentage of * traffic to be forwarded to each cluster. The router selects an upstream cluster based on the * weights. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts index 95b161939..9b9859bc5 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/trace/v3/Tracing.ts @@ -6,34 +6,21 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ * Configuration for an HTTP tracer provider used by Envoy. * * The configuration is defined by the - * :ref:`HttpConnectionManager.Tracing ` - * :ref:`provider ` + * :ref:`HttpConnectionManager.Tracing ` + * :ref:`provider ` * field. */ export interface _envoy_config_trace_v3_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* + * supported HTTP trace driver. + * See the :ref:`extensions listed in typed_config below ` for the default list of the HTTP trace driver. */ 'name'?: (string); 'typed_config'?: (_google_protobuf_Any | null); /** - * Trace driver specific configuration which depends on the driver being instantiated. - * See the trace drivers for examples: - * - * - :ref:`LightstepConfig ` - * - :ref:`ZipkinConfig ` - * - :ref:`DynamicOtConfig ` - * - :ref:`DatadogConfig ` - * - :ref:`OpenCensusConfig ` - * - :ref:`AWS X-Ray ` + * Trace driver specific configuration which must be set according to the driver being instantiated. + * [#extension-category: envoy.tracers] */ 'config_type'?: "typed_config"; } @@ -42,34 +29,21 @@ export interface _envoy_config_trace_v3_Tracing_Http { * Configuration for an HTTP tracer provider used by Envoy. * * The configuration is defined by the - * :ref:`HttpConnectionManager.Tracing ` - * :ref:`provider ` + * :ref:`HttpConnectionManager.Tracing ` + * :ref:`provider ` * field. */ export interface _envoy_config_trace_v3_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* + * supported HTTP trace driver. + * See the :ref:`extensions listed in typed_config below ` for the default list of the HTTP trace driver. */ 'name': (string); 'typed_config'?: (_google_protobuf_Any__Output | null); /** - * Trace driver specific configuration which depends on the driver being instantiated. - * See the trace drivers for examples: - * - * - :ref:`LightstepConfig ` - * - :ref:`ZipkinConfig ` - * - :ref:`DynamicOtConfig ` - * - :ref:`DatadogConfig ` - * - :ref:`OpenCensusConfig ` - * - :ref:`AWS X-Ray ` + * Trace driver specific configuration which must be set according to the driver being instantiated. + * [#extension-category: envoy.tracers] */ 'config_type': "typed_config"; } @@ -83,7 +57,7 @@ export interface _envoy_config_trace_v3_Tracing_Http__Output { * .. attention:: * * Use of this message type has been deprecated in favor of direct use of - * :ref:`Tracing.Http `. + * :ref:`Tracing.Http `. */ export interface Tracing { /** @@ -101,7 +75,7 @@ export interface Tracing { * .. attention:: * * Use of this message type has been deprecated in favor of direct use of - * :ref:`Tracing.Http `. + * :ref:`Tracing.Http `. */ export interface Tracing__Output { /** diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts index 8e2739958..bec0403e4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts @@ -30,17 +30,16 @@ export interface _envoy_extensions_filters_common_fault_v3_FaultDelay_HeaderDela /** * Delay specification is used to inject latency into the - * HTTP/gRPC/Mongo/Redis operation or delay proxying of TCP connections. + * HTTP/Mongo operation. * [#next-free-field: 6] */ export interface FaultDelay { /** * Add a fixed delay before forwarding the operation upstream. See * https://developers.google.com/protocol-buffers/docs/proto3#json for - * the JSON/YAML Duration mapping. For HTTP/Mongo/Redis, the specified - * delay will be injected before a new request/operation. For TCP - * connections, the proxying of the connection upstream will be delayed - * for the specified period. This is required if type is FIXED. + * the JSON/YAML Duration mapping. For HTTP/Mongo, the specified + * delay will be injected before a new request/operation. + * This is required if type is FIXED. */ 'fixed_delay'?: (_google_protobuf_Duration | null); /** @@ -56,17 +55,16 @@ export interface FaultDelay { /** * Delay specification is used to inject latency into the - * HTTP/gRPC/Mongo/Redis operation or delay proxying of TCP connections. + * HTTP/Mongo operation. * [#next-free-field: 6] */ export interface FaultDelay__Output { /** * Add a fixed delay before forwarding the operation upstream. See * https://developers.google.com/protocol-buffers/docs/proto3#json for - * the JSON/YAML Duration mapping. For HTTP/Mongo/Redis, the specified - * delay will be injected before a new request/operation. For TCP - * connections, the proxying of the connection upstream will be delayed - * for the specified period. This is required if type is FIXED. + * the JSON/YAML Duration mapping. For HTTP/Mongo, the specified + * delay will be injected before a new request/operation. + * This is required if type is FIXED. */ 'fixed_delay'?: (_google_protobuf_Duration__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts index 20c543508..d78bd9e4a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts @@ -7,7 +7,7 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a import type { FaultRateLimit as _envoy_extensions_filters_common_fault_v3_FaultRateLimit, FaultRateLimit__Output as _envoy_extensions_filters_common_fault_v3_FaultRateLimit__Output } from '../../../../../../envoy/extensions/filters/common/fault/v3/FaultRateLimit'; /** - * [#next-free-field: 15] + * [#next-free-field: 16] */ export interface HTTPFault { /** @@ -31,7 +31,7 @@ export interface HTTPFault { * injection filter can be applied selectively to requests that match a set of * headers specified in the fault filter config. The chances of actual fault * injection further depend on the value of the :ref:`percentage - * ` field. + * ` field. * The filter will check the request's headers against all the specified * headers in the filter config. A match will happen if all the headers in the * config are present in the request with the same values (or based on @@ -107,10 +107,17 @@ export interface HTTPFault { * runtime. The default is: fault.http.abort.grpc_status */ 'abort_grpc_status_runtime'?: (string); + /** + * To control whether stats storage is allocated dynamically for each downstream server. + * If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + * If set to false, dynamic stats storage will be allocated for the downstream cluster name. + * Default value is false. + */ + 'disable_downstream_cluster_stats'?: (boolean); } /** - * [#next-free-field: 15] + * [#next-free-field: 16] */ export interface HTTPFault__Output { /** @@ -134,7 +141,7 @@ export interface HTTPFault__Output { * injection filter can be applied selectively to requests that match a set of * headers specified in the fault filter config. The chances of actual fault * injection further depend on the value of the :ref:`percentage - * ` field. + * ` field. * The filter will check the request's headers against all the specified * headers in the filter config. A match will happen if all the headers in the * config are present in the request with the same values (or based on @@ -210,4 +217,11 @@ export interface HTTPFault__Output { * runtime. The default is: fault.http.abort.grpc_status */ 'abort_grpc_status_runtime': (string); + /** + * To control whether stats storage is allocated dynamically for each downstream server. + * If set to true, "x-envoy-downstream-service-cluster" field of header will be ignored by this filter. + * If set to false, dynamic stats storage will be allocated for the downstream cluster name. + * Default value is false. + */ + 'disable_downstream_cluster_stats': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/EnvoyMobileHttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/EnvoyMobileHttpConnectionManager.ts new file mode 100644 index 000000000..eb73721c3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/EnvoyMobileHttpConnectionManager.ts @@ -0,0 +1,29 @@ +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +import type { HttpConnectionManager as _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager, HttpConnectionManager__Output as _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; + +/** + * [#protodoc-title: Envoy Mobile HTTP connection manager] + * HTTP connection manager for use in Envoy mobile. + * [#extension: envoy.filters.network.envoy_mobile_http_connection_manager] + */ +export interface EnvoyMobileHttpConnectionManager { + /** + * The configuration for the underlying HttpConnectionManager which will be + * instantiated for Envoy mobile. + */ + 'config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager | null); +} + +/** + * [#protodoc-title: Envoy Mobile HTTP connection manager] + * HTTP connection manager for use in Envoy mobile. + * [#extension: envoy.filters.network.envoy_mobile_http_connection_manager] + */ +export interface EnvoyMobileHttpConnectionManager__Output { + /** + * The configuration for the underlying HttpConnectionManager which will be + * instantiated for Envoy mobile. + */ + 'config': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts index 4a8912599..fdf7084fe 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts @@ -13,9 +13,13 @@ import type { ScopedRoutes as _envoy_extensions_filters_network_http_connection_ import type { HttpProtocolOptions as _envoy_config_core_v3_HttpProtocolOptions, HttpProtocolOptions__Output as _envoy_config_core_v3_HttpProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/HttpProtocolOptions'; import type { RequestIDExtension as _envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension, RequestIDExtension__Output as _envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/RequestIDExtension'; import type { LocalReplyConfig as _envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig, LocalReplyConfig__Output as _envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output } from '../../../../../../envoy/extensions/filters/network/http_connection_manager/v3/LocalReplyConfig'; +import type { Http3ProtocolOptions as _envoy_config_core_v3_Http3ProtocolOptions, Http3ProtocolOptions__Output as _envoy_config_core_v3_Http3ProtocolOptions__Output } from '../../../../../../envoy/config/core/v3/Http3ProtocolOptions'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { SchemeHeaderTransformation as _envoy_config_core_v3_SchemeHeaderTransformation, SchemeHeaderTransformation__Output as _envoy_config_core_v3_SchemeHeaderTransformation__Output } from '../../../../../../envoy/config/core/v3/SchemeHeaderTransformation'; import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../../../envoy/type/v3/Percent'; import type { CustomTag as _envoy_type_tracing_v3_CustomTag, CustomTag__Output as _envoy_type_tracing_v3_CustomTag__Output } from '../../../../../../envoy/type/tracing/v3/CustomTag'; import type { _envoy_config_trace_v3_Tracing_Http, _envoy_config_trace_v3_Tracing_Http__Output } from '../../../../../../envoy/config/trace/v3/Tracing'; +import type { PathTransformation as _envoy_type_http_v3_PathTransformation, PathTransformation__Output as _envoy_type_http_v3_PathTransformation__Output } from '../../../../../../envoy/type/http/v3/PathTransformation'; // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -106,6 +110,120 @@ export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpCon EGRESS = 1, } +/** + * [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied + * before any processing of requests by HTTP filters, routing, and matching. Only the normalized + * path will be visible internally if a transformation is enabled. Any path rewrites that the + * router performs (e.g. :ref:`regex_rewrite + * ` or :ref:`prefix_rewrite + * `) will apply to the *:path* header + * destined for the upstream. + * + * Note: access logging and tracing will show the original *:path* header. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions { + /** + * [#not-implemented-hide:] Normalization applies internally before any processing of requests by + * HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults + * to :ref:`NormalizePathRFC3986 + * `. When not + * specified, this value may be overridden by the runtime variable + * :ref:`http_connection_manager.normalize_path`. + * Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'forwarding_transformation'?: (_envoy_type_http_v3_PathTransformation | null); + /** + * [#not-implemented-hide:] Normalization only applies internally before any processing of + * requests by HTTP filters, routing, and matching. These will be applied after full + * transformation is applied. The *:path* header before this transformation will be restored in + * the router filter and sent upstream unless it was mutated by a filter. Defaults to no + * transformations. + * Multiple actions can be applied in the same Transformation, forming a sequential + * pipeline. The transformations will be performed in the order that they appear. Envoy will + * respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'http_filter_transformation'?: (_envoy_type_http_v3_PathTransformation | null); +} + +/** + * [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied + * before any processing of requests by HTTP filters, routing, and matching. Only the normalized + * path will be visible internally if a transformation is enabled. Any path rewrites that the + * router performs (e.g. :ref:`regex_rewrite + * ` or :ref:`prefix_rewrite + * `) will apply to the *:path* header + * destined for the upstream. + * + * Note: access logging and tracing will show the original *:path* header. + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions__Output { + /** + * [#not-implemented-hide:] Normalization applies internally before any processing of requests by + * HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults + * to :ref:`NormalizePathRFC3986 + * `. When not + * specified, this value may be overridden by the runtime variable + * :ref:`http_connection_manager.normalize_path`. + * Envoy will respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'forwarding_transformation': (_envoy_type_http_v3_PathTransformation__Output | null); + /** + * [#not-implemented-hide:] Normalization only applies internally before any processing of + * requests by HTTP filters, routing, and matching. These will be applied after full + * transformation is applied. The *:path* header before this transformation will be restored in + * the router filter and sent upstream unless it was mutated by a filter. Defaults to no + * transformations. + * Multiple actions can be applied in the same Transformation, forming a sequential + * pipeline. The transformations will be performed in the order that they appear. Envoy will + * respond with 400 to paths that are malformed (e.g. for paths that fail RFC 3986 + * normalization due to disallowed characters.) + */ + 'http_filter_transformation': (_envoy_type_http_v3_PathTransformation__Output | null); +} + +// Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto + +/** + * Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + * This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + */ +export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction { + /** + * Default behavior specific to implementation (i.e. Envoy) of this configuration option. + * Envoy, by default, takes the KEEP_UNCHANGED action. + * NOTE: the implementation may change the default behavior at-will. + */ + IMPLEMENTATION_SPECIFIC_DEFAULT = 0, + /** + * Keep escaped slashes. + */ + KEEP_UNCHANGED = 1, + /** + * Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + * The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + */ + REJECT_REQUEST = 2, + /** + * Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + * Redirect occurs after path normalization and merge slashes transformations if they were configured. + * NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + * This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + * traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + * The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + * redirected request. + */ + UNESCAPE_AND_REDIRECT = 3, + /** + * Unescape %2F and %5C sequences. + * Note: this option should not be enabled if intermediaries perform path based access control as + * it may lead to path confusion vulnerabilities. + */ + UNESCAPE_AND_FORWARD = 4, +} + // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation { @@ -346,7 +464,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht /** * Determines if upgrades are enabled or disabled by default. Defaults to true. * This can be overridden on a per-route basis with :ref:`cluster - * ` as documented in the + * ` as documented in the * :ref:`upgrade documentation `. */ 'enabled'?: (_google_protobuf_BoolValue | null); @@ -383,14 +501,14 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht /** * Determines if upgrades are enabled or disabled by default. Defaults to true. * This can be overridden on a per-route basis with :ref:`cluster - * ` as documented in the + * ` as documented in the * :ref:`upgrade documentation `. */ 'enabled': (_google_protobuf_BoolValue__Output | null); } /** - * [#next-free-field: 43] + * [#next-free-field: 49] */ export interface HttpConnectionManager { /** @@ -426,7 +544,7 @@ export interface HttpConnectionManager { /** * Presence of the object defines whether the connection manager * emits :ref:`tracing ` data to the :ref:`configured tracing provider - * `. + * `. */ 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing | null); /** @@ -483,7 +601,7 @@ export interface HttpConnectionManager { 'forward_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); /** * This field is valid only when :ref:`forward_client_cert_details - * ` + * ` * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in * the client certificate to be forwarded. Note that in the * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and @@ -509,7 +627,7 @@ export interface HttpConnectionManager { /** * If * :ref:`use_remote_address - * ` + * ` * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. * This is useful for testing compatibility of upstream services that parse the header value. For @@ -527,7 +645,7 @@ export interface HttpConnectionManager { * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in * conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager * has mutated the request headers. While :ref:`use_remote_address - * ` + * ` * will also suppress XFF addition, it has consequences for logging and other * Envoy uses of the remote address, so *skip_xff_append* should be used * when only an elision of XFF addition is intended. @@ -548,10 +666,10 @@ export interface HttpConnectionManager { * * This idle timeout applies to new streams and is overridable by the * :ref:`route-level idle_timeout - * `. Even on a stream in + * `. Even on a stream in * which the override applies, prior to receipt of the initial request * headers, the :ref:`stream_idle_timeout - * ` + * ` * applies. Each time an encode/decode event for headers or data is processed * for the stream, the timer will be reset. If the timeout fires, the stream * is terminated with a 408 Request Timeout error code if no upstream response @@ -564,12 +682,12 @@ export interface HttpConnectionManager { * data has been proxied within available flow control windows. If the timeout is hit in this * case, the :ref:`tx_flush_timeout ` counter will be * incremented. Note that :ref:`max_stream_duration - * ` does not apply to + * ` does not apply to * this corner case. * * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" * is configured, this timeout is scaled according to the value for - * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. * * Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due * to the granularity of events presented to the connection manager. For example, while receiving @@ -632,8 +750,6 @@ export interface HttpConnectionManager { * The maximum request headers size for incoming connections. * If unconfigured, the default max request headers allowed is 60 KiB. * Requests that exceed this limit will receive a 431 response. - * The max configurable limit is 96 KiB, based on current implementation - * constraints. */ 'max_request_headers_kb'?: (_google_protobuf_UInt32Value | null); /** @@ -684,15 +800,21 @@ export interface HttpConnectionManager { 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions | null); /** * The configuration of the request ID extension. This includes operations such as - * generation, validation, and associated tracing operations. - * - * If not set, Envoy uses the default UUID-based behavior: + * generation, validation, and associated tracing operations. If empty, the + * :ref:`UuidRequestIdConfig ` + * default extension is used with default parameters. See the documentation for that extension + * for details on what it does. Customizing the configuration for the default extension can be + * achieved by configuring it explicitly here. For example, to disable trace reason packing, + * the following configuration can be used: * - * 1. Request ID is propagated using *x-request-id* header. + * .. validated-code-block:: yaml + * :type-name: envoy.extensions.filters.network.http_connection_manager.v3.RequestIDExtension * - * 2. Request ID is a universally unique identifier (UUID). + * typed_config: + * "@type": type.googleapis.com/envoy.extensions.request_id.uuid.v3.UuidRequestIdConfig + * pack_trace_reason: false * - * 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. + * [#extension-category: envoy.request_id] */ 'request_id_extension'?: (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension | null); /** @@ -709,10 +831,12 @@ export interface HttpConnectionManager { 'local_reply_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig | null); /** * Determines if the port part should be removed from host/authority header before any processing - * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` - * local port and request method is not CONNECT. This affects the upstream host header as well. + * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + * local port. This affects the upstream host header unless the method is + * CONNECT in which case if no filter adds a port the original port will be restored before headers are + * sent upstream. * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. */ @@ -744,20 +868,82 @@ export interface HttpConnectionManager { 'request_headers_timeout'?: (_google_protobuf_Duration | null); /** * Determines if the port part should be removed from host/authority header before any processing - * of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. - * This affects the upstream host header as well. + * of request by HTTP filters or routing. + * This affects the upstream host header unless the method is CONNECT in + * which case if no filter adds a port the original port will be restored before headers are sent upstream. * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. */ 'strip_any_host_port'?: (boolean); + /** + * [#not-implemented-hide:] Path normalization configuration. This includes + * configurations for transformations (e.g. RFC 3986 normalization or merge + * adjacent slashes) and the policy to apply them. The policy determines + * whether transformations affect the forwarded *:path* header. RFC 3986 path + * normalization is enabled by default and the default policy is that the + * normalized header will be forwarded. See :ref:`PathNormalizationOptions + * ` + * for details. + */ + 'path_normalization_options'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions | null); + /** + * Additional HTTP/3 settings that are passed directly to the HTTP/3 codec. + * [#not-implemented-hide:] + */ + 'http3_protocol_options'?: (_envoy_config_core_v3_Http3ProtocolOptions | null); + /** + * Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + * The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + * runtime variable. + * The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + * variable can be used to apply the action to a portion of all requests. + */ + 'path_with_escaped_slashes_action'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); + /** + * The configuration for the original IP detection extensions. + * + * When configured the extensions will be called along with the request headers + * and information about the downstream connection, such as the directly connected address. + * Each extension will then use these parameters to decide the request's effective remote address. + * If an extension fails to detect the original IP address and isn't configured to reject + * the request, the HCM will try the remaining extensions until one succeeds or rejects + * the request. If the request isn't rejected nor any extension succeeds, the HCM will + * fallback to using the remote address. + * + * .. WARNING:: + * Extensions cannot be used in conjunction with :ref:`use_remote_address + * ` + * nor :ref:`xff_num_trusted_hops + * `. + * + * [#extension-category: envoy.http.original_ip_detection] + */ + 'original_ip_detection_extensions'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Determines if trailing dot of the host should be removed from host/authority header before any + * processing of request by HTTP filters or routing. + * This affects the upstream host header. + * Without setting this option, incoming requests with host `example.com.` will not match against + * route with :ref:`domains` match set to `example.com`. Defaults to `false`. + * When the incoming request contains a host/authority header that includes a port number, + * setting this option will strip a trailing dot, if present, from the host section, + * leaving the port as is (e.g. host value `example.com.:443` will be updated to `example.com:443`). + */ + 'strip_trailing_host_dot'?: (boolean); + /** + * Allows for explicit transformation of the :scheme header on the request path. + * If not set, Envoy's default :ref:`scheme ` + * handling applies. + */ + 'scheme_header_transformation'?: (_envoy_config_core_v3_SchemeHeaderTransformation | null); 'route_specifier'?: "rds"|"route_config"|"scoped_routes"; 'strip_port_mode'?: "strip_any_host_port"; } /** - * [#next-free-field: 43] + * [#next-free-field: 49] */ export interface HttpConnectionManager__Output { /** @@ -793,7 +979,7 @@ export interface HttpConnectionManager__Output { /** * Presence of the object defines whether the connection manager * emits :ref:`tracing ` data to the :ref:`configured tracing provider - * `. + * `. */ 'tracing': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output | null); /** @@ -850,7 +1036,7 @@ export interface HttpConnectionManager__Output { 'forward_client_cert_details': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); /** * This field is valid only when :ref:`forward_client_cert_details - * ` + * ` * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in * the client certificate to be forwarded. Note that in the * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and @@ -876,7 +1062,7 @@ export interface HttpConnectionManager__Output { /** * If * :ref:`use_remote_address - * ` + * ` * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. * This is useful for testing compatibility of upstream services that parse the header value. For @@ -894,7 +1080,7 @@ export interface HttpConnectionManager__Output { * :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in * conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager * has mutated the request headers. While :ref:`use_remote_address - * ` + * ` * will also suppress XFF addition, it has consequences for logging and other * Envoy uses of the remote address, so *skip_xff_append* should be used * when only an elision of XFF addition is intended. @@ -915,10 +1101,10 @@ export interface HttpConnectionManager__Output { * * This idle timeout applies to new streams and is overridable by the * :ref:`route-level idle_timeout - * `. Even on a stream in + * `. Even on a stream in * which the override applies, prior to receipt of the initial request * headers, the :ref:`stream_idle_timeout - * ` + * ` * applies. Each time an encode/decode event for headers or data is processed * for the stream, the timer will be reset. If the timeout fires, the stream * is terminated with a 408 Request Timeout error code if no upstream response @@ -931,12 +1117,12 @@ export interface HttpConnectionManager__Output { * data has been proxied within available flow control windows. If the timeout is hit in this * case, the :ref:`tx_flush_timeout ` counter will be * incremented. Note that :ref:`max_stream_duration - * ` does not apply to + * ` does not apply to * this corner case. * * If the :ref:`overload action ` "envoy.overload_actions.reduce_timeouts" * is configured, this timeout is scaled according to the value for - * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. + * :ref:`HTTP_DOWNSTREAM_STREAM_IDLE `. * * Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due * to the granularity of events presented to the connection manager. For example, while receiving @@ -999,8 +1185,6 @@ export interface HttpConnectionManager__Output { * The maximum request headers size for incoming connections. * If unconfigured, the default max request headers allowed is 60 KiB. * Requests that exceed this limit will receive a 431 response. - * The max configurable limit is 96 KiB, based on current implementation - * constraints. */ 'max_request_headers_kb': (_google_protobuf_UInt32Value__Output | null); /** @@ -1051,15 +1235,21 @@ export interface HttpConnectionManager__Output { 'common_http_protocol_options': (_envoy_config_core_v3_HttpProtocolOptions__Output | null); /** * The configuration of the request ID extension. This includes operations such as - * generation, validation, and associated tracing operations. + * generation, validation, and associated tracing operations. If empty, the + * :ref:`UuidRequestIdConfig ` + * default extension is used with default parameters. See the documentation for that extension + * for details on what it does. Customizing the configuration for the default extension can be + * achieved by configuring it explicitly here. For example, to disable trace reason packing, + * the following configuration can be used: * - * If not set, Envoy uses the default UUID-based behavior: + * .. validated-code-block:: yaml + * :type-name: envoy.extensions.filters.network.http_connection_manager.v3.RequestIDExtension * - * 1. Request ID is propagated using *x-request-id* header. + * typed_config: + * "@type": type.googleapis.com/envoy.extensions.request_id.uuid.v3.UuidRequestIdConfig + * pack_trace_reason: false * - * 2. Request ID is a universally unique identifier (UUID). - * - * 3. Tracing decision (sampled, forced, etc) is set in 14th byte of the UUID. + * [#extension-category: envoy.request_id] */ 'request_id_extension': (_envoy_extensions_filters_network_http_connection_manager_v3_RequestIDExtension__Output | null); /** @@ -1076,10 +1266,12 @@ export interface HttpConnectionManager__Output { 'local_reply_config': (_envoy_extensions_filters_network_http_connection_manager_v3_LocalReplyConfig__Output | null); /** * Determines if the port part should be removed from host/authority header before any processing - * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` - * local port and request method is not CONNECT. This affects the upstream host header as well. + * of request by HTTP filters or routing. The port would be removed only if it is equal to the :ref:`listener's` + * local port. This affects the upstream host header unless the method is + * CONNECT in which case if no filter adds a port the original port will be restored before headers are + * sent upstream. * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. */ @@ -1111,14 +1303,76 @@ export interface HttpConnectionManager__Output { 'request_headers_timeout': (_google_protobuf_Duration__Output | null); /** * Determines if the port part should be removed from host/authority header before any processing - * of request by HTTP filters or routing. The port would be removed only if request method is not CONNECT. - * This affects the upstream host header as well. + * of request by HTTP filters or routing. + * This affects the upstream host header unless the method is CONNECT in + * which case if no filter adds a port the original port will be restored before headers are sent upstream. * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. */ 'strip_any_host_port'?: (boolean); + /** + * [#not-implemented-hide:] Path normalization configuration. This includes + * configurations for transformations (e.g. RFC 3986 normalization or merge + * adjacent slashes) and the policy to apply them. The policy determines + * whether transformations affect the forwarded *:path* header. RFC 3986 path + * normalization is enabled by default and the default policy is that the + * normalized header will be forwarded. See :ref:`PathNormalizationOptions + * ` + * for details. + */ + 'path_normalization_options': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions__Output | null); + /** + * Additional HTTP/3 settings that are passed directly to the HTTP/3 codec. + * [#not-implemented-hide:] + */ + 'http3_protocol_options': (_envoy_config_core_v3_Http3ProtocolOptions__Output | null); + /** + * Action to take when request URL path contains escaped slash sequences (%2F, %2f, %5C and %5c). + * The default value can be overridden by the :ref:`http_connection_manager.path_with_escaped_slashes_action` + * runtime variable. + * The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime + * variable can be used to apply the action to a portion of all requests. + */ + 'path_with_escaped_slashes_action': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); + /** + * The configuration for the original IP detection extensions. + * + * When configured the extensions will be called along with the request headers + * and information about the downstream connection, such as the directly connected address. + * Each extension will then use these parameters to decide the request's effective remote address. + * If an extension fails to detect the original IP address and isn't configured to reject + * the request, the HCM will try the remaining extensions until one succeeds or rejects + * the request. If the request isn't rejected nor any extension succeeds, the HCM will + * fallback to using the remote address. + * + * .. WARNING:: + * Extensions cannot be used in conjunction with :ref:`use_remote_address + * ` + * nor :ref:`xff_num_trusted_hops + * `. + * + * [#extension-category: envoy.http.original_ip_detection] + */ + 'original_ip_detection_extensions': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Determines if trailing dot of the host should be removed from host/authority header before any + * processing of request by HTTP filters or routing. + * This affects the upstream host header. + * Without setting this option, incoming requests with host `example.com.` will not match against + * route with :ref:`domains` match set to `example.com`. Defaults to `false`. + * When the incoming request contains a host/authority header that includes a port number, + * setting this option will strip a trailing dot, if present, from the host section, + * leaving the port as is (e.g. host value `example.com.:443` will be updated to `example.com:443`). + */ + 'strip_trailing_host_dot': (boolean); + /** + * Allows for explicit transformation of the :scheme header on the request path. + * If not set, Envoy's default :ref:`scheme ` + * handling applies. + */ + 'scheme_header_transformation': (_envoy_config_core_v3_SchemeHeaderTransformation__Output | null); 'route_specifier': "rds"|"route_config"|"scoped_routes"; 'strip_port_mode': "strip_any_host_port"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts index 42e4215d0..d1c1cbc06 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts @@ -16,19 +16,29 @@ export interface HttpFilter { /** * Filter specific configuration which depends on the filter being instantiated. See the supported * filters for further documentation. + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. + * [#extension-category: envoy.filters.http] */ 'typed_config'?: (_google_protobuf_Any | null); /** * Configuration source specifier for an extension configuration discovery service. * In case of a failure and without the default configuration, the HTTP listener responds with code 500. * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. This works for both the default filter configuration as well + * as for filters provided via the API. */ 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource | null); /** * If true, clients that do not support this filter may ignore the * filter but otherwise accept the config. * Otherwise, clients that do not support this filter must reject the config. - * [#not-implemented-hide:] + * This is also same with typed per filter config. */ 'is_optional'?: (boolean); 'config_type'?: "typed_config"|"config_discovery"; @@ -47,19 +57,29 @@ export interface HttpFilter__Output { /** * Filter specific configuration which depends on the filter being instantiated. See the supported * filters for further documentation. + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. + * [#extension-category: envoy.filters.http] */ 'typed_config'?: (_google_protobuf_Any__Output | null); /** * Configuration source specifier for an extension configuration discovery service. * In case of a failure and without the default configuration, the HTTP listener responds with code 500. * Extension configs delivered through this mechanism are not expected to require warming (see https://github.com/envoyproxy/envoy/issues/12061). + * + * To support configuring a :ref:`match tree `, use an + * :ref:`ExtensionWithMatcher ` + * with the desired HTTP filter. This works for both the default filter configuration as well + * as for filters provided via the API. */ 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output | null); /** * If true, clients that do not support this filter may ignore the * filter but otherwise accept the config. * Otherwise, clients that do not support this filter must reject the config. - * [#not-implemented-hide:] + * This is also same with typed per filter config. */ 'is_optional': (boolean); 'config_type': "typed_config"|"config_discovery"; diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts index e4565ce80..5f7d2b6fe 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRds.ts @@ -7,6 +7,11 @@ export interface ScopedRds { * Configuration source specifier for scoped RDS. */ 'scoped_rds_config_source'?: (_envoy_config_core_v3_ConfigSource | null); + /** + * xdstp:// resource locator for scoped RDS collection. + * [#not-implemented-hide:] + */ + 'srds_resources_locator'?: (string); } export interface ScopedRds__Output { @@ -14,4 +19,9 @@ export interface ScopedRds__Output { * Configuration source specifier for scoped RDS. */ 'scoped_rds_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); + /** + * xdstp:// resource locator for scoped RDS collection. + * [#not-implemented-hide:] + */ + 'srds_resources_locator': (string); } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts index 8aecd3883..041af534b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ScopedRoutes.ts @@ -160,19 +160,19 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Sc /** * Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These - * keys are matched against a set of :ref:`Key` - * objects assembled from :ref:`ScopedRouteConfiguration` + * keys are matched against a set of :ref:`Key` + * objects assembled from :ref:`ScopedRouteConfiguration` * messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - * :ref:`scoped_route_configurations_list`. + * :ref:`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`) to use for the request. + * :ref:`RouteConfiguration`) to use for the request. */ export interface _envoy_extensions_filters_network_http_connection_manager_v3_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`. + * fragments of a :ref:`ScopedRouteConfiguration`. * A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. */ 'fragments'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder)[]; @@ -180,19 +180,19 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Sc /** * Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These - * keys are matched against a set of :ref:`Key` - * objects assembled from :ref:`ScopedRouteConfiguration` + * keys are matched against a set of :ref:`Key` + * objects assembled from :ref:`ScopedRouteConfiguration` * messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via - * :ref:`scoped_route_configurations_list`. + * :ref:`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`) to use for the request. + * :ref:`RouteConfiguration`) to use for the request. */ export interface _envoy_extensions_filters_network_http_connection_manager_v3_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`. + * fragments of a :ref:`ScopedRouteConfiguration`. * A missing fragment during comparison will make the key invalid, i.e., the computed key doesn't match any key. */ 'fragments': (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRoutes_ScopeKeyBuilder_FragmentBuilder__Output)[]; @@ -220,7 +220,7 @@ export interface ScopedRoutes { * 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` + * :ref:`ScopeKeyBuilder` * in this message. */ 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList | null); @@ -228,7 +228,7 @@ export interface ScopedRoutes { * 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` + * :ref:`ScopeKeyBuilder` * in this message. */ 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds | null); @@ -257,7 +257,7 @@ export interface ScopedRoutes__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` + * :ref:`ScopeKeyBuilder` * in this message. */ 'scoped_route_configurations_list'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRouteConfigurationsList__Output | null); @@ -265,7 +265,7 @@ export interface ScopedRoutes__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` + * :ref:`ScopeKeyBuilder` * in this message. */ 'scoped_rds'?: (_envoy_extensions_filters_network_http_connection_manager_v3_ScopedRds__Output | null); diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts new file mode 100644 index 000000000..3a3100f55 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts @@ -0,0 +1,52 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + + +/** + * Indicates a certificate to be obtained from a named CertificateProvider plugin instance. + * The plugin instances are defined in the client's bootstrap file. + * The plugin allows certificates to be fetched/refreshed over the network asynchronously with + * respect to the TLS handshake. + * [#not-implemented-hide:] + */ +export interface CertificateProviderPluginInstance { + /** + * Provider instance name. If not present, defaults to "default". + * + * Instance names should generally be defined not in terms of the underlying provider + * implementation (e.g., "file_watcher") but rather in terms of the function of the + * certificates (e.g., "foo_deployment_identity"). + */ + 'instance_name'?: (string); + /** + * Opaque name used to specify certificate instances or types. For example, "ROOTCA" to specify + * a root-certificate (validation context) or "example.com" to specify a certificate for a + * particular domain. Not all provider instances will actually use this field, so the value + * defaults to the empty string. + */ + 'certificate_name'?: (string); +} + +/** + * Indicates a certificate to be obtained from a named CertificateProvider plugin instance. + * The plugin instances are defined in the client's bootstrap file. + * The plugin allows certificates to be fetched/refreshed over the network asynchronously with + * respect to the TLS handshake. + * [#not-implemented-hide:] + */ +export interface CertificateProviderPluginInstance__Output { + /** + * Provider instance name. If not present, defaults to "default". + * + * Instance names should generally be defined not in terms of the underlying provider + * implementation (e.g., "file_watcher") but rather in terms of the function of the + * certificates (e.g., "foo_deployment_identity"). + */ + 'instance_name': (string); + /** + * Opaque name used to specify certificate instances or types. For example, "ROOTCA" to specify + * a root-certificate (validation context) or "example.com" to specify a certificate for a + * particular domain. Not all provider instances will actually use this field, so the value + * defaults to the empty string. + */ + 'certificate_name': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts new file mode 100644 index 000000000..379320086 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts @@ -0,0 +1,372 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; +import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../../google/protobuf/BoolValue'; +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../../envoy/type/matcher/v3/StringMatcher'; +import type { WatchedDirectory as _envoy_config_core_v3_WatchedDirectory, WatchedDirectory__Output as _envoy_config_core_v3_WatchedDirectory__Output } from '../../../../../envoy/config/core/v3/WatchedDirectory'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { CertificateProviderPluginInstance as _envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance, CertificateProviderPluginInstance__Output as _envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance'; + +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +/** + * Peer certificate verification mode. + */ +export enum _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification { + /** + * Perform default certificate verification (e.g., against CA / verification lists) + */ + VERIFY_TRUST_CHAIN = 0, + /** + * Connections where the certificate fails verification will be permitted. + * For HTTP connections, the result of certificate verification can be used in route matching. ( + * see :ref:`validated ` ). + */ + ACCEPT_UNTRUSTED = 1, +} + +/** + * [#next-free-field: 14] + */ +export interface CertificateValidationContext { + /** + * TLS certificate data containing certificate authority certificates to use in verifying + * a presented peer certificate (e.g. server certificate for clusters or client certificate + * for listeners). If not specified and a peer certificate is presented it will not be + * verified. By default, a client certificate is optional, unless one of the additional + * options (:ref:`require_client_certificate + * `, + * :ref:`verify_certificate_spki + * `, + * :ref:`verify_certificate_hash + * `, or + * :ref:`match_subject_alt_names + * `) is also + * specified. + * + * It can optionally contain certificate revocation lists, in which case Envoy will verify + * that the presented peer certificate has not been revoked by one of the included CRLs. Note + * that if a CRL is provided for any certificate authority in a trust chain, a CRL must be + * provided for all certificate authorities in that chain. Failure to do so will result in + * verification failure for both revoked and unrevoked certificates from that chain. + * + * See :ref:`the TLS overview ` for a list of common + * system CA locations. + * + * If *trusted_ca* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *CertificateValidationContext* is + * delivered via SDS. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * + * [#next-major-version: This field and watched_directory below should ideally be moved into a + * separate sub-message, since there's no point in specifying the latter field without this one.] + */ + 'trusted_ca'?: (_envoy_config_core_v3_DataSource | null); + /** + * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that + * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. + * + * A hex-encoded SHA-256 of the certificate can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 + * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a + * + * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 + * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A + * + * Both of those formats are acceptable. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + */ + 'verify_certificate_hash'?: (string)[]; + /** + * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the + * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate + * matches one of the specified values. + * + * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -pubkey + * | openssl pkey -pubin -outform DER + * | openssl dgst -sha256 -binary + * | openssl enc -base64 + * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= + * + * This is the format used in HTTP Public Key Pinning. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + * + * .. attention:: + * + * This option is preferred over :ref:`verify_certificate_hash + * `, + * because SPKI is tied to a private key, so it doesn't change when the certificate + * is renewed using the same private key. + */ + 'verify_certificate_spki'?: (string)[]; + /** + * [#not-implemented-hide:] Must present signed certificate time-stamp. + */ + 'require_signed_certificate_timestamp'?: (_google_protobuf_BoolValue | null); + /** + * An optional `certificate revocation list + * `_ + * (in PEM format). If specified, Envoy will verify that the presented peer + * certificate has not been revoked by this CRL. If this DataSource contains + * multiple CRLs, all of them will be used. Note that if a CRL is provided + * for any certificate authority in a trust chain, a CRL must be provided + * for all certificate authorities in that chain. Failure to do so will + * result in verification failure for both revoked and unrevoked certificates + * from that chain. + */ + 'crl'?: (_envoy_config_core_v3_DataSource | null); + /** + * If specified, Envoy will not reject expired certificates. + */ + 'allow_expired_certificate'?: (boolean); + /** + * An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the + * Subject Alternative Name of the presented certificate matches one of the specified matchers. + * + * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be + * configured with exact match type in the :ref:`string matcher `. + * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", + * it should be configured as shown below. + * + * .. code-block:: yaml + * + * match_subject_alt_names: + * exact: "api.example.com" + * + * .. attention:: + * + * Subject Alternative Names are easily spoofable and verifying only them is insecure, + * therefore this option must be used together with :ref:`trusted_ca + * `. + */ + 'match_subject_alt_names'?: (_envoy_type_matcher_v3_StringMatcher)[]; + /** + * Certificate trust chain verification mode. + */ + 'trust_chain_verification'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification | keyof typeof _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification); + /** + * If specified, updates of a file-based *trusted_ca* source will be triggered + * by this watch. This allows explicit control over the path watched, by + * default the parent directory of the filesystem path in *trusted_ca* is + * watched if this field is not specified. This only applies when a + * *CertificateValidationContext* is delivered by SDS with references to + * filesystem paths. See the :ref:`SDS key rotation ` + * documentation for further details. + */ + 'watched_directory'?: (_envoy_config_core_v3_WatchedDirectory | null); + /** + * The configuration of an extension specific certificate validator. + * If specified, all validation is done by the specified validator, + * and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + * Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + * [#extension-category: envoy.tls.cert_validator] + */ + 'custom_validator_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Certificate provider instance for fetching TLS certificates. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * [#not-implemented-hide:] + */ + 'ca_certificate_provider_instance'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance | null); +} + +/** + * [#next-free-field: 14] + */ +export interface CertificateValidationContext__Output { + /** + * TLS certificate data containing certificate authority certificates to use in verifying + * a presented peer certificate (e.g. server certificate for clusters or client certificate + * for listeners). If not specified and a peer certificate is presented it will not be + * verified. By default, a client certificate is optional, unless one of the additional + * options (:ref:`require_client_certificate + * `, + * :ref:`verify_certificate_spki + * `, + * :ref:`verify_certificate_hash + * `, or + * :ref:`match_subject_alt_names + * `) is also + * specified. + * + * It can optionally contain certificate revocation lists, in which case Envoy will verify + * that the presented peer certificate has not been revoked by one of the included CRLs. Note + * that if a CRL is provided for any certificate authority in a trust chain, a CRL must be + * provided for all certificate authorities in that chain. Failure to do so will result in + * verification failure for both revoked and unrevoked certificates from that chain. + * + * See :ref:`the TLS overview ` for a list of common + * system CA locations. + * + * If *trusted_ca* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *CertificateValidationContext* is + * delivered via SDS. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * + * [#next-major-version: This field and watched_directory below should ideally be moved into a + * separate sub-message, since there's no point in specifying the latter field without this one.] + */ + 'trusted_ca': (_envoy_config_core_v3_DataSource__Output | null); + /** + * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that + * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. + * + * A hex-encoded SHA-256 of the certificate can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 + * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a + * + * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 + * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A + * + * Both of those formats are acceptable. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + */ + 'verify_certificate_hash': (string)[]; + /** + * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the + * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate + * matches one of the specified values. + * + * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate + * can be generated with the following command: + * + * .. code-block:: bash + * + * $ openssl x509 -in path/to/client.crt -noout -pubkey + * | openssl pkey -pubin -outform DER + * | openssl dgst -sha256 -binary + * | openssl enc -base64 + * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= + * + * This is the format used in HTTP Public Key Pinning. + * + * When both: + * :ref:`verify_certificate_hash + * ` and + * :ref:`verify_certificate_spki + * ` are specified, + * a hash matching value from either of the lists will result in the certificate being accepted. + * + * .. attention:: + * + * This option is preferred over :ref:`verify_certificate_hash + * `, + * because SPKI is tied to a private key, so it doesn't change when the certificate + * is renewed using the same private key. + */ + 'verify_certificate_spki': (string)[]; + /** + * [#not-implemented-hide:] Must present signed certificate time-stamp. + */ + 'require_signed_certificate_timestamp': (_google_protobuf_BoolValue__Output | null); + /** + * An optional `certificate revocation list + * `_ + * (in PEM format). If specified, Envoy will verify that the presented peer + * certificate has not been revoked by this CRL. If this DataSource contains + * multiple CRLs, all of them will be used. Note that if a CRL is provided + * for any certificate authority in a trust chain, a CRL must be provided + * for all certificate authorities in that chain. Failure to do so will + * result in verification failure for both revoked and unrevoked certificates + * from that chain. + */ + 'crl': (_envoy_config_core_v3_DataSource__Output | null); + /** + * If specified, Envoy will not reject expired certificates. + */ + 'allow_expired_certificate': (boolean); + /** + * An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the + * Subject Alternative Name of the presented certificate matches one of the specified matchers. + * + * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be + * configured with exact match type in the :ref:`string matcher `. + * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", + * it should be configured as shown below. + * + * .. code-block:: yaml + * + * match_subject_alt_names: + * exact: "api.example.com" + * + * .. attention:: + * + * Subject Alternative Names are easily spoofable and verifying only them is insecure, + * therefore this option must be used together with :ref:`trusted_ca + * `. + */ + 'match_subject_alt_names': (_envoy_type_matcher_v3_StringMatcher__Output)[]; + /** + * Certificate trust chain verification mode. + */ + 'trust_chain_verification': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification); + /** + * If specified, updates of a file-based *trusted_ca* source will be triggered + * by this watch. This allows explicit control over the path watched, by + * default the parent directory of the filesystem path in *trusted_ca* is + * watched if this field is not specified. This only applies when a + * *CertificateValidationContext* is delivered by SDS with references to + * filesystem paths. See the :ref:`SDS key rotation ` + * documentation for further details. + */ + 'watched_directory': (_envoy_config_core_v3_WatchedDirectory__Output | null); + /** + * The configuration of an extension specific certificate validator. + * If specified, all validation is done by the specified validator, + * and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). + * Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. + * [#extension-category: envoy.tls.cert_validator] + */ + 'custom_validator_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Certificate provider instance for fetching TLS certificates. + * + * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. + * [#not-implemented-hide:] + */ + 'ca_certificate_provider_instance': (_envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts new file mode 100644 index 000000000..b206fb13a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; + +export interface GenericSecret { + /** + * Secret of generic type and is available to filters. + */ + 'secret'?: (_envoy_config_core_v3_DataSource | null); +} + +export interface GenericSecret__Output { + /** + * Secret of generic type and is available to filters. + */ + 'secret': (_envoy_config_core_v3_DataSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts new file mode 100644 index 000000000..b4a2ad933 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts @@ -0,0 +1,39 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../google/protobuf/Any'; + +/** + * BoringSSL private key method configuration. The private key methods are used for external + * (potentially asynchronous) signing and decryption operations. Some use cases for private key + * methods would be TPM support and TLS acceleration. + */ +export interface PrivateKeyProvider { + /** + * Private key method provider name. The name must match a + * supported private key method provider type. + */ + 'provider_name'?: (string); + 'typed_config'?: (_google_protobuf_Any | null); + /** + * Private key method provider specific configuration. + */ + 'config_type'?: "typed_config"; +} + +/** + * BoringSSL private key method configuration. The private key methods are used for external + * (potentially asynchronous) signing and decryption operations. Some use cases for private key + * methods would be TPM support and TLS acceleration. + */ +export interface PrivateKeyProvider__Output { + /** + * Private key method provider name. The name must match a + * supported private key method provider type. + */ + 'provider_name': (string); + 'typed_config'?: (_google_protobuf_Any__Output | null); + /** + * Private key method provider specific configuration. + */ + 'config_type': "typed_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts new file mode 100644 index 000000000..38b850c50 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts @@ -0,0 +1,23 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto + +import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../envoy/config/core/v3/ConfigSource'; + +export interface SdsSecretConfig { + /** + * Name by which the secret can be uniquely referred to. When both name and config are specified, + * then secret can be fetched and/or reloaded via SDS. When only name is specified, then secret + * will be loaded from static resources. + */ + 'name'?: (string); + 'sds_config'?: (_envoy_config_core_v3_ConfigSource | null); +} + +export interface SdsSecretConfig__Output { + /** + * Name by which the secret can be uniquely referred to. When both name and config are specified, + * then secret can be fetched and/or reloaded via SDS. When only name is specified, then secret + * will be loaded from static resources. + */ + 'name': (string); + 'sds_config': (_envoy_config_core_v3_ConfigSource__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts new file mode 100644 index 000000000..c86957da5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts @@ -0,0 +1,36 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto + +import type { TlsCertificate as _envoy_extensions_transport_sockets_tls_v3_TlsCertificate, TlsCertificate__Output as _envoy_extensions_transport_sockets_tls_v3_TlsCertificate__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/TlsCertificate'; +import type { TlsSessionTicketKeys as _envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys, TlsSessionTicketKeys__Output as _envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys'; +import type { CertificateValidationContext as _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext, CertificateValidationContext__Output as _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext'; +import type { GenericSecret as _envoy_extensions_transport_sockets_tls_v3_GenericSecret, GenericSecret__Output as _envoy_extensions_transport_sockets_tls_v3_GenericSecret__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/GenericSecret'; + +/** + * [#next-free-field: 6] + */ +export interface Secret { + /** + * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. + */ + 'name'?: (string); + 'tls_certificate'?: (_envoy_extensions_transport_sockets_tls_v3_TlsCertificate | null); + 'session_ticket_keys'?: (_envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys | null); + 'validation_context'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext | null); + 'generic_secret'?: (_envoy_extensions_transport_sockets_tls_v3_GenericSecret | null); + 'type'?: "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; +} + +/** + * [#next-free-field: 6] + */ +export interface Secret__Output { + /** + * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. + */ + 'name': (string); + 'tls_certificate'?: (_envoy_extensions_transport_sockets_tls_v3_TlsCertificate__Output | null); + 'session_ticket_keys'?: (_envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys__Output | null); + 'validation_context'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext__Output | null); + 'generic_secret'?: (_envoy_extensions_transport_sockets_tls_v3_GenericSecret__Output | null); + 'type': "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts new file mode 100644 index 000000000..ce8046e95 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts @@ -0,0 +1,127 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; +import type { PrivateKeyProvider as _envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider, PrivateKeyProvider__Output as _envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider'; +import type { WatchedDirectory as _envoy_config_core_v3_WatchedDirectory, WatchedDirectory__Output as _envoy_config_core_v3_WatchedDirectory__Output } from '../../../../../envoy/config/core/v3/WatchedDirectory'; + +/** + * [#next-free-field: 8] + */ +export interface TlsCertificate { + /** + * The TLS certificate chain. + * + * If *certificate_chain* is a filesystem path, a watch will be added to the + * parent directory for any file moves to support rotation. This currently + * only applies to dynamic secrets, when the *TlsCertificate* is delivered via + * SDS. + */ + 'certificate_chain'?: (_envoy_config_core_v3_DataSource | null); + /** + * The TLS private key. + * + * If *private_key* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. + */ + 'private_key'?: (_envoy_config_core_v3_DataSource | null); + /** + * The password to decrypt the TLS private key. If this field is not set, it is assumed that the + * TLS private key is not password encrypted. + */ + 'password'?: (_envoy_config_core_v3_DataSource | null); + /** + * The OCSP response to be stapled with this certificate during the handshake. + * The response must be DER-encoded and may only be provided via ``filename`` or + * ``inline_bytes``. The response may pertain to only one certificate. + */ + 'ocsp_staple'?: (_envoy_config_core_v3_DataSource | null); + /** + * [#not-implemented-hide:] + */ + 'signed_certificate_timestamp'?: (_envoy_config_core_v3_DataSource)[]; + /** + * BoringSSL private key method provider. This is an alternative to :ref:`private_key + * ` field. This can't be + * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key + * ` and + * :ref:`private_key_provider + * ` fields will result in an + * error. + */ + 'private_key_provider'?: (_envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider | null); + /** + * If specified, updates of file-based *certificate_chain* and *private_key* + * sources will be triggered by this watch. The certificate/key pair will be + * read together and validated for atomic read consistency (i.e. no + * intervening modification occurred between cert/key read, verified by file + * hash comparisons). This allows explicit control over the path watched, by + * default the parent directories of the filesystem paths in + * *certificate_chain* and *private_key* are watched if this field is not + * specified. This only applies when a *TlsCertificate* is delivered by SDS + * with references to filesystem paths. See the :ref:`SDS key rotation + * ` documentation for further details. + */ + 'watched_directory'?: (_envoy_config_core_v3_WatchedDirectory | null); +} + +/** + * [#next-free-field: 8] + */ +export interface TlsCertificate__Output { + /** + * The TLS certificate chain. + * + * If *certificate_chain* is a filesystem path, a watch will be added to the + * parent directory for any file moves to support rotation. This currently + * only applies to dynamic secrets, when the *TlsCertificate* is delivered via + * SDS. + */ + 'certificate_chain': (_envoy_config_core_v3_DataSource__Output | null); + /** + * The TLS private key. + * + * If *private_key* is a filesystem path, a watch will be added to the parent + * directory for any file moves to support rotation. This currently only + * applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. + */ + 'private_key': (_envoy_config_core_v3_DataSource__Output | null); + /** + * The password to decrypt the TLS private key. If this field is not set, it is assumed that the + * TLS private key is not password encrypted. + */ + 'password': (_envoy_config_core_v3_DataSource__Output | null); + /** + * The OCSP response to be stapled with this certificate during the handshake. + * The response must be DER-encoded and may only be provided via ``filename`` or + * ``inline_bytes``. The response may pertain to only one certificate. + */ + 'ocsp_staple': (_envoy_config_core_v3_DataSource__Output | null); + /** + * [#not-implemented-hide:] + */ + 'signed_certificate_timestamp': (_envoy_config_core_v3_DataSource__Output)[]; + /** + * BoringSSL private key method provider. This is an alternative to :ref:`private_key + * ` field. This can't be + * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key + * ` and + * :ref:`private_key_provider + * ` fields will result in an + * error. + */ + 'private_key_provider': (_envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider__Output | null); + /** + * If specified, updates of file-based *certificate_chain* and *private_key* + * sources will be triggered by this watch. The certificate/key pair will be + * read together and validated for atomic read consistency (i.e. no + * intervening modification occurred between cert/key read, verified by file + * hash comparisons). This allows explicit control over the path watched, by + * default the parent directories of the filesystem paths in + * *certificate_chain* and *private_key* are watched if this field is not + * specified. This only applies when a *TlsCertificate* is delivered by SDS + * with references to filesystem paths. See the :ref:`SDS key rotation + * ` documentation for further details. + */ + 'watched_directory': (_envoy_config_core_v3_WatchedDirectory__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts new file mode 100644 index 000000000..e68464c8b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts @@ -0,0 +1,211 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + + +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +export enum _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol { + /** + * Envoy will choose the optimal TLS version. + */ + TLS_AUTO = 0, + /** + * TLS 1.0 + */ + TLSv1_0 = 1, + /** + * TLS 1.1 + */ + TLSv1_1 = 2, + /** + * TLS 1.2 + */ + TLSv1_2 = 3, + /** + * TLS 1.3 + */ + TLSv1_3 = 4, +} + +export interface TlsParameters { + /** + * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for + * servers. + */ + 'tls_minimum_protocol_version'?: (_envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol | keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for + * servers. + */ + 'tls_maximum_protocol_version'?: (_envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol | keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * If specified, the TLS listener will only support the specified `cipher list + * `_ + * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). + * + * If not specified, a default list will be used. Defaults are different for server (downstream) and + * client (upstream) TLS configurations. + * + * In non-FIPS builds, the default server cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In builds using :ref:`BoringSSL FIPS `, the default server cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In non-FIPS builds, the default client cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * + * In builds using :ref:`BoringSSL FIPS `, the default client cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + */ + 'cipher_suites'?: (string)[]; + /** + * If specified, the TLS connection will only support the specified ECDH + * curves. If not specified, the default curves will be used. + * + * In non-FIPS builds, the default curves are: + * + * .. code-block:: none + * + * X25519 + * P-256 + * + * In builds using :ref:`BoringSSL FIPS `, the default curve is: + * + * .. code-block:: none + * + * P-256 + */ + 'ecdh_curves'?: (string)[]; +} + +export interface TlsParameters__Output { + /** + * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for + * servers. + */ + 'tls_minimum_protocol_version': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for + * servers. + */ + 'tls_maximum_protocol_version': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); + /** + * If specified, the TLS listener will only support the specified `cipher list + * `_ + * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). + * + * If not specified, a default list will be used. Defaults are different for server (downstream) and + * client (upstream) TLS configurations. + * + * In non-FIPS builds, the default server cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In builds using :ref:`BoringSSL FIPS `, the default server cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES128-SHA + * ECDHE-RSA-AES128-SHA + * AES128-GCM-SHA256 + * AES128-SHA + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * ECDHE-ECDSA-AES256-SHA + * ECDHE-RSA-AES256-SHA + * AES256-GCM-SHA384 + * AES256-SHA + * + * In non-FIPS builds, the default client cipher list is: + * + * .. code-block:: none + * + * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + * + * In builds using :ref:`BoringSSL FIPS `, the default client cipher list is: + * + * .. code-block:: none + * + * ECDHE-ECDSA-AES128-GCM-SHA256 + * ECDHE-RSA-AES128-GCM-SHA256 + * ECDHE-ECDSA-AES256-GCM-SHA384 + * ECDHE-RSA-AES256-GCM-SHA384 + */ + 'cipher_suites': (string)[]; + /** + * If specified, the TLS connection will only support the specified ECDH + * curves. If not specified, the default curves will be used. + * + * In non-FIPS builds, the default curves are: + * + * .. code-block:: none + * + * X25519 + * P-256 + * + * In builds using :ref:`BoringSSL FIPS `, the default curve is: + * + * .. code-block:: none + * + * P-256 + */ + 'ecdh_curves': (string)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts new file mode 100644 index 000000000..152bccac7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts @@ -0,0 +1,61 @@ +// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto + +import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; + +export interface TlsSessionTicketKeys { + /** + * Keys for encrypting and decrypting TLS session tickets. The + * first key in the array contains the key to encrypt all new sessions created by this context. + * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys + * by, for example, putting the new key first, and the previous key second. + * + * If :ref:`session_ticket_keys ` + * is not specified, the TLS library will still support resuming sessions via tickets, but it will + * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts + * or on different hosts. + * + * Each key must contain exactly 80 bytes of cryptographically-secure random data. For + * example, the output of ``openssl rand 80``. + * + * .. attention:: + * + * Using this feature has serious security considerations and risks. Improper handling of keys + * may result in loss of secrecy in connections, even if ciphers supporting perfect forward + * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some + * discussion. To minimize the risk, you must: + * + * * Keep the session ticket keys at least as secure as your TLS certificate private keys + * * Rotate session ticket keys at least daily, and preferably hourly + * * Always generate keys using a cryptographically-secure random data source + */ + 'keys'?: (_envoy_config_core_v3_DataSource)[]; +} + +export interface TlsSessionTicketKeys__Output { + /** + * Keys for encrypting and decrypting TLS session tickets. The + * first key in the array contains the key to encrypt all new sessions created by this context. + * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys + * by, for example, putting the new key first, and the previous key second. + * + * If :ref:`session_ticket_keys ` + * is not specified, the TLS library will still support resuming sessions via tickets, but it will + * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts + * or on different hosts. + * + * Each key must contain exactly 80 bytes of cryptographically-secure random data. For + * example, the output of ``openssl rand 80``. + * + * .. attention:: + * + * Using this feature has serious security considerations and risks. Improper handling of keys + * may result in loss of secrecy in connections, even if ciphers supporting perfect forward + * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some + * discussion. To minimize the risk, you must: + * + * * Keep the session ticket keys at least as secure as your TLS certificate private keys + * * Rotate session ticket keys at least daily, and preferably hourly + * * Always generate keys using a cryptographically-secure random data source + */ + 'keys': (_envoy_config_core_v3_DataSource__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts index b42470516..6e900970a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts @@ -97,7 +97,7 @@ export interface DeltaDiscoveryRequest { */ 'response_nonce'?: (string); /** - * This is populated when the previous :ref:`DiscoveryResponse ` + * This is populated when the previous :ref:`DiscoveryResponse ` * failed to update configuration. The *message* field in *error_details* * provides the Envoy internal exception related to the failure. */ @@ -198,7 +198,7 @@ export interface DeltaDiscoveryRequest__Output { */ 'response_nonce': (string); /** - * This is populated when the previous :ref:`DiscoveryResponse ` + * This is populated when the previous :ref:`DiscoveryResponse ` * failed to update configuration. The *message* field in *error_details* * provides the Envoy internal exception related to the failure. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts index b30930b6f..f392ab8ae 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts @@ -48,7 +48,7 @@ export interface DiscoveryRequest { */ 'response_nonce'?: (string); /** - * This is populated when the previous :ref:`DiscoveryResponse ` + * This is populated when the previous :ref:`DiscoveryResponse ` * failed to update configuration. The *message* field in *error_details* provides the Envoy * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. @@ -101,7 +101,7 @@ export interface DiscoveryRequest__Output { */ 'response_nonce': (string); /** - * This is populated when the previous :ref:`DiscoveryResponse ` + * This is populated when the previous :ref:`DiscoveryResponse ` * failed to update configuration. The *message* field in *error_details* provides the Envoy * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts index c39658cde..40f561870 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts @@ -31,7 +31,7 @@ export interface LoadStatsResponse { /** * If true, the client should send all clusters it knows about. * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - * :ref:`client_features` field will honor this field. + * :ref:`client_features` field will honor this field. */ 'send_all_clusters'?: (boolean); } @@ -65,7 +65,7 @@ export interface LoadStatsResponse__Output { /** * If true, the client should send all clusters it knows about. * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - * :ref:`client_features` field will honor this field. + * :ref:`client_features` field will honor this field. */ 'send_all_clusters': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts new file mode 100644 index 000000000..ba6b25b4c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts @@ -0,0 +1,159 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; +import type { PerXdsConfig as _envoy_service_status_v3_PerXdsConfig, PerXdsConfig__Output as _envoy_service_status_v3_PerXdsConfig__Output } from '../../../../envoy/service/status/v3/PerXdsConfig'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../../google/protobuf/Timestamp'; +import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus } from '../../../../envoy/service/status/v3/ConfigStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../../envoy/admin/v3/ClientResourceStatus'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../../envoy/admin/v3/UpdateFailureState'; + +/** + * GenericXdsConfig is used to specify the config status and the dump + * of any xDS resource identified by their type URL. It is the generalized + * version of the now deprecated ListenersConfigDump, ClustersConfigDump etc + * [#next-free-field: 10] + */ +export interface _envoy_service_status_v3_ClientConfig_GenericXdsConfig { + /** + * Type_url represents the fully qualified name of xDS resource type + * like envoy.v3.Cluster, envoy.v3.ClusterLoadAssignment etc. + */ + 'type_url'?: (string); + /** + * Name of the xDS resource + */ + 'name'?: (string); + /** + * This is the :ref:`version_info ` + * in the last processed xDS discovery response. If there are only + * static bootstrap listeners, this field will be "" + */ + 'version_info'?: (string); + /** + * The xDS resource config. Actual content depends on the type + */ + 'xds_config'?: (_google_protobuf_Any | null); + /** + * Timestamp when the xDS resource was last updated + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Per xDS resource config status. It is generated by management servers. + * It will not be present if the CSDS server is an xDS client. + */ + 'config_status'?: (_envoy_service_status_v3_ConfigStatus | keyof typeof _envoy_service_status_v3_ConfigStatus); + /** + * Per xDS resource status from the view of a xDS client + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + /** + * Set if the last update failed, cleared after the next successful + * update. The *error_state* field contains the rejected version of + * this particular resource along with the reason and timestamp. For + * successfully updated or acknowledged resource, this field should + * be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * Is static resource is true if it is specified in the config supplied + * through the file at the startup. + */ + 'is_static_resource'?: (boolean); +} + +/** + * GenericXdsConfig is used to specify the config status and the dump + * of any xDS resource identified by their type URL. It is the generalized + * version of the now deprecated ListenersConfigDump, ClustersConfigDump etc + * [#next-free-field: 10] + */ +export interface _envoy_service_status_v3_ClientConfig_GenericXdsConfig__Output { + /** + * Type_url represents the fully qualified name of xDS resource type + * like envoy.v3.Cluster, envoy.v3.ClusterLoadAssignment etc. + */ + 'type_url': (string); + /** + * Name of the xDS resource + */ + 'name': (string); + /** + * This is the :ref:`version_info ` + * in the last processed xDS discovery response. If there are only + * static bootstrap listeners, this field will be "" + */ + 'version_info': (string); + /** + * The xDS resource config. Actual content depends on the type + */ + 'xds_config': (_google_protobuf_Any__Output | null); + /** + * Timestamp when the xDS resource was last updated + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Per xDS resource config status. It is generated by management servers. + * It will not be present if the CSDS server is an xDS client. + */ + 'config_status': (keyof typeof _envoy_service_status_v3_ConfigStatus); + /** + * Per xDS resource status from the view of a xDS client + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + /** + * Set if the last update failed, cleared after the next successful + * update. The *error_state* field contains the rejected version of + * this particular resource along with the reason and timestamp. For + * successfully updated or acknowledged resource, this field should + * be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * Is static resource is true if it is specified in the config supplied + * through the file at the startup. + */ + 'is_static_resource': (boolean); +} + +/** + * All xds configs for a particular client. + */ +export interface ClientConfig { + /** + * Node for a particular client. + */ + 'node'?: (_envoy_config_core_v3_Node | null); + /** + * This field is deprecated in favor of generic_xds_configs which is + * much simpler and uniform in structure. + */ + 'xds_config'?: (_envoy_service_status_v3_PerXdsConfig)[]; + /** + * Represents generic xDS config and the exact config structure depends on + * the type URL (like Cluster if it is CDS) + */ + 'generic_xds_configs'?: (_envoy_service_status_v3_ClientConfig_GenericXdsConfig)[]; +} + +/** + * All xds configs for a particular client. + */ +export interface ClientConfig__Output { + /** + * Node for a particular client. + */ + 'node': (_envoy_config_core_v3_Node__Output | null); + /** + * This field is deprecated in favor of generic_xds_configs which is + * much simpler and uniform in structure. + */ + 'xds_config': (_envoy_service_status_v3_PerXdsConfig__Output)[]; + /** + * Represents generic xDS config and the exact config structure depends on + * the type URL (like Cluster if it is CDS) + */ + 'generic_xds_configs': (_envoy_service_status_v3_ClientConfig_GenericXdsConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts new file mode 100644 index 000000000..104445a3f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts @@ -0,0 +1,26 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +/** + * Config status from a client-side view. + */ +export enum ClientConfigStatus { + /** + * Config status is not available/unknown. + */ + CLIENT_UNKNOWN = 0, + /** + * Client requested the config but hasn't received any config from management + * server yet. + */ + CLIENT_REQUESTED = 1, + /** + * Client received the config and replied with ACK. + */ + CLIENT_ACKED = 2, + /** + * Client received the config and replied with NACK. Notably, the attached + * config dump is not the NACKed version, but the most recent accepted one. If + * no config is accepted yet, the attached config dump will be empty. + */ + CLIENT_NACKED = 3, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusDiscoveryService.ts new file mode 100644 index 000000000..7402fb69b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusDiscoveryService.ts @@ -0,0 +1,45 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { ClientStatusRequest as _envoy_service_status_v3_ClientStatusRequest, ClientStatusRequest__Output as _envoy_service_status_v3_ClientStatusRequest__Output } from '../../../../envoy/service/status/v3/ClientStatusRequest'; +import type { ClientStatusResponse as _envoy_service_status_v3_ClientStatusResponse, ClientStatusResponse__Output as _envoy_service_status_v3_ClientStatusResponse__Output } from '../../../../envoy/service/status/v3/ClientStatusResponse'; + +/** + * CSDS is Client Status Discovery Service. It can be used to get the status of + * an xDS-compliant client from the management server's point of view. It can + * also be used to get the current xDS states directly from the client. + */ +export interface ClientStatusDiscoveryServiceClient extends grpc.Client { + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + FetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + fetchClientStatus(argument: _envoy_service_status_v3_ClientStatusRequest, callback: grpc.requestCallback<_envoy_service_status_v3_ClientStatusResponse__Output>): grpc.ClientUnaryCall; + + StreamClientStatus(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + StreamClientStatus(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + streamClientStatus(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + streamClientStatus(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse__Output>; + +} + +/** + * CSDS is Client Status Discovery Service. It can be used to get the status of + * an xDS-compliant client from the management server's point of view. It can + * also be used to get the current xDS states directly from the client. + */ +export interface ClientStatusDiscoveryServiceHandlers extends grpc.UntypedServiceImplementation { + FetchClientStatus: grpc.handleUnaryCall<_envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse>; + + StreamClientStatus: grpc.handleBidiStreamingCall<_envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse>; + +} + +export interface ClientStatusDiscoveryServiceDefinition extends grpc.ServiceDefinition { + FetchClientStatus: MethodDefinition<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse, _envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse__Output> + StreamClientStatus: MethodDefinition<_envoy_service_status_v3_ClientStatusRequest, _envoy_service_status_v3_ClientStatusResponse, _envoy_service_status_v3_ClientStatusRequest__Output, _envoy_service_status_v3_ClientStatusResponse__Output> +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusRequest.ts new file mode 100644 index 000000000..91adddacc --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusRequest.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { NodeMatcher as _envoy_type_matcher_v3_NodeMatcher, NodeMatcher__Output as _envoy_type_matcher_v3_NodeMatcher__Output } from '../../../../envoy/type/matcher/v3/NodeMatcher'; +import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; + +/** + * Request for client status of clients identified by a list of NodeMatchers. + */ +export interface ClientStatusRequest { + /** + * Management server can use these match criteria to identify clients. + * The match follows OR semantics. + */ + 'node_matchers'?: (_envoy_type_matcher_v3_NodeMatcher)[]; + /** + * The node making the csds request. + */ + 'node'?: (_envoy_config_core_v3_Node | null); +} + +/** + * Request for client status of clients identified by a list of NodeMatchers. + */ +export interface ClientStatusRequest__Output { + /** + * Management server can use these match criteria to identify clients. + * The match follows OR semantics. + */ + 'node_matchers': (_envoy_type_matcher_v3_NodeMatcher__Output)[]; + /** + * The node making the csds request. + */ + 'node': (_envoy_config_core_v3_Node__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusResponse.ts new file mode 100644 index 000000000..3611016ec --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientStatusResponse.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { ClientConfig as _envoy_service_status_v3_ClientConfig, ClientConfig__Output as _envoy_service_status_v3_ClientConfig__Output } from '../../../../envoy/service/status/v3/ClientConfig'; + +export interface ClientStatusResponse { + /** + * Client configs for the clients specified in the ClientStatusRequest. + */ + 'config'?: (_envoy_service_status_v3_ClientConfig)[]; +} + +export interface ClientStatusResponse__Output { + /** + * Client configs for the clients specified in the ClientStatusRequest. + */ + 'config': (_envoy_service_status_v3_ClientConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts new file mode 100644 index 000000000..71db302c3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts @@ -0,0 +1,30 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +/** + * Status of a config from a management server view. + */ +export enum ConfigStatus { + /** + * Status info is not available/unknown. + */ + UNKNOWN = 0, + /** + * Management server has sent the config to client and received ACK. + */ + SYNCED = 1, + /** + * Config is not sent. + */ + NOT_SENT = 2, + /** + * Management server has sent the config to client but hasn’t received + * ACK/NACK. + */ + STALE = 3, + /** + * Management server has sent the config to client but received NACK. The + * attached config dump will be the latest config (the rejected one), since + * it is the persisted version in the management server. + */ + ERROR = 4, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts new file mode 100644 index 000000000..947f1c81a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/service/status/v3/csds.proto + +import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus } from '../../../../envoy/service/status/v3/ConfigStatus'; +import type { ListenersConfigDump as _envoy_admin_v3_ListenersConfigDump, ListenersConfigDump__Output as _envoy_admin_v3_ListenersConfigDump__Output } from '../../../../envoy/admin/v3/ListenersConfigDump'; +import type { ClustersConfigDump as _envoy_admin_v3_ClustersConfigDump, ClustersConfigDump__Output as _envoy_admin_v3_ClustersConfigDump__Output } from '../../../../envoy/admin/v3/ClustersConfigDump'; +import type { RoutesConfigDump as _envoy_admin_v3_RoutesConfigDump, RoutesConfigDump__Output as _envoy_admin_v3_RoutesConfigDump__Output } from '../../../../envoy/admin/v3/RoutesConfigDump'; +import type { ScopedRoutesConfigDump as _envoy_admin_v3_ScopedRoutesConfigDump, ScopedRoutesConfigDump__Output as _envoy_admin_v3_ScopedRoutesConfigDump__Output } from '../../../../envoy/admin/v3/ScopedRoutesConfigDump'; +import type { EndpointsConfigDump as _envoy_admin_v3_EndpointsConfigDump, EndpointsConfigDump__Output as _envoy_admin_v3_EndpointsConfigDump__Output } from '../../../../envoy/admin/v3/EndpointsConfigDump'; +import type { ClientConfigStatus as _envoy_service_status_v3_ClientConfigStatus } from '../../../../envoy/service/status/v3/ClientConfigStatus'; + +/** + * Detailed config (per xDS) with status. + * [#next-free-field: 8] + */ +export interface PerXdsConfig { + /** + * Config status generated by management servers. Will not be present if the + * CSDS server is an xDS client. + */ + 'status'?: (_envoy_service_status_v3_ConfigStatus | keyof typeof _envoy_service_status_v3_ConfigStatus); + 'listener_config'?: (_envoy_admin_v3_ListenersConfigDump | null); + 'cluster_config'?: (_envoy_admin_v3_ClustersConfigDump | null); + 'route_config'?: (_envoy_admin_v3_RoutesConfigDump | null); + 'scoped_route_config'?: (_envoy_admin_v3_ScopedRoutesConfigDump | null); + 'endpoint_config'?: (_envoy_admin_v3_EndpointsConfigDump | null); + /** + * Client config status is populated by xDS clients. Will not be present if + * the CSDS server is an xDS server. No matter what the client config status + * is, xDS clients should always dump the most recent accepted xDS config. + * + * .. attention:: + * This field is deprecated. Use :ref:`ClientResourceStatus + * ` for per-resource + * config status instead. + */ + 'client_status'?: (_envoy_service_status_v3_ClientConfigStatus | keyof typeof _envoy_service_status_v3_ClientConfigStatus); + 'per_xds_config'?: "listener_config"|"cluster_config"|"route_config"|"scoped_route_config"|"endpoint_config"; +} + +/** + * Detailed config (per xDS) with status. + * [#next-free-field: 8] + */ +export interface PerXdsConfig__Output { + /** + * Config status generated by management servers. Will not be present if the + * CSDS server is an xDS client. + */ + 'status': (keyof typeof _envoy_service_status_v3_ConfigStatus); + 'listener_config'?: (_envoy_admin_v3_ListenersConfigDump__Output | null); + 'cluster_config'?: (_envoy_admin_v3_ClustersConfigDump__Output | null); + 'route_config'?: (_envoy_admin_v3_RoutesConfigDump__Output | null); + 'scoped_route_config'?: (_envoy_admin_v3_ScopedRoutesConfigDump__Output | null); + 'endpoint_config'?: (_envoy_admin_v3_EndpointsConfigDump__Output | null); + /** + * Client config status is populated by xDS clients. Will not be present if + * the CSDS server is an xDS server. No matter what the client config status + * is, xDS clients should always dump the most recent accepted xDS config. + * + * .. attention:: + * This field is deprecated. Use :ref:`ClientResourceStatus + * ` for per-resource + * config status instead. + */ + 'client_status': (keyof typeof _envoy_service_status_v3_ClientConfigStatus); + 'per_xds_config': "listener_config"|"cluster_config"|"route_config"|"scoped_route_config"|"endpoint_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts b/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts new file mode 100644 index 000000000..4dc10efcb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts @@ -0,0 +1,92 @@ +// Original file: deps/envoy-api/envoy/type/http/v3/path_transformation.proto + + +/** + * Determines if adjacent slashes are merged into one. A common use case is for a request path + * header. Using this option in `:ref: PathNormalizationOptions + * ` + * will allow incoming requests with path `//dir///file` to match against route with `prefix` + * match set to `/dir`. When using for header transformations, note that slash merging is not + * part of `HTTP spec `_ and is provided for convenience. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_MergeSlashes { +} + +/** + * Determines if adjacent slashes are merged into one. A common use case is for a request path + * header. Using this option in `:ref: PathNormalizationOptions + * ` + * will allow incoming requests with path `//dir///file` to match against route with `prefix` + * match set to `/dir`. When using for header transformations, note that slash merging is not + * part of `HTTP spec `_ and is provided for convenience. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_MergeSlashes__Output { +} + +/** + * Should text be normalized according to RFC 3986? This typically is used for path headers + * before any processing of requests by HTTP filters or routing. This applies percent-encoded + * normalization and path segment normalization. Fails on characters disallowed in URLs + * (e.g. NULLs). See `Normalization and Comparison + * `_ for details of normalization. Note that + * this options does not perform `case normalization + * `_ + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986 { +} + +/** + * Should text be normalized according to RFC 3986? This typically is used for path headers + * before any processing of requests by HTTP filters or routing. This applies percent-encoded + * normalization and path segment normalization. Fails on characters disallowed in URLs + * (e.g. NULLs). See `Normalization and Comparison + * `_ for details of normalization. Note that + * this options does not perform `case normalization + * `_ + */ +export interface _envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986__Output { +} + +/** + * A type of operation to alter text. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation { + /** + * Enable path normalization per RFC 3986. + */ + 'normalize_path_rfc_3986'?: (_envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986 | null); + /** + * Enable merging adjacent slashes. + */ + 'merge_slashes'?: (_envoy_type_http_v3_PathTransformation_Operation_MergeSlashes | null); + 'operation_specifier'?: "normalize_path_rfc_3986"|"merge_slashes"; +} + +/** + * A type of operation to alter text. + */ +export interface _envoy_type_http_v3_PathTransformation_Operation__Output { + /** + * Enable path normalization per RFC 3986. + */ + 'normalize_path_rfc_3986'?: (_envoy_type_http_v3_PathTransformation_Operation_NormalizePathRFC3986__Output | null); + /** + * Enable merging adjacent slashes. + */ + 'merge_slashes'?: (_envoy_type_http_v3_PathTransformation_Operation_MergeSlashes__Output | null); + 'operation_specifier': "normalize_path_rfc_3986"|"merge_slashes"; +} + +export interface PathTransformation { + /** + * A list of operations to apply. Transformations will be performed in the order that they appear. + */ + 'operations'?: (_envoy_type_http_v3_PathTransformation_Operation)[]; +} + +export interface PathTransformation__Output { + /** + * A list of operations to apply. Transformations will be performed in the order that they appear. + */ + 'operations': (_envoy_type_http_v3_PathTransformation_Operation__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts index 58117faab..78d4f03da 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/MetadataMatcher.ts @@ -44,6 +44,10 @@ export interface MetadataMatcher { * The MetadataMatcher is matched if the value retrieved by path is matched to this value. */ 'value'?: (_envoy_type_matcher_v3_ValueMatcher | null); + /** + * If true, the match result will be inverted. + */ + 'invert'?: (boolean); } /** @@ -62,4 +66,8 @@ export interface MetadataMatcher__Output { * The MetadataMatcher is matched if the value retrieved by path is matched to this value. */ 'value': (_envoy_type_matcher_v3_ValueMatcher__Output | null); + /** + * If true, the match result will be inverted. + */ + 'invert': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/NodeMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/NodeMatcher.ts new file mode 100644 index 000000000..7e31b9753 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/NodeMatcher.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/node.proto + +import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; +import type { StructMatcher as _envoy_type_matcher_v3_StructMatcher, StructMatcher__Output as _envoy_type_matcher_v3_StructMatcher__Output } from '../../../../envoy/type/matcher/v3/StructMatcher'; + +/** + * Specifies the way to match a Node. + * The match follows AND semantics. + */ +export interface NodeMatcher { + /** + * Specifies match criteria on the node id. + */ + 'node_id'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * Specifies match criteria on the node metadata. + */ + 'node_metadatas'?: (_envoy_type_matcher_v3_StructMatcher)[]; +} + +/** + * Specifies the way to match a Node. + * The match follows AND semantics. + */ +export interface NodeMatcher__Output { + /** + * Specifies match criteria on the node id. + */ + 'node_id': (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * Specifies match criteria on the node metadata. + */ + 'node_metadatas': (_envoy_type_matcher_v3_StructMatcher__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts index c89190510..7440746f1 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts @@ -38,8 +38,8 @@ export interface StringMatcher { */ 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher | null); /** - * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no - * effect for the safe_regex match. + * If true, indicates the exact/prefix/suffix/contains matching should be case insensitive. This + * has no effect for the safe_regex match. * For example, the matcher *data* will match both input string *Data* and *data* if set to true. */ 'ignore_case'?: (boolean); @@ -91,8 +91,8 @@ export interface StringMatcher__Output { */ 'safe_regex'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); /** - * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no - * effect for the safe_regex match. + * If true, indicates the exact/prefix/suffix/contains matching should be case insensitive. This + * has no effect for the safe_regex match. * For example, the matcher *data* will match both input string *Data* and *data* if set to true. */ 'ignore_case': (boolean); diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts new file mode 100644 index 000000000..141806489 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts @@ -0,0 +1,153 @@ +// Original file: deps/envoy-api/envoy/type/matcher/v3/struct.proto + +import type { ValueMatcher as _envoy_type_matcher_v3_ValueMatcher, ValueMatcher__Output as _envoy_type_matcher_v3_ValueMatcher__Output } from '../../../../envoy/type/matcher/v3/ValueMatcher'; + +/** + * Specifies the segment in a path to retrieve value from Struct. + */ +export interface _envoy_type_matcher_v3_StructMatcher_PathSegment { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment'?: "key"; +} + +/** + * Specifies the segment in a path to retrieve value from Struct. + */ +export interface _envoy_type_matcher_v3_StructMatcher_PathSegment__Output { + /** + * If specified, use the key to retrieve the value in a Struct. + */ + 'key'?: (string); + 'segment': "key"; +} + +/** + * StructMatcher provides a general interface to check if a given value is matched in + * google.protobuf.Struct. It uses `path` to retrieve the value + * from the struct and then check if it's matched to the specified value. + * + * For example, for the following Struct: + * + * .. code-block:: yaml + * + * fields: + * a: + * struct_value: + * fields: + * b: + * struct_value: + * fields: + * c: + * string_value: pro + * t: + * list_value: + * values: + * - string_value: m + * - string_value: n + * + * The following MetadataMatcher is matched as the path [a, b, c] will retrieve a string value "pro" + * from the Metadata which is matched to the specified prefix match. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: b + * - key: c + * value: + * string_match: + * prefix: pr + * + * The following StructMatcher is matched as the code will match one of the string values in the + * list at the path [a, t]. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: t + * value: + * list_match: + * one_of: + * string_match: + * exact: m + * + * An example use of StructMatcher is to match metadata in envoy.v*.core.Node. + */ +export interface StructMatcher { + /** + * The path to retrieve the Value from the Struct. + */ + 'path'?: (_envoy_type_matcher_v3_StructMatcher_PathSegment)[]; + /** + * The StructMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value'?: (_envoy_type_matcher_v3_ValueMatcher | null); +} + +/** + * StructMatcher provides a general interface to check if a given value is matched in + * google.protobuf.Struct. It uses `path` to retrieve the value + * from the struct and then check if it's matched to the specified value. + * + * For example, for the following Struct: + * + * .. code-block:: yaml + * + * fields: + * a: + * struct_value: + * fields: + * b: + * struct_value: + * fields: + * c: + * string_value: pro + * t: + * list_value: + * values: + * - string_value: m + * - string_value: n + * + * The following MetadataMatcher is matched as the path [a, b, c] will retrieve a string value "pro" + * from the Metadata which is matched to the specified prefix match. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: b + * - key: c + * value: + * string_match: + * prefix: pr + * + * The following StructMatcher is matched as the code will match one of the string values in the + * list at the path [a, t]. + * + * .. code-block:: yaml + * + * path: + * - key: a + * - key: t + * value: + * list_match: + * one_of: + * string_match: + * exact: m + * + * An example use of StructMatcher is to match metadata in envoy.v*.core.Node. + */ +export interface StructMatcher__Output { + /** + * The path to retrieve the Value from the Struct. + */ + 'path': (_envoy_type_matcher_v3_StructMatcher_PathSegment__Output)[]; + /** + * The StructMatcher is matched if the value retrieved by path is matched to this value. + */ + 'value': (_envoy_type_matcher_v3_ValueMatcher__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts index fcc43b07b..50b6690d3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts @@ -27,7 +27,7 @@ export interface _envoy_type_metadata_v3_MetadataKey_PathSegment__Output { /** * MetadataKey provides a general interface using `key` and `path` to retrieve value from - * :ref:`Metadata `. + * :ref:`Metadata `. * * For example, for the following Metadata: * @@ -68,7 +68,7 @@ export interface MetadataKey { /** * MetadataKey provides a general interface using `key` and `path` to retrieve value from - * :ref:`Metadata `. + * :ref:`Metadata `. * * For example, for the following Metadata: * diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts index 2457b3f91..3ca368ccb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKind.ts @@ -2,27 +2,27 @@ /** - * Represents metadata from :ref:`the upstream cluster`. + * Represents metadata from :ref:`the upstream cluster`. */ export interface _envoy_type_metadata_v3_MetadataKind_Cluster { } /** - * Represents metadata from :ref:`the upstream cluster`. + * Represents metadata from :ref:`the upstream cluster`. */ export interface _envoy_type_metadata_v3_MetadataKind_Cluster__Output { } /** * Represents metadata from :ref:`the upstream - * host`. + * host`. */ export interface _envoy_type_metadata_v3_MetadataKind_Host { } /** * Represents metadata from :ref:`the upstream - * host`. + * host`. */ export interface _envoy_type_metadata_v3_MetadataKind_Host__Output { } @@ -40,13 +40,13 @@ export interface _envoy_type_metadata_v3_MetadataKind_Request__Output { } /** - * Represents metadata from :ref:`the route`. + * Represents metadata from :ref:`the route`. */ export interface _envoy_type_metadata_v3_MetadataKind_Route { } /** - * Represents metadata from :ref:`the route`. + * Represents metadata from :ref:`the route`. */ export interface _envoy_type_metadata_v3_MetadataKind_Route__Output { } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts index d5fe26ed5..34ac26f8c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/tracing/v3/CustomTag.ts @@ -89,8 +89,8 @@ export interface _envoy_type_tracing_v3_CustomTag_Literal__Output { /** * Metadata type custom tag using - * :ref:`MetadataKey ` to retrieve the protobuf value - * from :ref:`Metadata `, and populate the tag value with + * :ref:`MetadataKey ` to retrieve the protobuf value + * from :ref:`Metadata `, and populate the tag value with * `the canonical JSON `_ * representation of it. */ @@ -113,8 +113,8 @@ export interface _envoy_type_tracing_v3_CustomTag_Metadata { /** * Metadata type custom tag using - * :ref:`MetadataKey ` to retrieve the protobuf value - * from :ref:`Metadata `, and populate the tag value with + * :ref:`MetadataKey ` to retrieve the protobuf value + * from :ref:`Metadata `, and populate the tag value with * `the canonical JSON `_ * representation of it. */ diff --git a/packages/grpc-js-xds/src/generated/fault.ts b/packages/grpc-js-xds/src/generated/fault.ts index 0f60fc224..896382e50 100644 --- a/packages/grpc-js-xds/src/generated/fault.ts +++ b/packages/grpc-js-xds/src/generated/fault.ts @@ -38,6 +38,7 @@ export interface ProtoGrpcType { Node: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -68,6 +69,7 @@ export interface ProtoGrpcType { HeaderMatcher: MessageTypeDefinition HedgePolicy: MessageTypeDefinition InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition QueryParameterMatcher: MessageTypeDefinition RateLimit: MessageTypeDefinition RedirectAction: MessageTypeDefinition @@ -105,10 +107,14 @@ export interface ProtoGrpcType { type: { matcher: { v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition RegexMatchAndSubstitute: MessageTypeDefinition RegexMatcher: MessageTypeDefinition StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition } } metadata: { @@ -222,6 +228,7 @@ export interface ProtoGrpcType { core: { v3: { Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition } } } diff --git a/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts b/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts new file mode 100644 index 000000000..2b6490be6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/api/CustomHttpPattern.ts @@ -0,0 +1,30 @@ +// Original file: deps/googleapis/google/api/http.proto + + +/** + * A custom pattern is used for defining custom HTTP verb. + */ +export interface CustomHttpPattern { + /** + * The name of this custom HTTP verb. + */ + 'kind'?: (string); + /** + * The path matched by this custom verb. + */ + 'path'?: (string); +} + +/** + * A custom pattern is used for defining custom HTTP verb. + */ +export interface CustomHttpPattern__Output { + /** + * The name of this custom HTTP verb. + */ + 'kind': (string); + /** + * The path matched by this custom verb. + */ + 'path': (string); +} diff --git a/packages/grpc-js-xds/src/generated/google/api/Http.ts b/packages/grpc-js-xds/src/generated/google/api/Http.ts new file mode 100644 index 000000000..e9b3cb309 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/api/Http.ts @@ -0,0 +1,49 @@ +// Original file: deps/googleapis/google/api/http.proto + +import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; + +/** + * Defines the HTTP configuration for an API service. It contains a list of + * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method + * to one or more HTTP REST API methods. + */ +export interface Http { + /** + * A list of HTTP configuration rules that apply to individual API methods. + * + * **NOTE:** All service configuration rules follow "last one wins" order. + */ + 'rules'?: (_google_api_HttpRule)[]; + /** + * When set to true, URL path parameters will be fully URI-decoded except in + * cases of single segment matches in reserved expansion, where "%2F" will be + * left encoded. + * + * The default behavior is to not decode RFC 6570 reserved characters in multi + * segment matches. + */ + 'fully_decode_reserved_expansion'?: (boolean); +} + +/** + * Defines the HTTP configuration for an API service. It contains a list of + * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method + * to one or more HTTP REST API methods. + */ +export interface Http__Output { + /** + * A list of HTTP configuration rules that apply to individual API methods. + * + * **NOTE:** All service configuration rules follow "last one wins" order. + */ + 'rules': (_google_api_HttpRule__Output)[]; + /** + * When set to true, URL path parameters will be fully URI-decoded except in + * cases of single segment matches in reserved expansion, where "%2F" will be + * left encoded. + * + * The default behavior is to not decode RFC 6570 reserved characters in multi + * segment matches. + */ + 'fully_decode_reserved_expansion': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts b/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts new file mode 100644 index 000000000..243a99f80 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/google/api/HttpRule.ts @@ -0,0 +1,680 @@ +// Original file: deps/googleapis/google/api/http.proto + +import type { CustomHttpPattern as _google_api_CustomHttpPattern, CustomHttpPattern__Output as _google_api_CustomHttpPattern__Output } from '../../google/api/CustomHttpPattern'; +import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; + +/** + * # gRPC Transcoding + * + * gRPC Transcoding is a feature for mapping between a gRPC method and one or + * more HTTP REST endpoints. It allows developers to build a single API service + * that supports both gRPC APIs and REST APIs. Many systems, including [Google + * APIs](https://github.com/googleapis/googleapis), + * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC + * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), + * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature + * and use it for large scale production services. + * + * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies + * how different portions of the gRPC request message are mapped to the URL + * path, URL query parameters, and HTTP request body. It also controls how the + * gRPC response message is mapped to the HTTP response body. `HttpRule` is + * typically specified as an `google.api.http` annotation on the gRPC method. + * + * Each mapping specifies a URL path template and an HTTP method. The path + * template may refer to one or more fields in the gRPC request message, as long + * as each field is a non-repeated field with a primitive (non-message) type. + * The path template controls how fields of the request message are mapped to + * the URL path. + * + * Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/{name=messages/*}" + * }; + * } + * } + * message GetMessageRequest { + * string name = 1; // Mapped to URL path. + * } + * message Message { + * string text = 1; // The resource content. + * } + * + * This enables an HTTP REST to gRPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` + * + * Any fields in the request message which are not bound by the path template + * automatically become HTTP query parameters if there is no HTTP request body. + * For example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get:"/v1/messages/{message_id}" + * }; + * } + * } + * message GetMessageRequest { + * message SubMessage { + * string subfield = 1; + * } + * string message_id = 1; // Mapped to URL path. + * int64 revision = 2; // Mapped to URL query parameter `revision`. + * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. + * } + * + * This enables a HTTP JSON to RPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | + * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: + * "foo"))` + * + * Note that fields which are mapped to URL query parameters must have a + * primitive type or a repeated primitive type or a non-repeated message type. + * In the case of a repeated type, the parameter can be repeated in the URL + * as `...?param=A¶m=B`. In the case of a message type, each field of the + * message is mapped to a separate parameter, such as + * `...?foo.a=A&foo.b=B&foo.c=C`. + * + * For HTTP methods that allow a request body, the `body` field + * specifies the mapping. Consider a REST update method on the + * message resource collection: + * + * service Messaging { + * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "message" + * }; + * } + * } + * message UpdateMessageRequest { + * string message_id = 1; // mapped to the URL + * Message message = 2; // mapped to the body + * } + * + * The following HTTP JSON to RPC mapping is enabled, where the + * representation of the JSON in the request body is determined by + * protos JSON encoding: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" message { text: "Hi!" })` + * + * The special name `*` can be used in the body mapping to define that + * every field not bound by the path template should be mapped to the + * request body. This enables the following alternative definition of + * the update method: + * + * service Messaging { + * rpc UpdateMessage(Message) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "*" + * }; + * } + * } + * message Message { + * string message_id = 1; + * string text = 2; + * } + * + * + * The following HTTP JSON to RPC mapping is enabled: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" text: "Hi!")` + * + * Note that when using `*` in the body mapping, it is not possible to + * have HTTP parameters, as all fields not bound by the path end in + * the body. This makes this option more rarely used in practice when + * defining REST APIs. The common usage of `*` is in custom methods + * which don't use the URL at all for transferring data. + * + * It is possible to define multiple HTTP methods for one RPC by using + * the `additional_bindings` option. Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/messages/{message_id}" + * additional_bindings { + * get: "/v1/users/{user_id}/messages/{message_id}" + * } + * }; + * } + * } + * message GetMessageRequest { + * string message_id = 1; + * string user_id = 2; + * } + * + * This enables the following two alternative HTTP JSON to RPC mappings: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` + * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: + * "123456")` + * + * ## Rules for HTTP mapping + * + * 1. Leaf request fields (recursive expansion nested messages in the request + * message) are classified into three categories: + * - Fields referred by the path template. They are passed via the URL path. + * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP + * request body. + * - All other fields are passed via the URL query parameters, and the + * parameter name is the field path in the request message. A repeated + * field can be represented as multiple query parameters under the same + * name. + * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields + * are passed via URL path and HTTP request body. + * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all + * fields are passed via URL path and URL query parameters. + * + * ### Path template syntax + * + * Template = "/" Segments [ Verb ] ; + * Segments = Segment { "/" Segment } ; + * Segment = "*" | "**" | LITERAL | Variable ; + * Variable = "{" FieldPath [ "=" Segments ] "}" ; + * FieldPath = IDENT { "." IDENT } ; + * Verb = ":" LITERAL ; + * + * The syntax `*` matches a single URL path segment. The syntax `**` matches + * zero or more URL path segments, which must be the last part of the URL path + * except the `Verb`. + * + * The syntax `Variable` matches part of the URL path as specified by its + * template. A variable template must not contain other variables. If a variable + * matches a single path segment, its template may be omitted, e.g. `{var}` + * is equivalent to `{var=*}`. + * + * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` + * contains any reserved character, such characters should be percent-encoded + * before the matching. + * + * If a variable contains exactly one path segment, such as `"{var}"` or + * `"{var=*}"`, when such a variable is expanded into a URL path on the client + * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The + * server side does the reverse decoding. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{var}`. + * + * If a variable contains multiple path segments, such as `"{var=foo/*}"` + * or `"{var=**}"`, when such a variable is expanded into a URL path on the + * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. + * The server side does the reverse decoding, except "%2F" and "%2f" are left + * unchanged. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{+var}`. + * + * ## Using gRPC API Service Configuration + * + * gRPC API Service Configuration (service config) is a configuration language + * for configuring a gRPC service to become a user-facing product. The + * service config is simply the YAML representation of the `google.api.Service` + * proto message. + * + * As an alternative to annotating your proto file, you can configure gRPC + * transcoding in your service config YAML files. You do this by specifying a + * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same + * effect as the proto annotation. This can be particularly useful if you + * have a proto that is reused in multiple services. Note that any transcoding + * specified in the service config will override any matching transcoding + * configuration in the proto. + * + * Example: + * + * http: + * rules: + * # Selects a gRPC method and applies HttpRule to it. + * - selector: example.v1.Messaging.GetMessage + * get: /v1/messages/{message_id}/{sub.subfield} + * + * ## Special notes + * + * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the + * proto to JSON conversion must follow the [proto3 + * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + * + * While the single segment variable follows the semantics of + * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String + * Expansion, the multi segment variable **does not** follow RFC 6570 Section + * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion + * does not expand special characters like `?` and `#`, which would lead + * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding + * for multi segment variables. + * + * The path variables **must not** refer to any repeated or mapped field, + * because client libraries are not capable of handling such variable expansion. + * + * The path variables **must not** capture the leading "/" character. The reason + * is that the most common use case "{var}" does not capture the leading "/" + * character. For consistency, all path variables must share the same behavior. + * + * Repeated message fields must not be mapped to URL query parameters, because + * no client library can support such complicated mapping. + * + * If an API needs to use a JSON array for request or response body, it can map + * the request or response body to a repeated field. However, some gRPC + * Transcoding implementations may not support this feature. + */ +export interface HttpRule { + /** + * Selects a method to which this rule applies. + * + * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + */ + 'selector'?: (string); + /** + * Maps to HTTP GET. Used for listing and getting information about + * resources. + */ + 'get'?: (string); + /** + * Maps to HTTP PUT. Used for replacing a resource. + */ + 'put'?: (string); + /** + * Maps to HTTP POST. Used for creating a resource or performing an action. + */ + 'post'?: (string); + /** + * Maps to HTTP DELETE. Used for deleting a resource. + */ + 'delete'?: (string); + /** + * Maps to HTTP PATCH. Used for updating a resource. + */ + 'patch'?: (string); + /** + * The name of the request field whose value is mapped to the HTTP request + * body, or `*` for mapping all request fields not captured by the path + * pattern to the HTTP body, or omitted for not having any HTTP request body. + * + * NOTE: the referred field must be present at the top-level of the request + * message type. + */ + 'body'?: (string); + /** + * The custom pattern is used for specifying an HTTP method that is not + * included in the `pattern` field, such as HEAD, or "*" to leave the + * HTTP method unspecified for this rule. The wild-card rule is useful + * for services that provide content to Web (HTML) clients. + */ + 'custom'?: (_google_api_CustomHttpPattern | null); + /** + * Additional HTTP bindings for the selector. Nested bindings must + * not contain an `additional_bindings` field themselves (that is, + * the nesting may only be one level deep). + */ + 'additional_bindings'?: (_google_api_HttpRule)[]; + /** + * Optional. The name of the response field whose value is mapped to the HTTP + * response body. When omitted, the entire response message will be used + * as the HTTP response body. + * + * NOTE: The referred field must be present at the top-level of the response + * message type. + */ + 'response_body'?: (string); + /** + * Determines the URL pattern is matched by this rules. This pattern can be + * used with any of the {get|put|post|delete|patch} methods. A custom method + * can be defined using the 'custom' field. + */ + 'pattern'?: "get"|"put"|"post"|"delete"|"patch"|"custom"; +} + +/** + * # gRPC Transcoding + * + * gRPC Transcoding is a feature for mapping between a gRPC method and one or + * more HTTP REST endpoints. It allows developers to build a single API service + * that supports both gRPC APIs and REST APIs. Many systems, including [Google + * APIs](https://github.com/googleapis/googleapis), + * [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC + * Gateway](https://github.com/grpc-ecosystem/grpc-gateway), + * and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature + * and use it for large scale production services. + * + * `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies + * how different portions of the gRPC request message are mapped to the URL + * path, URL query parameters, and HTTP request body. It also controls how the + * gRPC response message is mapped to the HTTP response body. `HttpRule` is + * typically specified as an `google.api.http` annotation on the gRPC method. + * + * Each mapping specifies a URL path template and an HTTP method. The path + * template may refer to one or more fields in the gRPC request message, as long + * as each field is a non-repeated field with a primitive (non-message) type. + * The path template controls how fields of the request message are mapped to + * the URL path. + * + * Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/{name=messages/*}" + * }; + * } + * } + * message GetMessageRequest { + * string name = 1; // Mapped to URL path. + * } + * message Message { + * string text = 1; // The resource content. + * } + * + * This enables an HTTP REST to gRPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` + * + * Any fields in the request message which are not bound by the path template + * automatically become HTTP query parameters if there is no HTTP request body. + * For example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get:"/v1/messages/{message_id}" + * }; + * } + * } + * message GetMessageRequest { + * message SubMessage { + * string subfield = 1; + * } + * string message_id = 1; // Mapped to URL path. + * int64 revision = 2; // Mapped to URL query parameter `revision`. + * SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. + * } + * + * This enables a HTTP JSON to RPC mapping as below: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456?revision=2&sub.subfield=foo` | + * `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: + * "foo"))` + * + * Note that fields which are mapped to URL query parameters must have a + * primitive type or a repeated primitive type or a non-repeated message type. + * In the case of a repeated type, the parameter can be repeated in the URL + * as `...?param=A¶m=B`. In the case of a message type, each field of the + * message is mapped to a separate parameter, such as + * `...?foo.a=A&foo.b=B&foo.c=C`. + * + * For HTTP methods that allow a request body, the `body` field + * specifies the mapping. Consider a REST update method on the + * message resource collection: + * + * service Messaging { + * rpc UpdateMessage(UpdateMessageRequest) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "message" + * }; + * } + * } + * message UpdateMessageRequest { + * string message_id = 1; // mapped to the URL + * Message message = 2; // mapped to the body + * } + * + * The following HTTP JSON to RPC mapping is enabled, where the + * representation of the JSON in the request body is determined by + * protos JSON encoding: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" message { text: "Hi!" })` + * + * The special name `*` can be used in the body mapping to define that + * every field not bound by the path template should be mapped to the + * request body. This enables the following alternative definition of + * the update method: + * + * service Messaging { + * rpc UpdateMessage(Message) returns (Message) { + * option (google.api.http) = { + * patch: "/v1/messages/{message_id}" + * body: "*" + * }; + * } + * } + * message Message { + * string message_id = 1; + * string text = 2; + * } + * + * + * The following HTTP JSON to RPC mapping is enabled: + * + * HTTP | gRPC + * -----|----- + * `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: + * "123456" text: "Hi!")` + * + * Note that when using `*` in the body mapping, it is not possible to + * have HTTP parameters, as all fields not bound by the path end in + * the body. This makes this option more rarely used in practice when + * defining REST APIs. The common usage of `*` is in custom methods + * which don't use the URL at all for transferring data. + * + * It is possible to define multiple HTTP methods for one RPC by using + * the `additional_bindings` option. Example: + * + * service Messaging { + * rpc GetMessage(GetMessageRequest) returns (Message) { + * option (google.api.http) = { + * get: "/v1/messages/{message_id}" + * additional_bindings { + * get: "/v1/users/{user_id}/messages/{message_id}" + * } + * }; + * } + * } + * message GetMessageRequest { + * string message_id = 1; + * string user_id = 2; + * } + * + * This enables the following two alternative HTTP JSON to RPC mappings: + * + * HTTP | gRPC + * -----|----- + * `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` + * `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: + * "123456")` + * + * ## Rules for HTTP mapping + * + * 1. Leaf request fields (recursive expansion nested messages in the request + * message) are classified into three categories: + * - Fields referred by the path template. They are passed via the URL path. + * - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP + * request body. + * - All other fields are passed via the URL query parameters, and the + * parameter name is the field path in the request message. A repeated + * field can be represented as multiple query parameters under the same + * name. + * 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields + * are passed via URL path and HTTP request body. + * 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all + * fields are passed via URL path and URL query parameters. + * + * ### Path template syntax + * + * Template = "/" Segments [ Verb ] ; + * Segments = Segment { "/" Segment } ; + * Segment = "*" | "**" | LITERAL | Variable ; + * Variable = "{" FieldPath [ "=" Segments ] "}" ; + * FieldPath = IDENT { "." IDENT } ; + * Verb = ":" LITERAL ; + * + * The syntax `*` matches a single URL path segment. The syntax `**` matches + * zero or more URL path segments, which must be the last part of the URL path + * except the `Verb`. + * + * The syntax `Variable` matches part of the URL path as specified by its + * template. A variable template must not contain other variables. If a variable + * matches a single path segment, its template may be omitted, e.g. `{var}` + * is equivalent to `{var=*}`. + * + * The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` + * contains any reserved character, such characters should be percent-encoded + * before the matching. + * + * If a variable contains exactly one path segment, such as `"{var}"` or + * `"{var=*}"`, when such a variable is expanded into a URL path on the client + * side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The + * server side does the reverse decoding. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{var}`. + * + * If a variable contains multiple path segments, such as `"{var=foo/*}"` + * or `"{var=**}"`, when such a variable is expanded into a URL path on the + * client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. + * The server side does the reverse decoding, except "%2F" and "%2f" are left + * unchanged. Such variables show up in the + * [Discovery + * Document](https://developers.google.com/discovery/v1/reference/apis) as + * `{+var}`. + * + * ## Using gRPC API Service Configuration + * + * gRPC API Service Configuration (service config) is a configuration language + * for configuring a gRPC service to become a user-facing product. The + * service config is simply the YAML representation of the `google.api.Service` + * proto message. + * + * As an alternative to annotating your proto file, you can configure gRPC + * transcoding in your service config YAML files. You do this by specifying a + * `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same + * effect as the proto annotation. This can be particularly useful if you + * have a proto that is reused in multiple services. Note that any transcoding + * specified in the service config will override any matching transcoding + * configuration in the proto. + * + * Example: + * + * http: + * rules: + * # Selects a gRPC method and applies HttpRule to it. + * - selector: example.v1.Messaging.GetMessage + * get: /v1/messages/{message_id}/{sub.subfield} + * + * ## Special notes + * + * When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the + * proto to JSON conversion must follow the [proto3 + * specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + * + * While the single segment variable follows the semantics of + * [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String + * Expansion, the multi segment variable **does not** follow RFC 6570 Section + * 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion + * does not expand special characters like `?` and `#`, which would lead + * to invalid URLs. As the result, gRPC Transcoding uses a custom encoding + * for multi segment variables. + * + * The path variables **must not** refer to any repeated or mapped field, + * because client libraries are not capable of handling such variable expansion. + * + * The path variables **must not** capture the leading "/" character. The reason + * is that the most common use case "{var}" does not capture the leading "/" + * character. For consistency, all path variables must share the same behavior. + * + * Repeated message fields must not be mapped to URL query parameters, because + * no client library can support such complicated mapping. + * + * If an API needs to use a JSON array for request or response body, it can map + * the request or response body to a repeated field. However, some gRPC + * Transcoding implementations may not support this feature. + */ +export interface HttpRule__Output { + /** + * Selects a method to which this rule applies. + * + * Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + */ + 'selector': (string); + /** + * Maps to HTTP GET. Used for listing and getting information about + * resources. + */ + 'get'?: (string); + /** + * Maps to HTTP PUT. Used for replacing a resource. + */ + 'put'?: (string); + /** + * Maps to HTTP POST. Used for creating a resource or performing an action. + */ + 'post'?: (string); + /** + * Maps to HTTP DELETE. Used for deleting a resource. + */ + 'delete'?: (string); + /** + * Maps to HTTP PATCH. Used for updating a resource. + */ + 'patch'?: (string); + /** + * The name of the request field whose value is mapped to the HTTP request + * body, or `*` for mapping all request fields not captured by the path + * pattern to the HTTP body, or omitted for not having any HTTP request body. + * + * NOTE: the referred field must be present at the top-level of the request + * message type. + */ + 'body': (string); + /** + * The custom pattern is used for specifying an HTTP method that is not + * included in the `pattern` field, such as HEAD, or "*" to leave the + * HTTP method unspecified for this rule. The wild-card rule is useful + * for services that provide content to Web (HTML) clients. + */ + 'custom'?: (_google_api_CustomHttpPattern__Output | null); + /** + * Additional HTTP bindings for the selector. Nested bindings must + * not contain an `additional_bindings` field themselves (that is, + * the nesting may only be one level deep). + */ + 'additional_bindings': (_google_api_HttpRule__Output)[]; + /** + * Optional. The name of the response field whose value is mapped to the HTTP + * response body. When omitted, the entire response message will be used + * as the HTTP response body. + * + * NOTE: The referred field must be present at the top-level of the response + * message type. + */ + 'response_body': (string); + /** + * Determines the URL pattern is matched by this rules. This pattern can be + * used with any of the {get|put|post|delete|patch} methods. A custom method + * can be defined using the 'custom' field. + */ + 'pattern': "get"|"put"|"post"|"delete"|"patch"|"custom"; +} 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 48fea77be..9ba51ed60 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/EnumValueOptions.ts @@ -8,6 +8,7 @@ export interface EnumValueOptions { '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 { @@ -15,4 +16,5 @@ export interface EnumValueOptions__Output { '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 91af8a985..d62db88d0 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -2,6 +2,7 @@ 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 { FieldSecurityAnnotation as _udpa_annotations_FieldSecurityAnnotation, FieldSecurityAnnotation__Output as _udpa_annotations_FieldSecurityAnnotation__Output } from '../../udpa/annotations/FieldSecurityAnnotation'; 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'; @@ -30,7 +31,9 @@ export interface FieldOptions { 'weak'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; '.validate.rules'?: (_validate_FieldRules | null); + '.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation | null); '.udpa.annotations.sensitive'?: (boolean); + '.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); @@ -45,7 +48,9 @@ export interface FieldOptions__Output { 'weak': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; '.validate.rules': (_validate_FieldRules__Output | null); + '.udpa.annotations.security': (_udpa_annotations_FieldSecurityAnnotation__Output | null); '.udpa.annotations.sensitive': (boolean); + '.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/MethodOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts index e47fd756c..5f81f0dd9 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts @@ -1,13 +1,16 @@ // Original file: null import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { 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 | null); } export interface MethodOptions__Output { 'deprecated': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + '.google.api.http': (_google_api_HttpRule__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/http_connection_manager.ts b/packages/grpc-js-xds/src/generated/http_connection_manager.ts index 351e65400..137dcd45a 100644 --- a/packages/grpc-js-xds/src/generated/http_connection_manager.ts +++ b/packages/grpc-js-xds/src/generated/http_connection_manager.ts @@ -34,6 +34,7 @@ export interface ProtoGrpcType { v3: { Address: MessageTypeDefinition AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition ApiConfigSource: MessageTypeDefinition ApiVersion: EnumTypeDefinition AsyncDataSource: MessageTypeDefinition @@ -63,6 +64,8 @@ export interface ProtoGrpcType { Node: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -73,6 +76,7 @@ export interface ProtoGrpcType { RuntimeFractionalPercent: MessageTypeDefinition RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition @@ -88,6 +92,7 @@ export interface ProtoGrpcType { } route: { v3: { + ClusterSpecifierPlugin: MessageTypeDefinition CorsPolicy: MessageTypeDefinition Decorator: MessageTypeDefinition DirectResponseAction: MessageTypeDefinition @@ -96,6 +101,7 @@ export interface ProtoGrpcType { HeaderMatcher: MessageTypeDefinition HedgePolicy: MessageTypeDefinition InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition QueryParameterMatcher: MessageTypeDefinition RateLimit: MessageTypeDefinition RedirectAction: MessageTypeDefinition @@ -123,6 +129,7 @@ export interface ProtoGrpcType { network: { http_connection_manager: { v3: { + EnvoyMobileHttpConnectionManager: MessageTypeDefinition HttpConnectionManager: MessageTypeDefinition HttpFilter: MessageTypeDefinition LocalReplyConfig: MessageTypeDefinition @@ -138,6 +145,11 @@ export interface ProtoGrpcType { } } type: { + http: { + v3: { + PathTransformation: MessageTypeDefinition + } + } matcher: { v3: { DoubleMatcher: MessageTypeDefinition @@ -262,6 +274,7 @@ export interface ProtoGrpcType { core: { v3: { Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition } } } diff --git a/packages/grpc-js-xds/src/generated/listener.ts b/packages/grpc-js-xds/src/generated/listener.ts index aac080a90..b92353ab1 100644 --- a/packages/grpc-js-xds/src/generated/listener.ts +++ b/packages/grpc-js-xds/src/generated/listener.ts @@ -34,6 +34,7 @@ export interface ProtoGrpcType { v3: { Address: MessageTypeDefinition AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition ApiConfigSource: MessageTypeDefinition ApiVersion: EnumTypeDefinition AsyncDataSource: MessageTypeDefinition @@ -47,16 +48,24 @@ export interface ProtoGrpcType { EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition ExtensionConfigSource: MessageTypeDefinition + GrpcProtocolOptions: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition HeaderValueOption: MessageTypeDefinition + Http1ProtocolOptions: MessageTypeDefinition + Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition + HttpProtocolOptions: MessageTypeDefinition HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -67,13 +76,17 @@ export interface ProtoGrpcType { RuntimeFractionalPercent: MessageTypeDefinition RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition + TcpProtocolOptions: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition TypedExtensionConfig: MessageTypeDefinition + UdpSocketConfig: MessageTypeDefinition + UpstreamHttpProtocolOptions: MessageTypeDefinition WatchedDirectory: MessageTypeDefinition } } @@ -88,6 +101,7 @@ export interface ProtoGrpcType { ListenerCollection: MessageTypeDefinition ListenerFilter: MessageTypeDefinition ListenerFilterChainMatchPredicate: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition UdpListenerConfig: MessageTypeDefinition } } @@ -101,6 +115,7 @@ export interface ProtoGrpcType { HeaderMatcher: MessageTypeDefinition HedgePolicy: MessageTypeDefinition InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition QueryParameterMatcher: MessageTypeDefinition RateLimit: MessageTypeDefinition RedirectAction: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/lrs.ts b/packages/grpc-js-xds/src/generated/lrs.ts index 929fd6914..e92f80800 100644 --- a/packages/grpc-js-xds/src/generated/lrs.ts +++ b/packages/grpc-js-xds/src/generated/lrs.ts @@ -10,6 +10,8 @@ type SubtypeConstructor any, Subtype> export interface ProtoGrpcType { envoy: { + annotations: { + } api: { v2: { core: { @@ -73,6 +75,7 @@ export interface ProtoGrpcType { Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition RetryPolicy: MessageTypeDefinition @@ -200,5 +203,21 @@ export interface ProtoGrpcType { UInt32Rules: MessageTypeDefinition UInt64Rules: MessageTypeDefinition } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + ContextParams: MessageTypeDefinition + } + } + } } diff --git a/packages/grpc-js-xds/src/generated/route.ts b/packages/grpc-js-xds/src/generated/route.ts index 67b9c5ce4..d6485bcd7 100644 --- a/packages/grpc-js-xds/src/generated/route.ts +++ b/packages/grpc-js-xds/src/generated/route.ts @@ -38,6 +38,7 @@ export interface ProtoGrpcType { Node: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + QueryParameter: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition @@ -60,6 +61,7 @@ export interface ProtoGrpcType { } route: { v3: { + ClusterSpecifierPlugin: MessageTypeDefinition CorsPolicy: MessageTypeDefinition Decorator: MessageTypeDefinition DirectResponseAction: MessageTypeDefinition @@ -68,6 +70,7 @@ export interface ProtoGrpcType { HeaderMatcher: MessageTypeDefinition HedgePolicy: MessageTypeDefinition InternalRedirectPolicy: MessageTypeDefinition + NonForwardingAction: MessageTypeDefinition QueryParameterMatcher: MessageTypeDefinition RateLimit: MessageTypeDefinition RedirectAction: MessageTypeDefinition @@ -87,10 +90,14 @@ export interface ProtoGrpcType { type: { matcher: { v3: { + DoubleMatcher: MessageTypeDefinition + ListMatcher: MessageTypeDefinition ListStringMatcher: MessageTypeDefinition + MetadataMatcher: MessageTypeDefinition RegexMatchAndSubstitute: MessageTypeDefinition RegexMatcher: MessageTypeDefinition StringMatcher: MessageTypeDefinition + ValueMatcher: MessageTypeDefinition } } metadata: { @@ -204,6 +211,7 @@ export interface ProtoGrpcType { core: { v3: { Authority: MessageTypeDefinition + ContextParams: MessageTypeDefinition } } } diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index ab036184e..f6e2ad402 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -432,7 +432,12 @@ class XdsResolver implements Resolver { weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, dynamicFilterFactories: extraFilterFactories}); } routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, timeout); + break; } + default: + /* The validation logic should prevent us from reaching this point. + * This is just for the type checker. */ + continue; } const routeMatcher = getPredicateForMatcher(route.match!); matchList.push({matcher: routeMatcher, action: routeAction}); From dca36701fc15b85cfe28f727658496d084fd3339 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 2 Dec 2021 16:14:40 -0500 Subject: [PATCH 138/694] grpc-js-xds: Add details to ADS response handling result --- packages/grpc-js-xds/src/xds-client.ts | 128 ++++++++++-------- .../src/xds-stream-state/cds-state.ts | 37 +++-- .../src/xds-stream-state/eds-state.ts | 30 ++-- .../src/xds-stream-state/lds-state.ts | 38 ++++-- .../src/xds-stream-state/rds-state.ts | 30 ++-- .../src/xds-stream-state/xds-stream-state.ts | 25 +++- 6 files changed, 193 insertions(+), 95 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 8a08276eb..6c8bcb67f 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -45,7 +45,7 @@ import { EdsState } from './xds-stream-state/eds-state'; import { CdsState } from './xds-stream-state/cds-state'; import { RdsState } from './xds-stream-state/rds-state'; import { LdsState } from './xds-stream-state/lds-state'; -import { Watcher } from './xds-stream-state/xds-stream-state'; +import { HandleResponseResult, ResourcePair, Watcher } from './xds-stream-state/xds-stream-state'; import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; @@ -242,11 +242,14 @@ function getResponseMessages( targetTypeUrl: T, allowedTypeUrls: string[], resources: Any__Output[] -): AdsOutputType[] { - const result: AdsOutputType[] = []; +): ResourcePair>[] { + const result: ResourcePair>[] = []; for (const resource of resources) { if (allowedTypeUrls.includes(resource.type_url)) { - result.push(decodeSingleResource(targetTypeUrl, resource.value)); + result.push({ + resource: decodeSingleResource(targetTypeUrl, resource.value), + raw: resource + }); } else { throw new Error( `ADS Error: Invalid resource type ${resource.type_url}, expected ${allowedTypeUrls}` @@ -450,8 +453,10 @@ export class XdsClient { } private handleAdsResponse(message: DiscoveryResponse__Output) { - let errorString: string | null; - let serviceKind: AdsServiceKind; + let handleResponseResult: { + result: HandleResponseResult; + serviceKind: AdsServiceKind; + } | null = null; let isV2: boolean; switch (message.type_url) { case EDS_TYPE_URL_V2: @@ -463,56 +468,71 @@ export class XdsClient { default: isV2 = false; } - switch (message.type_url) { - case EDS_TYPE_URL_V2: - case EDS_TYPE_URL_V3: - errorString = this.adsState.eds.handleResponses( - getResponseMessages(EDS_TYPE_URL_V3, [EDS_TYPE_URL_V2, EDS_TYPE_URL_V3], message.resources), - isV2 - ); - serviceKind = 'eds'; - break; - case CDS_TYPE_URL_V2: - case CDS_TYPE_URL_V3: - errorString = this.adsState.cds.handleResponses( - getResponseMessages(CDS_TYPE_URL_V3, [CDS_TYPE_URL_V2, CDS_TYPE_URL_V3], message.resources), - isV2 - ); - serviceKind = 'cds'; - break; - case RDS_TYPE_URL_V2: - case RDS_TYPE_URL_V3: - errorString = this.adsState.rds.handleResponses( - getResponseMessages(RDS_TYPE_URL_V3, [RDS_TYPE_URL_V2, RDS_TYPE_URL_V3], message.resources), - isV2 - ); - serviceKind = 'rds'; - break; - case LDS_TYPE_URL_V2: - case LDS_TYPE_URL_V3: - errorString = this.adsState.lds.handleResponses( - getResponseMessages(LDS_TYPE_URL_V3, [LDS_TYPE_URL_V2, LDS_TYPE_URL_V3], message.resources), - isV2 - ); - serviceKind = 'lds'; - break; - default: - errorString = `Unknown type_url ${message.type_url}`; - // This is not used in this branch, but setting it makes the types easier to handle - serviceKind = 'eds'; + try { + switch (message.type_url) { + case EDS_TYPE_URL_V2: + case EDS_TYPE_URL_V3: + handleResponseResult = { + result: this.adsState.eds.handleResponses( + getResponseMessages(EDS_TYPE_URL_V3, [EDS_TYPE_URL_V2, EDS_TYPE_URL_V3], message.resources), + isV2 + ), + serviceKind: 'eds' + }; + break; + case CDS_TYPE_URL_V2: + case CDS_TYPE_URL_V3: + handleResponseResult = { + result: this.adsState.cds.handleResponses( + getResponseMessages(CDS_TYPE_URL_V3, [CDS_TYPE_URL_V2, CDS_TYPE_URL_V3], message.resources), + isV2 + ), + serviceKind: 'cds' + }; + break; + case RDS_TYPE_URL_V2: + case RDS_TYPE_URL_V3: + handleResponseResult = { + result: this.adsState.rds.handleResponses( + getResponseMessages(RDS_TYPE_URL_V3, [RDS_TYPE_URL_V2, RDS_TYPE_URL_V3], message.resources), + isV2 + ), + serviceKind: 'rds' + }; + break; + case LDS_TYPE_URL_V2: + case LDS_TYPE_URL_V3: + handleResponseResult = { + result: this.adsState.lds.handleResponses( + getResponseMessages(LDS_TYPE_URL_V3, [LDS_TYPE_URL_V2, LDS_TYPE_URL_V3], message.resources), + isV2 + ), + serviceKind: 'lds' + } + break; + } + } catch (e) { + trace('Nacking message with protobuf parsing error: ' + e.message); + this.nack(message.type_url, e.message); } - if (errorString === null) { - trace('Acking message with type URL ' + message.type_url); - /* errorString can only be null in one of the first 4 cases, which - * implies that message.type_url is one of the 4 known type URLs, which - * means that this type assertion is valid. */ - const typeUrl = message.type_url as AdsTypeUrl; - this.adsState[serviceKind].nonce = message.nonce; - this.adsState[serviceKind].versionInfo = message.version_info; - this.ack(serviceKind); + if (handleResponseResult === null) { + // Null handleResponseResult means that the type_url was unrecognized + trace('Nacking message with unknown type URL ' + message.type_url); + this.nack(message.type_url, `Unknown type_url ${message.type_url}`); } else { - trace('Nacking message with type URL ' + message.type_url + ': "' + errorString + '"'); - this.nack(message.type_url, errorString); + if (handleResponseResult.result.rejected.length > 0) { + // rejected.length > 0 means that at least one message validation failed + const errorString = `${handleResponseResult.serviceKind.toUpperCase()} Error: ${handleResponseResult.result.rejected[0].error}`; + trace('Nacking message with type URL ' + message.type_url + ': ' + errorString); + this.nack(message.type_url, errorString); + } else { + // If we get here, all message validation succeeded + trace('Acking message with type URL ' + message.type_url); + const serviceKind = handleResponseResult.serviceKind; + this.adsState[serviceKind].nonce = message.nonce; + this.adsState[serviceKind].versionInfo = message.version_info; + this.ack(serviceKind); + } } } diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 05477388d..ce0434b8b 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -17,8 +17,9 @@ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; +import { Any__Output } from "../generated/google/protobuf/Any"; import { EdsState } from "./eds-state"; -import { Watcher, XdsStreamState } from "./xds-stream-state"; +import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; const TRACER_NAME = 'xds_client'; @@ -125,26 +126,40 @@ export class CdsState implements XdsStreamState { * onResourceDoesNotExist method. * @param allClusterNames */ - private handleMissingNames(allClusterNames: Set) { + private handleMissingNames(allClusterNames: Set): string[] { + const missingNames: string[] = []; for (const [clusterName, watcherList] of this.watchers.entries()) { if (!allClusterNames.has(clusterName)) { trace('Reporting CDS resource does not exist for clusterName ' + clusterName); + missingNames.push(clusterName); for (const watcher of watcherList) { watcher.onResourceDoesNotExist(); } } } + return missingNames; } - handleResponses(responses: Cluster__Output[], isV2: boolean): string | null { + handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { const validResponses: Cluster__Output[] = []; - let errorMessage: string | null = null; - for (const message of responses) { - if (this.validateResponse(message)) { - validResponses.push(message); + const result: HandleResponseResult = { + accepted: [], + rejected: [], + missing: [] + } + for (const {resource, raw} of responses) { + if (this.validateResponse(resource)) { + validResponses.push(resource); + result.accepted.push({ + name: resource.name, + raw: raw}); } else { - trace('CDS validation failed for message ' + JSON.stringify(message)); - errorMessage = 'CDS Error: Cluster validation failed'; + trace('CDS validation failed for message ' + JSON.stringify(resource)); + result.rejected.push({ + name: resource.name, + raw: raw, + error: `Cluster validation failed for resource ${resource.name}` + }); } } this.latestResponses = validResponses; @@ -163,9 +178,9 @@ export class CdsState implements XdsStreamState { } } trace('Received CDS updates for cluster names [' + Array.from(allClusterNames) + ']'); - this.handleMissingNames(allClusterNames); + result.missing = this.handleMissingNames(allClusterNames); this.edsState.handleMissingNames(allEdsServiceNames); - return errorMessage; + return result; } reportStreamError(status: StatusObject): void { diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index f5a8b774f..5360400c5 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -18,7 +18,8 @@ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { isIPv4, isIPv6 } from "net"; import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; -import { Watcher, XdsStreamState } from "./xds-stream-state"; +import { Any__Output } from "../generated/google/protobuf/Any"; +import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; const TRACER_NAME = 'xds_client'; @@ -145,15 +146,26 @@ export class EdsState implements XdsStreamState { } } - handleResponses(responses: ClusterLoadAssignment__Output[], isV2: boolean) { + handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { const validResponses: ClusterLoadAssignment__Output[] = []; - let errorMessage: string | null = null; - for (const message of responses) { - if (this.validateResponse(message)) { - validResponses.push(message); + let result: HandleResponseResult = { + accepted: [], + rejected: [], + missing: [] + } + for (const {resource, raw} of responses) { + if (this.validateResponse(resource)) { + validResponses.push(resource); + result.accepted.push({ + name: resource.cluster_name, + raw: raw}); } else { - trace('EDS validation failed for message ' + JSON.stringify(message)); - errorMessage = 'EDS Error: ClusterLoadAssignment validation failed'; + trace('EDS validation failed for message ' + JSON.stringify(resource)); + result.rejected.push({ + name: resource.cluster_name, + raw: raw, + error: `ClusterLoadAssignment validation failed for resource ${resource.cluster_name}` + }); } } this.latestResponses = validResponses; @@ -167,7 +179,7 @@ export class EdsState implements XdsStreamState { } } trace('Received EDS updates for cluster names [' + Array.from(allClusterNames) + ']'); - return errorMessage; + return result; } reportStreamError(status: StatusObject): void { diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 7318b3b8a..0c4fdc516 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -19,11 +19,12 @@ import * as protoLoader from '@grpc/proto-loader'; import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { Listener__Output } from '../generated/envoy/config/listener/v3/Listener'; import { RdsState } from "./rds-state"; -import { Watcher, XdsStreamState } from "./xds-stream-state"; +import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; import { HttpConnectionManager__Output } from '../generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V2, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from '../resources'; import { getTopLevelFilterUrl, validateTopLevelFilter } from '../http-filter'; import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; +import { Any__Output } from '../generated/google/protobuf/Any'; const TRACER_NAME = 'xds_client'; @@ -143,25 +144,40 @@ export class LdsState implements XdsStreamState { return false; } - private handleMissingNames(allTargetNames: Set) { + private handleMissingNames(allTargetNames: Set): string[] { + const missingNames: string[] = []; for (const [targetName, watcherList] of this.watchers.entries()) { if (!allTargetNames.has(targetName)) { + missingNames.push(targetName); for (const watcher of watcherList) { watcher.onResourceDoesNotExist(); } } } + return missingNames; } - handleResponses(responses: Listener__Output[], isV2: boolean): string | null { + handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { const validResponses: Listener__Output[] = []; - let errorMessage: string | null = null; - for (const message of responses) { - if (this.validateResponse(message, isV2)) { - validResponses.push(message); + let result: HandleResponseResult = { + accepted: [], + rejected: [], + missing: [] + } + for (const {resource, raw} of responses) { + if (this.validateResponse(resource, isV2)) { + validResponses.push(resource); + result.accepted.push({ + name: resource.name, + raw: raw + }); } else { - trace('LDS validation failed for message ' + JSON.stringify(message)); - errorMessage = 'LDS Error: Route validation failed'; + trace('LDS validation failed for message ' + JSON.stringify(resource)); + result.rejected.push({ + name: resource.name, + raw: raw, + error: `Listener validation failed for resource ${resource.name}` + }); } } this.latestResponses = validResponses; @@ -180,9 +196,9 @@ export class LdsState implements XdsStreamState { } } trace('Received LDS response with listener names [' + Array.from(allTargetNames) + ']'); - this.handleMissingNames(allTargetNames); + result.missing = this.handleMissingNames(allTargetNames); this.rdsState.handleMissingNames(allRouteConfigNames); - return errorMessage; + return result; } reportStreamError(status: StatusObject): void { diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index bb7e0bc51..0ff4c2aae 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -18,9 +18,10 @@ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { EXPERIMENTAL_FAULT_INJECTION } from "../environment"; import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; +import { Any__Output } from "../generated/google/protobuf/Any"; import { validateOverrideFilter } from "../http-filter"; import { CdsLoadBalancingConfig } from "../load-balancer-cds"; -import { Watcher, XdsStreamState } from "./xds-stream-state"; +import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; import ServiceConfig = experimental.ServiceConfig; const TRACER_NAME = 'xds_client'; @@ -182,15 +183,26 @@ export class RdsState implements XdsStreamState { } } - handleResponses(responses: RouteConfiguration__Output[], isV2: boolean): string | null { + handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { const validResponses: RouteConfiguration__Output[] = []; - let errorMessage: string | null = null; - for (const message of responses) { - if (this.validateResponse(message, isV2)) { - validResponses.push(message); + let result: HandleResponseResult = { + accepted: [], + rejected: [], + missing: [] + } + for (const {resource, raw} of responses) { + if (this.validateResponse(resource, isV2)) { + validResponses.push(resource); + result.accepted.push({ + name: resource.name, + raw: raw}); } else { - trace('RDS validation failed for message ' + JSON.stringify(message)); - errorMessage = 'RDS Error: Route validation failed'; + trace('RDS validation failed for message ' + JSON.stringify(resource)); + result.rejected.push({ + name: resource.name, + raw: raw, + error: `Route validation failed for resource ${resource.name}` + }); } } this.latestResponses = validResponses; @@ -204,7 +216,7 @@ export class RdsState implements XdsStreamState { } } trace('Received RDS response with route config names [' + Array.from(allRouteConfigNames) + ']'); - return errorMessage; + return result; } reportStreamError(status: StatusObject): void { diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 14f3d1c76..c8cbc41cf 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -16,6 +16,7 @@ */ import { StatusObject } from "@grpc/grpc-js"; +import { Any__Output } from "../generated/google/protobuf/Any"; export interface Watcher { /* Including the isV2 flag here is a bit of a kludge. It would probably be @@ -28,6 +29,28 @@ export interface Watcher { onResourceDoesNotExist(): void; } +export interface ResourcePair { + resource: ResourceType; + raw: Any__Output; +} + +export interface AcceptedResourceEntry { + name: string; + raw: Any__Output; +} + +export interface RejectedResourceEntry { + name: string; + raw: Any__Output; + error: string; +} + +export interface HandleResponseResult { + accepted: AcceptedResourceEntry[]; + rejected: RejectedResourceEntry[]; + missing: string[]; +} + export interface XdsStreamState { versionInfo: string; nonce: string; @@ -37,7 +60,7 @@ export interface XdsStreamState { * or null if it should be acked. * @param responses */ - handleResponses(responses: ResponseType[], isV2: boolean): string | null; + handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult; reportStreamError(status: StatusObject): void; } \ No newline at end of file From 858d1b66ad57fa0eed03877c8b5deae08f3fd653 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 2 Dec 2021 16:15:09 -0500 Subject: [PATCH 139/694] grpc-js-xds: Implement CSDS --- packages/grpc-js-xds/src/csds.ts | 204 ++++++++++++++++++++++++++++++ packages/grpc-js-xds/src/index.ts | 2 + 2 files changed, 206 insertions(+) create mode 100644 packages/grpc-js-xds/src/csds.ts diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts new file mode 100644 index 000000000..19f1e9660 --- /dev/null +++ b/packages/grpc-js-xds/src/csds.ts @@ -0,0 +1,204 @@ +/* + * Copyright 2021 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 { Node } from "./generated/envoy/config/core/v3/Node"; +import { ClientConfig, _envoy_service_status_v3_ClientConfig_GenericXdsConfig as GenericXdsConfig } from "./generated/envoy/service/status/v3/ClientConfig"; +import { ClientStatusDiscoveryServiceHandlers } from "./generated/envoy/service/status/v3/ClientStatusDiscoveryService"; +import { ClientStatusRequest__Output } from "./generated/envoy/service/status/v3/ClientStatusRequest"; +import { ClientStatusResponse } from "./generated/envoy/service/status/v3/ClientStatusResponse"; +import { Timestamp } from "./generated/google/protobuf/Timestamp"; +import { AdsTypeUrl, CDS_TYPE_URL_V2, CDS_TYPE_URL_V3, EDS_TYPE_URL_V2, EDS_TYPE_URL_V3, LDS_TYPE_URL_V2, LDS_TYPE_URL_V3, RDS_TYPE_URL_V2, RDS_TYPE_URL_V3 } from "./resources"; +import { HandleResponseResult } from "./xds-stream-state/xds-stream-state"; +import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, status, experimental, loadPackageDefinition } from '@grpc/grpc-js'; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType as CsdsProtoGrpcType } from "./generated/csds"; + +import registerAdminService = experimental.registerAdminService; + + +function dateToProtoTimestamp(date?: Date | null): Timestamp | null { + if (!date) { + return null; + } + const millisSinceEpoch = date.getTime(); + return { + seconds: (millisSinceEpoch / 1000) | 0, + nanos: (millisSinceEpoch % 1000) * 1_000_000 + } +} + +let clientNode: Node | null = null; + +const configStatus = { + [EDS_TYPE_URL_V2]: new Map(), + [EDS_TYPE_URL_V3]: new Map(), + [CDS_TYPE_URL_V2]: new Map(), + [CDS_TYPE_URL_V3]: new Map(), + [RDS_TYPE_URL_V2]: new Map(), + [RDS_TYPE_URL_V3]: new Map(), + [LDS_TYPE_URL_V2]: new Map(), + [LDS_TYPE_URL_V3]: new Map() +}; + +/** + * This function only accepts a v3 Node message, because we are only supporting + * v3 CSDS and it only handles v3 Nodes. If the client is actually using v2 xDS + * APIs, it should just provide the equivalent v3 Node message. + * @param node The Node message for the client that is requesting resources + */ +export function setCsdsClientNode(node: Node) { + clientNode = node; +} + +/** + * Update the config status maps from the list of names of requested resources + * for a specific type URL. These lists are the source of truth for determining + * what resources will be listed in the CSDS response. Any resource that is not + * in this list will never actually be applied anywhere. + * @param typeUrl The resource type URL + * @param names The list of resource names that are being requested + */ +export function updateRequestedNameList(typeUrl: AdsTypeUrl, names: string[]) { + const currentTime = dateToProtoTimestamp(new Date()); + const configMap = configStatus[typeUrl]; + for (const name of names) { + if (!configMap.has(name)) { + configMap.set(name, { + type_url: typeUrl, + name: name, + last_updated: currentTime, + client_status: 'REQUESTED' + }); + } + } + for (const name of configMap.keys()) { + if (!names.includes(name)) { + configMap.delete(name); + } + } +} + +/** + * Update the config status maps from the result of parsing a single ADS + * response. All resources that validated are considered "ACKED", and all + * resources that failed validation are considered "NACKED". + * @param typeUrl The type URL of resources in this response + * @param versionInfo The version info field from this response + * @param updates The lists of resources that passed and failed validation + */ +export function updateResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, updates: HandleResponseResult) { + const currentTime = dateToProtoTimestamp(new Date()); + const configMap = configStatus[typeUrl]; + for (const {name, raw} of updates.accepted) { + const mapEntry = configMap.get(name); + if (mapEntry) { + mapEntry.client_status = 'ACKED'; + mapEntry.version_info = versionInfo; + mapEntry.xds_config = raw; + mapEntry.error_state = null; + mapEntry.last_updated = currentTime; + } + } + for (const {name, error, raw} of updates.rejected) { + const mapEntry = configMap.get(name); + if (mapEntry) { + mapEntry.client_status = 'NACKED'; + mapEntry.error_state = { + failed_configuration: raw, + last_update_attempt: currentTime, + details: error, + version_info: versionInfo + }; + } + } + for (const name of updates.missing) { + const mapEntry = configMap.get(name); + if (mapEntry) { + mapEntry.client_status = 'DOES_NOT_EXIST'; + mapEntry.version_info = versionInfo; + mapEntry.xds_config = null; + mapEntry.error_state = null; + mapEntry.last_updated = currentTime; + } + } +} + +function getCurrentConfig(): ClientConfig { + const genericConfigList: GenericXdsConfig[] = []; + for (const configMap of Object.values(configStatus)) { + for (const configValue of configMap.values()) { + genericConfigList.push(configValue); + } + } + return { + node: clientNode, + generic_xds_configs: genericConfigList + }; +} + +const csdsImplementation: ClientStatusDiscoveryServiceHandlers = { + FetchClientStatus(call: ServerUnaryCall, callback: sendUnaryData) { + const request = call.request; + if (request.node_matchers !== null) { + callback({ + code: status.INVALID_ARGUMENT, + details: 'Node matchers not supported' + }); + return; + } + callback(null, { + config: [getCurrentConfig()] + }); + }, + StreamClientStatus(call: ServerDuplexStream) { + call.on('data', (request: ClientStatusRequest__Output) => { + if (request.node_matchers !== null) { + call.emit('error', { + code: status.INVALID_ARGUMENT, + details: 'Node matchers not supported' + }); + return; + } + call.write({ + config: [getCurrentConfig()] + }); + }); + call.on('end', () => { + call.end(); + }); + } +} + +const loadedProto = loadSync('envoy/service/status/v3/csds.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + ], +}); + +const csdsGrpcObject = loadPackageDefinition(loadedProto) as unknown as CsdsProtoGrpcType; +const csdsServiceDefinition = csdsGrpcObject.envoy.service.status.v3.ClientStatusDiscoveryService.service; + +export function setup() { + registerAdminService(() => csdsServiceDefinition, () => csdsImplementation); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 7d35bcd1e..ca1cf72ef 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -24,6 +24,7 @@ import * as load_balancer_weighted_target from './load-balancer-weighted-target' import * as load_balancer_xds_cluster_manager from './load-balancer-xds-cluster-manager'; import * as router_filter from './http-filter/router-filter'; import * as fault_injection_filter from './http-filter/fault-injection-filter'; +import * as csds from './csds'; /** * Register the "xds:" name scheme with the @grpc/grpc-js library. @@ -38,4 +39,5 @@ export function register() { load_balancer_xds_cluster_manager.setup(); router_filter.setup(); fault_injection_filter.setup(); + csds.setup(); } \ No newline at end of file From adc25c25f3503c2743a17738e35dc7493c952708 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 2 Dec 2021 16:21:19 -0500 Subject: [PATCH 140/694] grpc-js-xds: Expose admin service in interop client, enable CSDS test --- packages/grpc-js-xds/interop/xds-interop-client.ts | 1 + packages/grpc-js-xds/scripts/xds.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 6cd3aeb33..029525a45 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -404,6 +404,7 @@ function main() { const server = new grpc.Server(); server.addService(loadedProto.grpc.testing.LoadBalancerStatsService.service, loadBalancerStatsServiceImpl); server.addService(loadedProto.grpc.testing.XdsUpdateClientConfigureService.service, xdsUpdateClientConfigureServiceImpl); + grpc.addAdminServicesToServer(server); server.bindAsync(`0.0.0.0:${argv.stats_port}`, grpc.ServerCredentials.createInsecure(), (error, port) => { if (error) { throw error; diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index a06cde84b..7fc09e2b1 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -52,7 +52,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ - --test_case="all,timeout,circuit_breaking,fault_injection" \ + --test_case="all,timeout,circuit_breaking,fault_injection,csds" \ --project_id=grpc-testing \ --source_image=projects/grpc-testing/global/images/xds-test-server-4 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ From 29711cd526133ab3b4e4d7e728113331e920bacf Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 2 Dec 2021 18:00:07 -0500 Subject: [PATCH 141/694] grpc-js-xds: CSDS: add tracing and fix bugs --- packages/grpc-js-xds/scripts/xds.sh | 2 +- packages/grpc-js-xds/src/csds.ts | 24 ++++++++++++++++++------ packages/grpc-js-xds/src/xds-client.ts | 12 ++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 7fc09e2b1..c7a9adb6e 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -48,7 +48,7 @@ git clone -b master --single-branch --depth=1 https://github.com/grpc/grpc.git grpc/tools/run_tests/helper_scripts/prep_xds.sh -GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter \ +GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds \ GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts index 19f1e9660..40ca67f1f 100644 --- a/packages/grpc-js-xds/src/csds.ts +++ b/packages/grpc-js-xds/src/csds.ts @@ -23,12 +23,18 @@ import { ClientStatusResponse } from "./generated/envoy/service/status/v3/Client import { Timestamp } from "./generated/google/protobuf/Timestamp"; import { AdsTypeUrl, CDS_TYPE_URL_V2, CDS_TYPE_URL_V3, EDS_TYPE_URL_V2, EDS_TYPE_URL_V3, LDS_TYPE_URL_V2, LDS_TYPE_URL_V3, RDS_TYPE_URL_V2, RDS_TYPE_URL_V3 } from "./resources"; import { HandleResponseResult } from "./xds-stream-state/xds-stream-state"; -import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, status, experimental, loadPackageDefinition } from '@grpc/grpc-js'; +import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, status, experimental, loadPackageDefinition, logVerbosity } from '@grpc/grpc-js'; import { loadSync } from "@grpc/proto-loader"; import { ProtoGrpcType as CsdsProtoGrpcType } from "./generated/csds"; import registerAdminService = experimental.registerAdminService; +const TRACER_NAME = 'csds'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + function dateToProtoTimestamp(date?: Date | null): Timestamp | null { if (!date) { @@ -72,7 +78,8 @@ export function setCsdsClientNode(node: Node) { * @param typeUrl The resource type URL * @param names The list of resource names that are being requested */ -export function updateRequestedNameList(typeUrl: AdsTypeUrl, names: string[]) { +export function updateCsdsRequestedNameList(typeUrl: AdsTypeUrl, names: string[]) { + trace('Update type URL ' + typeUrl + ' with names [' + names + ']'); const currentTime = dateToProtoTimestamp(new Date()); const configMap = configStatus[typeUrl]; for (const name of names) { @@ -100,12 +107,13 @@ export function updateRequestedNameList(typeUrl: AdsTypeUrl, names: string[]) { * @param versionInfo The version info field from this response * @param updates The lists of resources that passed and failed validation */ -export function updateResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, updates: HandleResponseResult) { +export function updateCsdsResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, updates: HandleResponseResult) { const currentTime = dateToProtoTimestamp(new Date()); const configMap = configStatus[typeUrl]; for (const {name, raw} of updates.accepted) { const mapEntry = configMap.get(name); if (mapEntry) { + trace('Updated ' + typeUrl + ' resource ' + name + ' to state ACKED'); mapEntry.client_status = 'ACKED'; mapEntry.version_info = versionInfo; mapEntry.xds_config = raw; @@ -116,6 +124,7 @@ export function updateResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, for (const {name, error, raw} of updates.rejected) { const mapEntry = configMap.get(name); if (mapEntry) { + trace('Updated ' + typeUrl + ' resource ' + name + ' to state NACKED'); mapEntry.client_status = 'NACKED'; mapEntry.error_state = { failed_configuration: raw, @@ -128,6 +137,7 @@ export function updateResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, for (const name of updates.missing) { const mapEntry = configMap.get(name); if (mapEntry) { + trace('Updated ' + typeUrl + ' resource ' + name + ' to state DOES_NOT_EXIST'); mapEntry.client_status = 'DOES_NOT_EXIST'; mapEntry.version_info = versionInfo; mapEntry.xds_config = null; @@ -144,16 +154,18 @@ function getCurrentConfig(): ClientConfig { genericConfigList.push(configValue); } } - return { + const config = { node: clientNode, generic_xds_configs: genericConfigList }; + trace('Sending curent config ' + JSON.stringify(config, undefined, 2)); + return config; } const csdsImplementation: ClientStatusDiscoveryServiceHandlers = { FetchClientStatus(call: ServerUnaryCall, callback: sendUnaryData) { const request = call.request; - if (request.node_matchers !== null) { + if (request.node_matchers.length > 0) { callback({ code: status.INVALID_ARGUMENT, details: 'Node matchers not supported' @@ -166,7 +178,7 @@ const csdsImplementation: ClientStatusDiscoveryServiceHandlers = { }, StreamClientStatus(call: ServerDuplexStream) { call.on('data', (request: ClientStatusRequest__Output) => { - if (request.node_matchers !== null) { + if (request.node_matchers.length > 0) { call.emit('error', { code: status.INVALID_ARGUMENT, details: 'Node matchers not supported' diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 6c8bcb67f..75a567a54 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -51,6 +51,7 @@ import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; import { Duration } from './generated/google/protobuf/Duration'; import { AdsOutputType, AdsTypeUrl, CDS_TYPE_URL_V2, CDS_TYPE_URL_V3, decodeSingleResource, EDS_TYPE_URL_V2, EDS_TYPE_URL_V3, LDS_TYPE_URL_V2, LDS_TYPE_URL_V3, RDS_TYPE_URL_V2, RDS_TYPE_URL_V3 } from './resources'; +import { setCsdsClientNode, updateCsdsRequestedNameList, updateCsdsResourceResponse } from './csds'; const TRACER_NAME = 'xds_client'; @@ -384,6 +385,7 @@ export class XdsClient { ...nodeV3, client_features: ['envoy.lrs.supports_send_all_clusters'], }; + setCsdsClientNode(this.adsNodeV3); if (this.apiVersion === XdsApiVersion.V2) { trace('ADS Node: ' + JSON.stringify(this.adsNodeV2, undefined, 2)); trace('LRS Node: ' + JSON.stringify(this.lrsNodeV2, undefined, 2)); @@ -514,12 +516,14 @@ export class XdsClient { } catch (e) { trace('Nacking message with protobuf parsing error: ' + e.message); this.nack(message.type_url, e.message); + return; } if (handleResponseResult === null) { // Null handleResponseResult means that the type_url was unrecognized trace('Nacking message with unknown type URL ' + message.type_url); this.nack(message.type_url, `Unknown type_url ${message.type_url}`); } else { + updateCsdsResourceResponse(message.type_url as AdsTypeUrl, message.version_info, handleResponseResult.result); if (handleResponseResult.result.rejected.length > 0) { // rejected.length > 0 means that at least one message validation failed const errorString = `${handleResponseResult.serviceKind.toUpperCase()} Error: ${handleResponseResult.result.rejected[0].error}`; @@ -754,8 +758,16 @@ export class XdsClient { } this.maybeStartAdsStream(); this.maybeStartLrsStream(); + if (!this.adsCallV2 && !this.adsCallV3) { + /* If the stream is not set up yet at this point, shortcut the rest + * becuase nothing will actually be sent. This would mainly happen if + * the bootstrap file has not been read yet. In that case, the output + * of getTypeUrl is garbage and everything after that is invalid. */ + return; + } trace('Sending update for ' + serviceKind + ' with names ' + this.adsState[serviceKind].getResourceNames()); const typeUrl = this.getTypeUrl(serviceKind); + updateCsdsRequestedNameList(typeUrl, this.adsState[serviceKind].getResourceNames()); this.maybeSendAdsMessage(typeUrl, this.adsState[serviceKind].getResourceNames(), this.adsState[serviceKind].nonce, this.adsState[serviceKind].versionInfo); } From efa6ea1d3e5575ad4717a337dd188f7154c7c815 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 6 Dec 2021 11:38:00 -0500 Subject: [PATCH 142/694] grpc-js-xds: Include additional paths when loading csds protos --- packages/grpc-js-xds/src/csds.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts index 40ca67f1f..6454e1ad1 100644 --- a/packages/grpc-js-xds/src/csds.ts +++ b/packages/grpc-js-xds/src/csds.ts @@ -205,6 +205,8 @@ const loadedProto = loadSync('envoy/service/status/v3/csds.proto', { // Paths are relative to src/build __dirname + '/../../deps/envoy-api/', __dirname + '/../../deps/xds/', + __dirname + '/../../deps/protoc-gen-validate/', + __dirname + '/../../deps/googleapis/' ], }); From 575c2004f374817c5272f02889b927d4d7221eca Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 6 Dec 2021 12:02:52 -0500 Subject: [PATCH 143/694] Skip some tests to make the Linux test job green --- packages/grpc-js/test/test-server.ts | 6 +++++- test/gulpfile.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index a5f7ec80b..2513b1fe2 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -848,7 +848,11 @@ describe('Compressed requests', () => { }); }); - it('Should not compress requests when the NoCompress write flag is used', done => { + /* As of Node 16, Writable and Duplex streams validate the encoding + * argument to write, and the flags values we are passing there are not + * valid. We don't currently have an alternative way to pass that flag + * down, so for now this feature is not supported. */ + it.skip('Should not compress requests when the NoCompress write flag is used', done => { const bidiStream = client.bidiStream(); let timesRequested = 0; let timesResponded = 0; diff --git a/test/gulpfile.ts b/test/gulpfile.ts index 2024f02cb..6d43d4472 100644 --- a/test/gulpfile.ts +++ b/test/gulpfile.ts @@ -25,6 +25,10 @@ import * as semver from 'semver'; const testDir = __dirname; const apiTestDir = path.resolve(testDir, 'api'); +/* The native library has some misbehavior in specific tests when running in + * Node 14 and above. */ +const NATIVE_SUPPORT_RANGE = '<14'; + const runInstall = () => { return execa('npm', ['install'], {cwd: testDir, stdio: 'inherit'}); }; @@ -51,11 +55,11 @@ const testJsClientNativeServer = runTestsWithFixture('native', 'js'); const testNativeClientJsServer = runTestsWithFixture('js', 'native'); const testJsClientJsServer = runTestsWithFixture('js', 'js'); -const test = gulp.series( +const test = semver.satisfies(process.version, NATIVE_SUPPORT_RANGE)? gulp.series( testJsClientJsServer, testJsClientNativeServer, testNativeClientJsServer - ); + ) : testJsClientJsServer; export { install, From 40c2f61eba518970210ec857cb067f069b907bf2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Dec 2021 10:27:21 -0500 Subject: [PATCH 144/694] Fix server decompression sequencing, add tracing --- packages/grpc-js/src/server-call.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 709d20d41..78cab5a9a 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -737,9 +737,24 @@ export class Http2ServerCallStream< ) { const decoder = new StreamDecoder(); + let readsDone = false; + + let pendingMessageProcessing = false; + + let pushedEnd = false; + + const maybePushEnd = () => { + if (!pushedEnd && readsDone && !pendingMessageProcessing) { + pushedEnd = true; + this.pushOrBufferMessage(readable, null); + } + } + this.stream.on('data', async (data: Buffer) => { const messages = decoder.write(data); + pendingMessageProcessing = true; + this.stream.pause(); for (const message of messages) { if ( this.maxReceiveMessageSize !== -1 && @@ -763,10 +778,14 @@ export class Http2ServerCallStream< this.pushOrBufferMessage(readable, decompressedMessage); } + pendingMessageProcessing = false; + this.stream.resume(); + maybePushEnd(); }); this.stream.once('end', () => { - this.pushOrBufferMessage(readable, null); + readsDone = true; + maybePushEnd(); }); } @@ -810,6 +829,7 @@ export class Http2ServerCallStream< messageBytes: Buffer | null ) { if (messageBytes === null) { + trace('Received end of stream'); if (this.canPush) { readable.push(null); } else { @@ -819,6 +839,8 @@ export class Http2ServerCallStream< return; } + trace('Received message of length ' + messageBytes.length); + this.isPushPending = true; try { From c52cb842af6c4d734686524cfbb30091042cbd3c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Dec 2021 11:07:29 -0500 Subject: [PATCH 145/694] Add detailed assertion output for some resolver tests, skip IPv4+IPv6 test --- packages/grpc-js/test/test-resolver.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index 7addfa232..eb4290fc8 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -24,7 +24,7 @@ import * as resolver_uds from '../src/resolver-uds'; import * as resolver_ip from '../src/resolver-ip'; import { ServiceConfig } from '../src/service-config'; import { StatusObject } from '../src/call-stream'; -import { SubchannelAddress, isTcpSubchannelAddress } from "../src/subchannel-address"; +import { SubchannelAddress, isTcpSubchannelAddress, subchannelAddressToString } from "../src/subchannel-address"; import { parseUri, GrpcUri } from '../src/uri-parser'; describe('Name Resolver', () => { @@ -223,7 +223,7 @@ describe('Name Resolver', () => { isTcpSubchannelAddress(addr) && addr.host === '127.0.0.1' && addr.port === 443 - ) + ), `None of [${addressList.map(addr => subchannelAddressToString(addr))}] matched '127.0.0.1:443'` ); done(); }, @@ -263,7 +263,10 @@ describe('Name Resolver', () => { const resolver = resolverManager.createResolver(target, listener, {}); resolver.updateResolution(); }); - it('Should resolve a DNS name to IPv4 and IPv6 addresses', done => { + /* This DNS name resolves to only the IPv4 address on Windows, and only the + * IPv6 address on Mac. There is no result that we can consistently test + * for here. */ + it.skip('Should resolve a DNS name to IPv4 and IPv6 addresses', done => { const target = resolverManager.mapUriDefaultScheme(parseUri('loopback46.unittest.grpc.io')!)!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( @@ -279,7 +282,7 @@ describe('Name Resolver', () => { isTcpSubchannelAddress(addr) && addr.host === '127.0.0.1' && addr.port === 443 - ) + ), `None of [${addressList.map(addr => subchannelAddressToString(addr))}] matched '127.0.0.1:443'` ); /* TODO(murgatroid99): check for IPv6 result, once we can get that * consistently */ From 57cbf5c8861a91c3229d88e32fc0c82992338246 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Dec 2021 13:44:46 -0500 Subject: [PATCH 146/694] Increase TLS vesion on Windows tests to fix nvm download --- run-tests.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.bat b/run-tests.bat index 1eebbd4a6..ae9ca1922 100644 --- a/run-tests.bat +++ b/run-tests.bat @@ -21,7 +21,7 @@ powershell -c "[System.Environment]::OSVersion" powershell -c "Get-WmiObject -Class Win32_ComputerSystem" powershell -c "(Get-WmiObject -Class Win32_ComputerSystem).SystemType" -powershell -c "& { iwr https://raw.githubusercontent.com/grumpycoders/nvm-ps/master/nvm.ps1 | iex }" +powershell -c "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; & { iwr https://raw.githubusercontent.com/grumpycoders/nvm-ps/master/nvm.ps1 | iex }" SET PATH=%APPDATA%\nvm-ps;%APPDATA%\nvm-ps\nodejs;%PATH% SET JOBS=8 From 359014eeedc3de46ae7ac73e6a88a8068d4afcf0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Dec 2021 14:46:33 -0500 Subject: [PATCH 147/694] Drop Node 8 tests on Windows --- run-tests.bat | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/run-tests.bat b/run-tests.bat index ae9ca1922..ce4480e44 100644 --- a/run-tests.bat +++ b/run-tests.bat @@ -28,8 +28,8 @@ SET JOBS=8 call nvm version -call nvm install 8 -call nvm use 8 +call nvm install 10 +call nvm use 10 SET npm_config_fetch_retries=5 @@ -38,7 +38,7 @@ call npm install || goto :error SET JUNIT_REPORT_STACK=1 SET FAILED=0 -for %%v in (8 10 12) do ( +for %%v in (10 12) do ( call nvm install %%v call nvm use %%v if "%%v"=="4" ( From 20dbaa8e27faad9e41d5ce0bafa40f082a37117c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Dec 2021 14:54:56 -0500 Subject: [PATCH 148/694] Make npm clean scripts platform-agnostic --- packages/grpc-js/package.json | 2 +- packages/proto-loader/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 69d565ad3..ad12fb82e 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -44,7 +44,7 @@ ], "scripts": { "build": "npm run compile", - "clean": "node -e 'require(\"rimraf\")(\"./build\", () => {})'", + "clean": "rimraf ./build", "compile": "tsc -p .", "format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts", "lint": "npm run check", diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index cd27743a0..db6e4755e 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -14,7 +14,7 @@ "typings": "build/src/index.d.ts", "scripts": { "build": "npm run compile", - "clean": "node -e 'require(\"rimraf\")(\"./build\", () => {})'", + "clean": "rimraf ./build", "compile": "tsc -p .", "format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts", "lint": "tslint -c node_modules/google-ts-style/tslint.json -p . -t codeFrame --type-check", From d5ce78434845442931bff2baedccc91730e0d3b1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Dec 2021 14:56:52 -0500 Subject: [PATCH 149/694] Update submodules in Windows test script --- run-tests.bat | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run-tests.bat b/run-tests.bat index ce4480e44..611f28ca4 100644 --- a/run-tests.bat +++ b/run-tests.bat @@ -31,6 +31,8 @@ call nvm version call nvm install 10 call nvm use 10 +git submodule update --init --recursive + SET npm_config_fetch_retries=5 call npm install || goto :error From e41b99dffc4bd4a676b7c6932ea46695c511d629 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Dec 2021 15:23:06 -0500 Subject: [PATCH 150/694] Fix a couple of issues with tests on Windows --- packages/grpc-js/test/test-client.ts | 3 ++- test/api/connectivity_test.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/test/test-client.ts b/packages/grpc-js/test/test-client.ts index 51af05416..4a02ff3de 100644 --- a/packages/grpc-js/test/test-client.ts +++ b/packages/grpc-js/test/test-client.ts @@ -79,7 +79,8 @@ describe('Client without a server', () => { after(() => { client.close(); }); - it('should fail multiple calls to the nonexistent server', done => { + it('should fail multiple calls to the nonexistent server', function(done) { + this.timeout(5000); // Regression test for https://github.com/grpc/grpc-node/issues/1411 client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { assert(error); diff --git a/test/api/connectivity_test.js b/test/api/connectivity_test.js index b5d31943d..64764d7d4 100644 --- a/test/api/connectivity_test.js +++ b/test/api/connectivity_test.js @@ -61,7 +61,9 @@ const serviceImpl = { describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { it('client should not wait for ready by default', function(done) { this.timeout(15000); - const disconnectedClient = new TestServiceClient('foo.test.google.com:50051', clientGrpc.credentials.createInsecure()); + /* TCP port 47 is reserved according to + * https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers */ + const disconnectedClient = new TestServiceClient('localhost:47', clientGrpc.credentials.createInsecure()); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 10); disconnectedClient.unary({}, {deadline: deadline}, (error, value) =>{ @@ -72,7 +74,7 @@ describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, functio }); it('client should wait for a connection with waitForReady on', function(done) { this.timeout(15000); - const disconnectedClient = new TestServiceClient('foo.test.google.com:50051', clientGrpc.credentials.createInsecure()); + const disconnectedClient = new TestServiceClient('localhost:47', clientGrpc.credentials.createInsecure()); const metadata = new clientGrpc.Metadata({waitForReady: true}); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 10); From f2e698a810b1924cb8b155ae3fcd5827eda591b6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Dec 2021 10:47:01 -0500 Subject: [PATCH 151/694] Skip clean step in Windows test runs --- run-tests.bat | 1 - 1 file changed, 1 deletion(-) diff --git a/run-tests.bat b/run-tests.bat index 611f28ca4..3b0aa07d0 100644 --- a/run-tests.bat +++ b/run-tests.bat @@ -55,7 +55,6 @@ for %%v in (10 12) do ( node -e "process.exit(process.version.startsWith('v%%v') ? 0 : -1)" || goto :error - call .\node_modules\.bin\gulp cleanAll || SET FAILED=1 call .\node_modules\.bin\gulp setup || SET FAILED=1 call .\node_modules\.bin\gulp test || SET FAILED=1 cmd.exe /c "SET GRPC_DNS_RESOLVER=ares& call .\node_modules\.bin\gulp nativeTestOnly" || SET FAILED=1 From f706d524e7d4de8f305c3d942d1a74830e831f9a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Dec 2021 10:47:48 -0500 Subject: [PATCH 152/694] Ignore trailing whitespace in proto-loader golden file test diff --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index db6e4755e..093bfa5a7 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -25,7 +25,7 @@ "pretest": "npm run compile", "posttest": "npm run check", "generate-golden": "node ./build/bin/proto-loader-gen-types.js --keepCase --longs=String --enums=String --defaults --oneofs --json --includeComments -I deps/gapic-showcase/schema/ deps/googleapis/ -O ./golden-generated --grpcLib @grpc/grpc-js google/showcase/v1beta1/echo.proto", - "validate-golden": "rm -rf ./golden-generated-old && mv ./golden-generated/ ./golden-generated-old && npm run generate-golden && diff -r ./golden-generated ./golden-generated-old" + "validate-golden": "rm -rf ./golden-generated-old && mv ./golden-generated/ ./golden-generated-old && npm run generate-golden && diff -rZ ./golden-generated ./golden-generated-old" }, "repository": { "type": "git", From ba375e73716d921eef9933eba76f3e077ae51fb5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Dec 2021 10:48:38 -0500 Subject: [PATCH 153/694] Skip a test because it behaves weirdly on Mac --- packages/grpc-js/test/test-resolver.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index eb4290fc8..354413ea6 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -207,7 +207,15 @@ describe('Name Resolver', () => { const resolver = resolverManager.createResolver(target, listener, {}); resolver.updateResolution(); }); - it('Should resolve a name with multiple dots', done => { + /* The DNS entry for loopback4.unittest.grpc.io only has a single A record + * with the address 127.0.0.1, but the Mac DNS resolver appears to use + * NAT64 to create an IPv6 address in that case, so it instead returns + * 64:ff9b::7f00:1. Handling that kind of translation is outside of the + * scope of this test, so we are skipping it. The test primarily exists + * as a regression test for https://github.com/grpc/grpc-node/issues/1044, + * and the test 'Should resolve gRPC interop servers' tests the same thing. + */ + it.skip('Should resolve a name with multiple dots', done => { const target = resolverManager.mapUriDefaultScheme(parseUri('loopback4.unittest.grpc.io')!)!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( @@ -317,6 +325,10 @@ describe('Name Resolver', () => { const resolver = resolverManager.createResolver(target, listener, {}); resolver.updateResolution(); }); + /* This test also serves as a regression test for + * https://github.com/grpc/grpc-node/issues/1044, specifically handling + * hyphens and multiple periods in a DNS name. It should not be skipped + * unless there is another test for the same issue. */ it('Should resolve gRPC interop servers', done => { let completeCount = 0; const target1 = resolverManager.mapUriDefaultScheme(parseUri('grpc-test.sandbox.googleapis.com')!)!; From 8d771044e75e5d6a659458297bc9229d7e1b2846 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Dec 2021 11:14:53 -0500 Subject: [PATCH 154/694] Make diff command work on Mac, make file comment use consistent directory separator --- packages/proto-loader/bin/proto-loader-gen-types.ts | 6 +++--- packages/proto-loader/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 3ecf6dcc4..b416d3382 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -379,7 +379,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob let usesLong: boolean = false; let seenDeps: Set = new Set(); const childTypes = getChildMessagesAndEnums(messageType); - formatter.writeLine(`// Original file: ${messageType.filename}`); + formatter.writeLine(`// Original file: ${messageType.filename?.replace(/\\/g, '/')}`); formatter.writeLine(''); messageType.fieldsArray.sort((fieldA, fieldB) => fieldA.id - fieldB.id); for (const field of messageType.fieldsArray) { @@ -437,7 +437,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob } function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum, options: GeneratorOptions, nameOverride?: string) { - formatter.writeLine(`// Original file: ${enumType.filename}`); + formatter.writeLine(`// Original file: ${enumType.filename?.replace(/\\/g, '/')}`); formatter.writeLine(''); if (options.includeComments) { formatComment(formatter, enumType.comment); @@ -590,7 +590,7 @@ function generateServiceDefinitionInterface(formatter: TextFormatter, serviceTyp } function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { - formatter.writeLine(`// Original file: ${serviceType.filename}`); + formatter.writeLine(`// Original file: ${serviceType.filename?.replace(/\\/g, '/')}`); formatter.writeLine(''); const grpcImportPath = options.grpcLib.startsWith('.') ? getPathToRoot(serviceType) + options.grpcLib : options.grpcLib; formatter.writeLine(`import type * as grpc from '${grpcImportPath}'`); diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 093bfa5a7..6db5cb721 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -25,7 +25,7 @@ "pretest": "npm run compile", "posttest": "npm run check", "generate-golden": "node ./build/bin/proto-loader-gen-types.js --keepCase --longs=String --enums=String --defaults --oneofs --json --includeComments -I deps/gapic-showcase/schema/ deps/googleapis/ -O ./golden-generated --grpcLib @grpc/grpc-js google/showcase/v1beta1/echo.proto", - "validate-golden": "rm -rf ./golden-generated-old && mv ./golden-generated/ ./golden-generated-old && npm run generate-golden && diff -rZ ./golden-generated ./golden-generated-old" + "validate-golden": "rm -rf ./golden-generated-old && mv ./golden-generated/ ./golden-generated-old && npm run generate-golden && diff -rb ./golden-generated ./golden-generated-old" }, "repository": { "type": "git", From 2af9a05ee4eff892f4cd6b7a8e864c7e0115788c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Dec 2021 13:17:01 -0500 Subject: [PATCH 155/694] Ensure consistent null in missing file names --- packages/proto-loader/bin/proto-loader-gen-types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index b416d3382..b0a9819b4 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -379,7 +379,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob let usesLong: boolean = false; let seenDeps: Set = new Set(); const childTypes = getChildMessagesAndEnums(messageType); - formatter.writeLine(`// Original file: ${messageType.filename?.replace(/\\/g, '/')}`); + formatter.writeLine(`// Original file: ${(messageType.filename ?? 'null')?.replace(/\\/g, '/')}`); formatter.writeLine(''); messageType.fieldsArray.sort((fieldA, fieldB) => fieldA.id - fieldB.id); for (const field of messageType.fieldsArray) { @@ -437,7 +437,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob } function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum, options: GeneratorOptions, nameOverride?: string) { - formatter.writeLine(`// Original file: ${enumType.filename?.replace(/\\/g, '/')}`); + formatter.writeLine(`// Original file: ${(enumType.filename ?? 'null')?.replace(/\\/g, '/')}`); formatter.writeLine(''); if (options.includeComments) { formatComment(formatter, enumType.comment); @@ -590,7 +590,7 @@ function generateServiceDefinitionInterface(formatter: TextFormatter, serviceTyp } function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { - formatter.writeLine(`// Original file: ${serviceType.filename?.replace(/\\/g, '/')}`); + formatter.writeLine(`// Original file: ${(serviceType.filename ?? 'null')?.replace(/\\/g, '/')}`); formatter.writeLine(''); const grpcImportPath = options.grpcLib.startsWith('.') ? getPathToRoot(serviceType) + options.grpcLib : options.grpcLib; formatter.writeLine(`import type * as grpc from '${grpcImportPath}'`); From 7cccc392181050f1e7ed7334f148d35dbe6b7b5f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Dec 2021 16:14:52 -0500 Subject: [PATCH 156/694] grpc-js: Preserve order of metadata and messages with async interceptors --- packages/grpc-js/src/call-stream.ts | 35 +++++++++++++--- packages/grpc-js/src/client-interceptors.ts | 44 ++++++++++++++++++--- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 70a9ac6ee..915d812fc 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -149,6 +149,9 @@ export function isInterceptingListener( } export class InterceptingListenerImpl implements InterceptingListener { + private processingMetadata = false; + private hasPendingMessage = false; + private pendingMessage: any; private processingMessage = false; private pendingStatus: StatusObject | null = null; constructor( @@ -156,9 +159,27 @@ export class InterceptingListenerImpl implements InterceptingListener { private nextListener: InterceptingListener ) {} + private processPendingMessage() { + if (this.hasPendingMessage) { + this.nextListener.onReceiveMessage(this.pendingMessage); + this.pendingMessage = null; + this.hasPendingMessage = false; + } + } + + private processPendingStatus() { + if (this.pendingStatus) { + this.nextListener.onReceiveStatus(this.pendingStatus); + } + } + onReceiveMetadata(metadata: Metadata): void { + this.processingMetadata = true; this.listener.onReceiveMetadata(metadata, (metadata) => { + this.processingMetadata = false; this.nextListener.onReceiveMetadata(metadata); + this.processPendingMessage(); + this.processPendingStatus(); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -168,15 +189,18 @@ export class InterceptingListenerImpl implements InterceptingListener { this.processingMessage = true; this.listener.onReceiveMessage(message, (msg) => { this.processingMessage = false; - this.nextListener.onReceiveMessage(msg); - if (this.pendingStatus) { - this.nextListener.onReceiveStatus(this.pendingStatus); + if (this.processingMetadata) { + this.pendingMessage = msg; + this.hasPendingMessage = true; + } else { + this.nextListener.onReceiveMessage(msg); + this.processPendingStatus(); } }); } onReceiveStatus(status: StatusObject): void { this.listener.onReceiveStatus(status, (processedStatus) => { - if (this.processingMessage) { + if (this.processingMetadata || this.processingMessage) { this.pendingStatus = processedStatus; } else { this.nextListener.onReceiveStatus(processedStatus); @@ -283,7 +307,7 @@ export class Http2CallStream implements Call { private outputStatus() { /* Precondition: this.finalStatus !== null */ - if (!this.statusOutput) { + if (this.listener && !this.statusOutput) { this.statusOutput = true; const filteredStatus = this.filterStack.receiveTrailers( this.finalStatus! @@ -692,6 +716,7 @@ export class Http2CallStream implements Call { this.trace('Sending metadata'); this.listener = listener; this.channel._startCallStream(this, metadata); + this.maybeOutputStatus(); } private destroyHttp2Stream() { diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index 5dfbeba14..ddb296ff1 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -208,8 +208,18 @@ export class InterceptingCall implements InterceptingCallInterface { */ private requester: FullRequester; /** - * Indicates that a message has been passed to the listener's onReceiveMessage - * method it has not been passed to the corresponding next callback + * Indicates that metadata has been passed to the requester's start + * method but it has not been passed to the corresponding next callback + */ + private processingMetadata = false; + /** + * Message context for a pending message that is waiting for + */ + private pendingMessageContext: MessageContext | null = null; + private pendingMessage: any; + /** + * Indicates that a message has been passed to the requester's sendMessage + * method but it has not been passed to the corresponding next callback */ private processingMessage = false; /** @@ -242,6 +252,21 @@ export class InterceptingCall implements InterceptingCallInterface { getPeer() { return this.nextCall.getPeer(); } + + private processPendingMessage() { + if (this.pendingMessageContext) { + this.nextCall.sendMessageWithContext(this.pendingMessageContext, this.pendingMessage); + this.pendingMessageContext = null; + this.pendingMessage = null; + } + } + + private processPendingHalfClose() { + if (this.pendingHalfClose) { + this.nextCall.halfClose(); + } + } + start( metadata: Metadata, interceptingListener?: Partial @@ -257,7 +282,9 @@ export class InterceptingCall implements InterceptingCallInterface { interceptingListener?.onReceiveStatus?.bind(interceptingListener) ?? ((status) => {}), }; + this.processingMetadata = true; this.requester.start(metadata, fullInterceptingListener, (md, listener) => { + this.processingMetadata = false; let finalInterceptingListener: InterceptingListener; if (isInterceptingListener(listener)) { finalInterceptingListener = listener; @@ -276,6 +303,8 @@ export class InterceptingCall implements InterceptingCallInterface { ); } this.nextCall.start(md, finalInterceptingListener); + this.processPendingMessage(); + this.processPendingHalfClose(); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -283,9 +312,12 @@ export class InterceptingCall implements InterceptingCallInterface { this.processingMessage = true; this.requester.sendMessage(message, (finalMessage) => { this.processingMessage = false; - this.nextCall.sendMessageWithContext(context, finalMessage); - if (this.pendingHalfClose) { - this.nextCall.halfClose(); + if (this.processingMetadata) { + this.pendingMessageContext = context; + this.pendingMessage = message; + } else { + this.nextCall.sendMessageWithContext(context, finalMessage); + this.processPendingHalfClose(); } }); } @@ -298,7 +330,7 @@ export class InterceptingCall implements InterceptingCallInterface { } halfClose(): void { this.requester.halfClose(() => { - if (this.processingMessage) { + if (this.processingMetadata || this.processingMessage) { this.pendingHalfClose = true; } else { this.nextCall.halfClose(); From 86f3ffd96ce7e656acd004caf10c1c6c98ceb84c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Dec 2021 16:31:29 -0500 Subject: [PATCH 157/694] grpc-js: Update version to 1.4.5 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index e14ffa0eb..02400ca90 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.4.4", + "version": "1.4.5", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From b12330abfd8ff9207a332e8a47e05703c26697f5 Mon Sep 17 00:00:00 2001 From: Cosmin-Catalin Crisan Date: Fri, 10 Dec 2021 20:20:11 +0200 Subject: [PATCH 158/694] grpc-js: Send backoffOptions to BackoffTimeout --- packages/grpc-js/src/resolving-load-balancer.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 235748f70..5bc861283 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -26,7 +26,7 @@ import { ConnectivityState } from './connectivity-state'; import { ConfigSelector, createResolver, Resolver } from './resolver'; import { ServiceError } from './call'; import { Picker, UnavailablePicker, QueuePicker } from './picker'; -import { BackoffTimeout } from './backoff-timeout'; +import { BackoffOptions, BackoffTimeout } from './backoff-timeout'; import { Status } from './constants'; import { StatusObject } from './call-stream'; import { Metadata } from './metadata'; @@ -248,7 +248,10 @@ export class ResolvingLoadBalancer implements LoadBalancer { }, channelOptions ); - + const backoffOptions: BackoffOptions = { + initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'], + maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'], + }; this.backoffTimeout = new BackoffTimeout(() => { if (this.continueResolving) { this.updateResolution(); @@ -256,7 +259,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { } else { this.updateState(this.latestChildState, this.latestChildPicker); } - }); + }, backoffOptions); this.backoffTimeout.unref(); } From 8e53f034d0ea6e1226b21f29d5249ecf60632b65 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 13 Dec 2021 10:05:37 -0500 Subject: [PATCH 159/694] grpc-js: Add credentials.createFromSecureContext --- packages/grpc-js/src/channel-credentials.ts | 57 +++++++++++---------- packages/grpc-js/src/index.ts | 1 + 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index 02c9c309b..94b66c1bd 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -15,7 +15,7 @@ * */ -import { ConnectionOptions, createSecureContext, PeerCertificate } from 'tls'; +import { ConnectionOptions, createSecureContext, PeerCertificate, SecureContext } from 'tls'; import { CallCredentials } from './call-credentials'; import { CIPHER_SUITES, getDefaultRootsData } from './tls-helpers'; @@ -110,6 +110,7 @@ export abstract class ChannelCredentials { * @param rootCerts The root certificate data. * @param privateKey The client certificate private key, if available. * @param certChain The client certificate key chain, if available. + * @param verifyOptions Additional options to modify certificate verification */ static createSsl( rootCerts?: Buffer | null, @@ -130,14 +131,35 @@ export abstract class ChannelCredentials { 'Certificate chain must be given with accompanying private key' ); } + const secureContext = createSecureContext({ + ca: rootCerts ?? getDefaultRootsData() ?? undefined, + key: privateKey ?? undefined, + cert: certChain ?? undefined, + ciphers: CIPHER_SUITES, + }); return new SecureChannelCredentialsImpl( - rootCerts || getDefaultRootsData(), - privateKey || null, - certChain || null, - verifyOptions || {} + secureContext, + verifyOptions ?? {} ); } + /** + * Return a new ChannelCredentials instance with credentials created using + * the provided secureContext. The resulting instances can be used to + * construct a Channel that communicates over TLS. gRPC will not override + * anything in the provided secureContext, so the environment variables + * GRPC_SSL_CIPHER_SUITES and GRPC_DEFAULT_SSL_ROOTS_FILE_PATH will + * not be applied. + * @param secureContext The return value of tls.createSecureContext() + * @param verifyOptions Additional options to modify certificate verification + */ + static createFromSecureContext(secureContext: SecureContext, verifyOptions?: VerifyOptions): ChannelCredentials { + return new SecureChannelCredentialsImpl( + secureContext, + verifyOptions ?? {} + ) + } + /** * Return a new ChannelCredentials instance with no credentials. */ @@ -170,18 +192,10 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { connectionOptions: ConnectionOptions; constructor( - private rootCerts: Buffer | null, - private privateKey: Buffer | null, - private certChain: Buffer | null, + private secureContext: SecureContext, private verifyOptions: VerifyOptions ) { super(); - const secureContext = createSecureContext({ - ca: rootCerts || undefined, - key: privateKey || undefined, - cert: certChain || undefined, - ciphers: CIPHER_SUITES, - }); this.connectionOptions = { secureContext }; @@ -210,19 +224,10 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { return true; } if (other instanceof SecureChannelCredentialsImpl) { - if (!bufferOrNullEqual(this.rootCerts, other.rootCerts)) { - return false; - } - if (!bufferOrNullEqual(this.privateKey, other.privateKey)) { - return false; - } - if (!bufferOrNullEqual(this.certChain, other.certChain)) { - return false; - } return ( - this.verifyOptions.checkServerIdentity === - other.verifyOptions.checkServerIdentity - ); + this.secureContext === other.secureContext && + this.verifyOptions.checkServerIdentity === other.verifyOptions.checkServerIdentity + ); } else { return false; } diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index c4c774d93..4bf1d9363 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -109,6 +109,7 @@ export const credentials = { // from channel-credentials.ts createInsecure: ChannelCredentials.createInsecure, createSsl: ChannelCredentials.createSsl, + createFromSecureContext: ChannelCredentials.createFromSecureContext, // from call-credentials.ts createFromMetadataGenerator: CallCredentials.createFromMetadataGenerator, From 33c5abd16353eba7c2347bb8368a941df5a37c49 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 13 Dec 2021 14:18:12 -0500 Subject: [PATCH 160/694] grpc-js: Clean up some dependencies --- packages/grpc-js/package.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index ad12fb82e..854000a3d 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -21,7 +21,7 @@ "@types/mocha": "^5.2.6", "@types/ncp": "^2.0.1", "@types/pify": "^3.0.2", - "@types/yargs": "^15.0.5", + "@types/semver": "^7.3.9", "clang-format": "^1.0.55", "execa": "^2.0.3", "gts": "^2.0.0", @@ -33,9 +33,9 @@ "ncp": "^2.0.0", "pify": "^4.0.1", "rimraf": "^3.0.2", + "semver": "^7.3.5", "ts-node": "^8.3.0", - "typescript": "^3.7.2", - "yargs": "^15.4.1" + "typescript": "^3.7.2" }, "contributors": [ { @@ -59,8 +59,7 @@ }, "dependencies": { "@grpc/proto-loader": "^0.6.4", - "@types/node": ">=12.12.47", - "@types/semver": "^7.3.9" + "@types/node": ">=12.12.47" }, "files": [ "src/**/*.ts", From ac893ca89fc1be95f3e62e9923a4c2ba88ddc0d3 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Wed, 29 Dec 2021 11:58:32 -0800 Subject: [PATCH 161/694] Use xds-test-server-5 as interop server image --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index c7a9adb6e..2df2ae42d 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -54,7 +54,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh python3 grpc/tools/run_tests/run_xds_tests.py \ --test_case="all,timeout,circuit_breaking,fault_injection,csds" \ --project_id=grpc-testing \ - --source_image=projects/grpc-testing/global/images/xds-test-server-4 \ + --source_image=projects/grpc-testing/global/images/xds-test-server-5 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ From a000d67a8869585d23d8e9f0a50195c5b364a240 Mon Sep 17 00:00:00 2001 From: Lidi Zheng Date: Wed, 29 Dec 2021 11:58:32 -0800 Subject: [PATCH 162/694] Use xds-test-server-5 as interop server image --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index a06cde84b..b584c21c3 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -54,7 +54,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh python3 grpc/tools/run_tests/run_xds_tests.py \ --test_case="all,timeout,circuit_breaking,fault_injection" \ --project_id=grpc-testing \ - --source_image=projects/grpc-testing/global/images/xds-test-server-4 \ + --source_image=projects/grpc-testing/global/images/xds-test-server-5 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ From 9f3001eb9737f282b31a4cbdcadb3c1f9994f667 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 4 Jan 2022 10:08:40 -0800 Subject: [PATCH 163/694] proto-loader: Update yargs to version 17 --- packages/proto-loader/bin/proto-loader-gen-types.ts | 4 ++-- packages/proto-loader/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index b0a9819b4..30df07bdc 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -767,8 +767,8 @@ async function writeAllFiles(protoFiles: string[], options: GeneratorOptions) { } } -function runScript() { - const argv = yargs +async function runScript() { + const argv = await yargs .parserConfiguration({ 'parse-positional-numbers': false }) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 6db5cb721..7fb179922 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -49,14 +49,14 @@ "lodash.camelcase": "^4.3.0", "long": "^4.0.0", "protobufjs": "^6.10.0", - "yargs": "^16.1.1" + "yargs": "^17.3.1" }, "devDependencies": { "@types/lodash.camelcase": "^4.3.4", "@types/mkdirp": "^1.0.1", "@types/mocha": "^5.2.7", "@types/node": "^10.17.26", - "@types/yargs": "^15.0.5", + "@types/yargs": "^17.0.8", "clang-format": "^1.2.2", "gts": "^1.1.0", "rimraf": "^3.0.2", From 9f5187fbaaa4a7527973fe70d5655947ed714e8a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 4 Jan 2022 10:09:17 -0800 Subject: [PATCH 164/694] proto-loader: Increase version to 0.6.8 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 7fb179922..b1d3182f7 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.7", + "version": "0.6.8", "author": "Google Inc.", "contributors": [ { From 311d22e03e5818260feedaaf233f86cc55ed581b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 4 Jan 2022 12:41:41 -0800 Subject: [PATCH 165/694] grpc-js: Fix compatibility with @types/node 17.0.6 --- packages/grpc-js/src/object-stream.ts | 25 ++++++------------------- packages/grpc-js/src/server-call.ts | 5 ++--- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/packages/grpc-js/src/object-stream.ts b/packages/grpc-js/src/object-stream.ts index b17058a7a..042820925 100644 --- a/packages/grpc-js/src/object-stream.ts +++ b/packages/grpc-js/src/object-stream.ts @@ -36,9 +36,9 @@ export interface IntermediateObjectWritable extends Writable { write(chunk: any & T, cb?: WriteCallback): boolean; write(chunk: any & T, encoding?: any, cb?: WriteCallback): boolean; setDefaultEncoding(encoding: string): this; - end(): void; - end(chunk: any & T, cb?: Function): void; - end(chunk: any & T, encoding?: any, cb?: Function): void; + end(): this; + end(chunk: any & T, cb?: Function): this; + end(chunk: any & T, encoding?: any, cb?: Function): this; } export interface ObjectWritable extends IntermediateObjectWritable { @@ -46,20 +46,7 @@ export interface ObjectWritable extends IntermediateObjectWritable { write(chunk: T, cb?: Function): boolean; write(chunk: T, encoding?: any, cb?: Function): boolean; setDefaultEncoding(encoding: string): this; - end(): void; - end(chunk: T, cb?: Function): void; - end(chunk: T, encoding?: any, cb?: Function): void; + end(): this; + end(chunk: T, cb?: Function): this; + end(chunk: T, encoding?: any, cb?: Function): this; } - -export type ObjectDuplex = { - read(size?: number): U; - - _write(chunk: T, encoding: string, callback: Function): void; - write(chunk: T, cb?: Function): boolean; - write(chunk: T, encoding?: any, cb?: Function): boolean; - end(): void; - end(chunk: T, cb?: Function): void; - end(chunk: T, encoding?: any, cb?: Function): void; -} & Duplex & - ObjectWritable & - ObjectReadable; diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index be4429b0f..a79e401c6 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -236,7 +236,7 @@ export class ServerWritableStreamImpl this.trailingMetadata = metadata; } - super.end(); + return super.end(); } } @@ -282,7 +282,7 @@ export class ServerDuplexStreamImpl this.trailingMetadata = metadata; } - super.end(); + return super.end(); } } @@ -292,7 +292,6 @@ ServerDuplexStreamImpl.prototype._write = ServerWritableStreamImpl.prototype._write; ServerDuplexStreamImpl.prototype._final = ServerWritableStreamImpl.prototype._final; -ServerDuplexStreamImpl.prototype.end = ServerWritableStreamImpl.prototype.end; // Unary response callback signature. export type sendUnaryData = ( From e2dfb8fbcf0c7f0282e7becfaced75fdef20bb8f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 4 Jan 2022 12:42:05 -0800 Subject: [PATCH 166/694] grpc-js: Increase version to 1.4.6 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 02400ca90..e3c983445 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.4.5", + "version": "1.4.6", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From fba2b9498f7ebf59cc1303dc39fcaf14b5cd3471 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 4 Jan 2022 14:00:25 -0800 Subject: [PATCH 167/694] Fix end type again for older @types/node versions --- packages/grpc-js/src/object-stream.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/src/object-stream.ts b/packages/grpc-js/src/object-stream.ts index 042820925..22ab8a41f 100644 --- a/packages/grpc-js/src/object-stream.ts +++ b/packages/grpc-js/src/object-stream.ts @@ -36,9 +36,9 @@ export interface IntermediateObjectWritable extends Writable { write(chunk: any & T, cb?: WriteCallback): boolean; write(chunk: any & T, encoding?: any, cb?: WriteCallback): boolean; setDefaultEncoding(encoding: string): this; - end(): this; - end(chunk: any & T, cb?: Function): this; - end(chunk: any & T, encoding?: any, cb?: Function): this; + end(): ReturnType extends Writable ? this : void; + end(chunk: any & T, cb?: Function): ReturnType extends Writable ? this : void; + end(chunk: any & T, encoding?: any, cb?: Function): ReturnType extends Writable ? this : void; } export interface ObjectWritable extends IntermediateObjectWritable { @@ -46,7 +46,7 @@ export interface ObjectWritable extends IntermediateObjectWritable { write(chunk: T, cb?: Function): boolean; write(chunk: T, encoding?: any, cb?: Function): boolean; setDefaultEncoding(encoding: string): this; - end(): this; - end(chunk: T, cb?: Function): this; - end(chunk: T, encoding?: any, cb?: Function): this; + end(): ReturnType extends Writable ? this : void; + end(chunk: T, cb?: Function): ReturnType extends Writable ? this : void; + end(chunk: T, encoding?: any, cb?: Function): ReturnType extends Writable ? this : void; } From 78631cdad826316e0a5d562fea55b2571484e615 Mon Sep 17 00:00:00 2001 From: Joey Harrington Date: Mon, 27 Dec 2021 12:55:16 -0800 Subject: [PATCH 168/694] Document grpc-js supported channel args in readme This moves the list of supported channel arguments from PACKAGE_COMPARISON.md into the readme, and also adds a link to the package comparison doc. resolves #1982, resolves #1983 --- PACKAGE-COMPARISON.md | 21 ++------------------- packages/grpc-js/README.md | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/PACKAGE-COMPARISON.md b/PACKAGE-COMPARISON.md index fa0ea319e..f0c444195 100644 --- a/PACKAGE-COMPARISON.md +++ b/PACKAGE-COMPARISON.md @@ -28,22 +28,5 @@ Supported Electron Versions | All | >= 3 Supported Platforms | Linux, Windows, MacOS | All Supported Architectures | x86, x86-64, ARM7+ | All -In addition, all channel arguments defined in [this header file](https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/grpc_types.h) are handled by the `grpc` library. Of those, the following are handled by the `@grpc/grpc-js` library: - - - `grpc.ssl_target_name_override` - - `grpc.primary_user_agent` - - `grpc.secondary_user_agent` - - `grpc.default_authority` - - `grpc.keepalive_time_ms` - - `grpc.keepalive_timeout_ms` - - `grpc.keepalive_permit_without_calls` - - `grpc.service_config` - - `grpc.max_concurrent_streams` - - `grpc.initial_reconnect_backoff_ms` - - `grpc.max_reconnect_backoff_ms` - - `grpc.use_local_subchannel_pool` - - `grpc.max_send_message_length` - - `grpc.max_receive_message_length` - - `grpc.enable_http_proxy` - - `channelOverride` - - `channelFactoryOverride` +In addition, all channel arguments defined in [this header file](https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/grpc_types.h) are handled by the `grpc` library. +Of those, a subset are handled by the `@grpc/grpc-js` library. See [the README](https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/README.md#supported-channel-options) for `@grpc/grpc-js` for the list of supported channel options. diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index df44264ce..4799b0f4a 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -36,6 +36,28 @@ This library does not directly handle `.proto` files. To use `.proto` files with - If you are currently loading `.proto` files using `grpc.load`, that function is not available in this library. You should instead load your `.proto` files using `@grpc/proto-loader` and load the resulting package definition objects into `@grpc/grpc-js` using `grpc.loadPackageDefinition`. - If you are currently loading packages generated by `grpc-tools`, you should instead generate your files using the `generate_package_definition` option in `grpc-tools`, then load the object exported by the generated file into `@grpc/grpc-js` using `grpc.loadPackageDefinition`. - If you have a server and you are using `Server#bind` to bind ports, you will need to use `Server#bindAsync` instead. +- If you are using any channel options supported in `grpc` but not supported in `@grpc/grpc-js`, you may need to adjust your code to handle the different behavior. Refer to [the list of supported options](#supported-channel-options) below. +- Refer to the [detailed package comparison](https://github.com/grpc/grpc-node/blob/master/PACKAGE-COMPARISON.md) for more details on the differences between `grpc` and `@grpc/grpc-js`. + +## Supported Channel Options +Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. The channel arguments supported by `@grpc/grpc-js` are: + - `grpc.ssl_target_name_override` + - `grpc.primary_user_agent` + - `grpc.secondary_user_agent` + - `grpc.default_authority` + - `grpc.keepalive_time_ms` + - `grpc.keepalive_timeout_ms` + - `grpc.keepalive_permit_without_calls` + - `grpc.service_config` + - `grpc.max_concurrent_streams` + - `grpc.initial_reconnect_backoff_ms` + - `grpc.max_reconnect_backoff_ms` + - `grpc.use_local_subchannel_pool` + - `grpc.max_send_message_length` + - `grpc.max_receive_message_length` + - `grpc.enable_http_proxy` + - `channelOverride` + - `channelFactoryOverride` ## Some Notes on API Guarantees From f049333e488d3446291d791b66383d1f2527cad7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 5 Jan 2022 10:51:00 -0800 Subject: [PATCH 169/694] proto-loader: Decrease dependency to yargs 16 for compatibility with Node <12 --- packages/proto-loader/bin/proto-loader-gen-types.ts | 2 +- packages/proto-loader/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 30df07bdc..a819d12d4 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -768,7 +768,7 @@ async function writeAllFiles(protoFiles: string[], options: GeneratorOptions) { } async function runScript() { - const argv = await yargs + const argv = yargs .parserConfiguration({ 'parse-positional-numbers': false }) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index b1d3182f7..5694cb547 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -49,14 +49,14 @@ "lodash.camelcase": "^4.3.0", "long": "^4.0.0", "protobufjs": "^6.10.0", - "yargs": "^17.3.1" + "yargs": "^16.2.0" }, "devDependencies": { "@types/lodash.camelcase": "^4.3.4", "@types/mkdirp": "^1.0.1", "@types/mocha": "^5.2.7", "@types/node": "^10.17.26", - "@types/yargs": "^17.0.8", + "@types/yargs": "^16.0.4", "clang-format": "^1.2.2", "gts": "^1.1.0", "rimraf": "^3.0.2", From ee9226e3c87bdbf767e6360f0c897019cec9bae1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 5 Jan 2022 10:51:45 -0800 Subject: [PATCH 170/694] proto-loader: Update version to 0.6.9 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 5694cb547..9b9431dc1 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.8", + "version": "0.6.9", "author": "Google Inc.", "contributors": [ { From ea1a266dec4a87546a48d29666a73d37d4c4d6e6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 6 Jan 2022 14:36:40 -0800 Subject: [PATCH 171/694] Update grpc-js and grpc-js-xds to version 1.5.0, and update README --- packages/grpc-js-xds/README.md | 3 ++- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js/package.json | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index 84d291508..1ac235c40 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -26,4 +26,5 @@ const client = new MyServiceClient('xds:///example.com:123'); - [xDS v3 API](https://github.com/grpc/proposal/blob/master/A30-xds-v3.md) - [xDS Timeouts](https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md) - [xDS Circuit Breaking](https://github.com/grpc/proposal/blob/master/A32-xds-circuit-breaking.md) - - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) \ No newline at end of file + - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) + - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) \ No newline at end of file diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 2ead8f1e8..d3d9ad72f 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.4.0", + "version": "1.5.0", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -47,7 +47,7 @@ "re2-wasm": "^1.0.1" }, "peerDependencies": { - "@grpc/grpc-js": "~1.4.0" + "@grpc/grpc-js": "~1.5.0" }, "engines": { "node": ">=10.10.0" diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 510eac614..97b647a86 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.4.6", + "version": "1.5.0", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From c95357ccd1a246178141ae4c5962e531f2c8466b Mon Sep 17 00:00:00 2001 From: Jason Praful <32552408+jasonpraful@users.noreply.github.com> Date: Fri, 7 Jan 2022 10:51:38 +0000 Subject: [PATCH 172/694] refactor: added max session memory to docs --- packages/grpc-js/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 4799b0f4a..cda4fec3c 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -56,6 +56,7 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.max_send_message_length` - `grpc.max_receive_message_length` - `grpc.enable_http_proxy` + - `grpc-node.max_session_memory` - `channelOverride` - `channelFactoryOverride` From d1762316e279d9a0dd224ca0a2eafc843f6a8bda Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 7 Jan 2022 12:21:12 -0800 Subject: [PATCH 173/694] grpc-js: Document recently-added channel options --- packages/grpc-js/README.md | 3 +++ packages/grpc-js/src/channel-options.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 4799b0f4a..71ec937ed 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -56,6 +56,9 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.max_send_message_length` - `grpc.max_receive_message_length` - `grpc.enable_http_proxy` + - `grpc.default_compression_algorithm` + - `grpc.enable_channelz` + - `grpc-node.max_session_memory` - `channelOverride` - `channelFactoryOverride` diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index e1c16d127..688317227 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -36,6 +36,9 @@ export interface ChannelOptions { 'grpc.max_send_message_length'?: number; 'grpc.max_receive_message_length'?: number; 'grpc.enable_http_proxy'?: number; + /* http_connect_target and http_connect_creds are used for passing data + * around internally, and should not be documented as public-facing options + */ 'grpc.http_connect_target'?: string; 'grpc.http_connect_creds'?: string; 'grpc.default_compression_algorithm'?: CompressionAlgorithms; From f6d8f137a2fc0f43faf25ab3f51f540522b94a97 Mon Sep 17 00:00:00 2001 From: DavyJohnes Date: Mon, 10 Jan 2022 13:54:53 +0300 Subject: [PATCH 174/694] fix(make-client): set provided serviceName to generated class --- packages/grpc-js/src/make-client.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grpc-js/src/make-client.ts b/packages/grpc-js/src/make-client.ts index b8ddda29f..7d08fee20 100644 --- a/packages/grpc-js/src/make-client.ts +++ b/packages/grpc-js/src/make-client.ts @@ -91,6 +91,7 @@ export interface ServiceClientConstructor { options?: Partial ): ServiceClient; service: ServiceDefinition; + serviceName: string; } /** @@ -127,6 +128,7 @@ export function makeClientConstructor( class ServiceClientImpl extends Client implements ServiceClient { static service: ServiceDefinition; + static serviceName: string; [methodName: string]: Function; } @@ -171,6 +173,7 @@ export function makeClientConstructor( }); ServiceClientImpl.service = methods; + ServiceClientImpl.serviceName = serviceName; return ServiceClientImpl; } From d46d5c0b2972cbbc2da6d17ab3dfab26efcc7df5 Mon Sep 17 00:00:00 2001 From: Seva Orlov Date: Tue, 11 Jan 2022 14:43:45 +0200 Subject: [PATCH 175/694] Add envoy/extensions files --- packages/grpc-js-xds/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 2ead8f1e8..3f1ddb5e9 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -60,6 +60,7 @@ "deps/envoy-api/envoy/service/**/*.proto", "deps/envoy-api/envoy/type/**/*.proto", "deps/envoy-api/envoy/annotations/**/*.proto", + "deps/envoy-api/envoy/extensions/**/*.proto", "deps/googleapis/google/api/**/*.proto", "deps/googleapis/google/protobuf/**/*.proto", "deps/googleapis/google/rpc/**/*.proto", From 7b2cdd0291d5c269a7e3914300794b71702106a1 Mon Sep 17 00:00:00 2001 From: Seva Orlov Date: Tue, 11 Jan 2022 14:43:45 +0200 Subject: [PATCH 176/694] Add envoy/extensions files --- packages/grpc-js-xds/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index d3d9ad72f..6d6cc68ad 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -60,6 +60,7 @@ "deps/envoy-api/envoy/service/**/*.proto", "deps/envoy-api/envoy/type/**/*.proto", "deps/envoy-api/envoy/annotations/**/*.proto", + "deps/envoy-api/envoy/extensions/**/*.proto", "deps/googleapis/google/api/**/*.proto", "deps/googleapis/google/protobuf/**/*.proto", "deps/googleapis/google/rpc/**/*.proto", From 9e3bd11d64c7436d296b999bf7e041aebe6bf732 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 11 Jan 2022 11:10:58 -0800 Subject: [PATCH 177/694] grpc-js-xds: Increase version to 1.5.1 --- packages/grpc-js-xds/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 6d6cc68ad..8af0c1037 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.5.0", + "version": "1.5.1", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { From 70b7917dda5ef6e971f10fb967b4ef0391be20d7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Jan 2022 10:30:27 -0800 Subject: [PATCH 178/694] grpc-js-xds: Add more missing files, add distrib test --- packages/grpc-js-xds/package.json | 1 + run-tests.sh | 2 ++ test/distrib/distrib-test.js | 22 +++++++++++++++++++++ test/distrib/run-distrib-test.sh | 33 +++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 test/distrib/distrib-test.js create mode 100644 test/distrib/run-distrib-test.sh diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 8af0c1037..ddc7f7bd2 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -55,6 +55,7 @@ "files": [ "src/**/*.ts", "build/src/**/*.{js,d.ts,js.map}", + "deps/envoy-api/envoy/admin/v3/**/*.proto", "deps/envoy-api/envoy/api/v2/**/*.proto", "deps/envoy-api/envoy/config/**/*.proto", "deps/envoy-api/envoy/service/**/*.proto", diff --git a/run-tests.sh b/run-tests.sh index e62cbd885..4bcb388b4 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -75,6 +75,8 @@ do # npm test calls nyc gulp test npm test || FAILED="true" + + ./test/distrib/run-distrib-test.sh || FAILED="true" done set +ex diff --git a/test/distrib/distrib-test.js b/test/distrib/distrib-test.js new file mode 100644 index 000000000..de3cbc7c7 --- /dev/null +++ b/test/distrib/distrib-test.js @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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. + * + */ + +const grpcJs = require('@grpc/grpc-js'); + +const grpcJsXds = require('@grpc/grpc-js-xds'); + +const protoLoader = require('@grpc/proto-loader'); \ No newline at end of file diff --git a/test/distrib/run-distrib-test.sh b/test/distrib/run-distrib-test.sh new file mode 100644 index 000000000..e2ed0853c --- /dev/null +++ b/test/distrib/run-distrib-test.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright 2022 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. + +set -ex + +cd $(dirname $0) +base=$(pwd) + +cd ../../packages/grpc-js +npm pack +cd ../grpc-js-xds +npm pack +cd ../proto-loader +npm pack + +cd $base +npm install ../../packages/grpc-js/grpc-grpc-js-*.tgz +npm install ../../packages/grpc-js-xds/grpc-grpc-js-xds-*.tgz +npm install ../../packages/proto-loader/grpc-proto-loader-*.tgz + +node ./distrib-test.js From aedfcebde5b2c5ffc64d867bd3628f2620a9a75f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Jan 2022 10:30:52 -0800 Subject: [PATCH 179/694] grpc-js-xds: Increase version to 1.5.2 --- packages/grpc-js-xds/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index ddc7f7bd2..b4e0a49d7 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.5.1", + "version": "1.5.2", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { From 52a5e08c7525c04170f99d1b43c905c2510ec3e2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Jan 2022 10:51:01 -0800 Subject: [PATCH 180/694] chmod a+x run-distrib-test.sh --- test/distrib/run-distrib-test.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 test/distrib/run-distrib-test.sh diff --git a/test/distrib/run-distrib-test.sh b/test/distrib/run-distrib-test.sh old mode 100644 new mode 100755 From bc28ee42fd1449113b218c6382d21acc02bf29ff Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Jan 2022 13:05:45 -0800 Subject: [PATCH 181/694] Add newline in distrib-test.js --- test/distrib/distrib-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/distrib/distrib-test.js b/test/distrib/distrib-test.js index de3cbc7c7..708c93eb8 100644 --- a/test/distrib/distrib-test.js +++ b/test/distrib/distrib-test.js @@ -19,4 +19,4 @@ const grpcJs = require('@grpc/grpc-js'); const grpcJsXds = require('@grpc/grpc-js-xds'); -const protoLoader = require('@grpc/proto-loader'); \ No newline at end of file +const protoLoader = require('@grpc/proto-loader'); From c6691c855105e95ac176cfb799069e69613f1993 Mon Sep 17 00:00:00 2001 From: Oskar Nyberg Date: Thu, 13 Jan 2022 16:24:48 +0100 Subject: [PATCH 182/694] grpc-js: Don't use http_proxy for uds connections --- packages/grpc-js/src/http_proxy.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index 248949a80..6faa1976d 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -136,6 +136,9 @@ export function mapProxyName( if ((options['grpc.enable_http_proxy'] ?? 1) === 0) { return noProxyResult; } + if (target.scheme === 'unix') { + return noProxyResult; + } const proxyInfo = getProxyInfo(); if (!proxyInfo.address) { return noProxyResult; From 5a728ffdc545bbd59247d927ba6e7f8f05e70934 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 13 Jan 2022 15:58:23 -0800 Subject: [PATCH 183/694] grpc-js: Add backoff to DNS resolution attempts --- packages/grpc-js/src/resolver-dns.ts | 33 ++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 9077228b8..160b3d326 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -32,6 +32,7 @@ import { SubchannelAddress, TcpSubchannelAddress } from './subchannel-address'; import { GrpcUri, uriToString, splitHostPort } from './uri-parser'; import { isIPv6, isIPv4 } from 'net'; import { ChannelOptions } from './channel-options'; +import { BackoffOptions, BackoffTimeout } from './backoff-timeout'; const TRACER_NAME = 'dns_resolver'; @@ -85,6 +86,8 @@ class DnsResolver implements Resolver { private latestServiceConfigError: StatusObject | null = null; private percentage: number; private defaultResolutionError: StatusObject; + private backoff: BackoffTimeout; + private continueResolving = false; constructor( private target: GrpcUri, private listener: ResolverListener, @@ -119,6 +122,18 @@ class DnsResolver implements Resolver { details: `Name resolution failed for target ${uriToString(this.target)}`, metadata: new Metadata(), }; + + const backoffOptions: BackoffOptions = { + initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'], + maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'], + }; + + this.backoff = new BackoffTimeout(() => { + if (this.continueResolving) { + this.startResolutionWithBackoff(); + } + }, backoffOptions); + this.backoff.unref(); } /** @@ -140,6 +155,7 @@ class DnsResolver implements Resolver { return; } if (this.dnsHostname === null) { + trace('Failed to parse DNS address ' + uriToString(this.target)); setImmediate(() => { this.listener.onError({ code: Status.UNAVAILABLE, @@ -148,6 +164,7 @@ class DnsResolver implements Resolver { }); }); } else { + trace('Looking up DNS hostname ' + this.dnsHostname); /* We clear out latestLookupResult here to ensure that it contains the * latest result since the last time we started resolving. That way, the * TXT resolution handler can use it, but only if it finishes second. We @@ -164,6 +181,7 @@ class DnsResolver implements Resolver { this.pendingLookupPromise.then( (addressList) => { this.pendingLookupPromise = null; + this.backoff.reset(); const ip4Addresses: dns.LookupAddress[] = addressList.filter( (addr) => addr.family === 4 ); @@ -263,10 +281,21 @@ class DnsResolver implements Resolver { } } + private startResolutionWithBackoff() { + this.startResolution(); + this.backoff.runOnce(); + } + updateResolution() { - trace('Resolution update requested for target ' + uriToString(this.target)); + /* If there is a pending lookup, just let it finish. Otherwise, if the + * backoff timer is running, do another lookup when it ends, and if not, + * do another lookup immeidately. */ if (this.pendingLookupPromise === null) { - this.startResolution(); + if (this.backoff.isRunning()) { + this.continueResolving = true; + } else { + this.startResolutionWithBackoff(); + } } } From 80f31bb1c29692939722fd342d3b9ed6b730de7d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 13 Jan 2022 15:58:37 -0800 Subject: [PATCH 184/694] grpc-js: Increase version to 1.5.1 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 97b647a86..95f157e63 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.0", + "version": "1.5.1", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 500fc2c752a7259665b480b8b1457225d43c8087 Mon Sep 17 00:00:00 2001 From: Oskar Nyberg Date: Thu, 13 Jan 2022 16:24:48 +0100 Subject: [PATCH 185/694] grpc-js: Don't use http_proxy for uds connections --- packages/grpc-js/src/http_proxy.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index 248949a80..6faa1976d 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -136,6 +136,9 @@ export function mapProxyName( if ((options['grpc.enable_http_proxy'] ?? 1) === 0) { return noProxyResult; } + if (target.scheme === 'unix') { + return noProxyResult; + } const proxyInfo = getProxyInfo(); if (!proxyInfo.address) { return noProxyResult; From db56e80b2135dc5dfa87aae5e47df8b67fde2f9c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 18 Jan 2022 12:35:22 -0800 Subject: [PATCH 186/694] grpc-js: Add secureConnection error handling in server --- packages/grpc-js/src/server.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 33057defa..9c28a276f 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -371,6 +371,13 @@ export class Server { creds._getSettings()! ); http2Server = http2.createSecureServer(secureServerOptions); + http2Server.on('secureConnection', (socket: TLSSocket) => { + /* These errors need to be handled by the user of Http2SecureServer, + * according to https://github.com/nodejs/node/issues/35824 */ + socket.on('error', (e: Error) => { + this.trace('An incoming TLS connection closed with error: ' + e.message); + }); + }); } else { http2Server = http2.createServer(serverOptions); } From b9deb5bc3c75d4d568c4777d3b8328c1f8d8683f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 18 Jan 2022 12:35:35 -0800 Subject: [PATCH 187/694] grpc-js: Increase version to 1.5.2 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 95f157e63..e84ae854f 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.1", + "version": "1.5.2", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 4b3c26382b0c4e4cd821b589d948d9ca5abd8b72 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 11 Jan 2022 11:07:19 -0800 Subject: [PATCH 188/694] Add subchannel interface --- packages/grpc-js/src/channel.ts | 3 +- packages/grpc-js/src/experimental.ts | 1 + .../src/load-balancer-child-handler.ts | 4 +- .../grpc-js/src/load-balancer-pick-first.ts | 14 ++-- .../grpc-js/src/load-balancer-round-robin.ts | 10 +-- packages/grpc-js/src/load-balancer.ts | 3 +- packages/grpc-js/src/picker.ts | 5 +- packages/grpc-js/src/subchannel-interface.ts | 82 +++++++++++++++++++ packages/grpc-js/src/subchannel.ts | 11 ++- 9 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 packages/grpc-js/src/subchannel-interface.ts diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 8fa2c592a..8b9275e95 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -49,6 +49,7 @@ import { Filter } from './filter'; import { ConnectivityState } from './connectivity-state'; import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; +import { Subchannel } from './subchannel'; /** * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args @@ -451,7 +452,7 @@ export class ChannelImplementation implements Channel { if (subchannelState === ConnectivityState.READY) { try { const pickExtraFilters = pickResult.extraFilterFactories.map(factory => factory.createFilter(callStream)); - pickResult.subchannel!.startCallStream( + pickResult.subchannel?.getRealSubchannel().startCallStream( finalMetadata, callStream, [...dynamicFilters, ...pickExtraFilters] diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 5353f7f59..7356cb81a 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -34,3 +34,4 @@ export { Call as CallStream } from './call-stream'; export { Filter, BaseFilter, FilterFactory } from './filter'; export { FilterStackFactory } from './filter-stack'; export { registerAdminService } from './admin'; +export { SubchannelInterface, BaseSubchannelWrapper, ConnectivityStateListener } from './subchannel-interface' diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index c53914942..50708cf90 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -21,12 +21,12 @@ import { LoadBalancingConfig, createLoadBalancer, } from './load-balancer'; -import { Subchannel } from './subchannel'; import { SubchannelAddress } from './subchannel-address'; import { ChannelOptions } from './channel-options'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; import { ChannelRef, SubchannelRef } from './channelz'; +import { SubchannelInterface } from './subchannel-interface'; const TYPE_NAME = 'child_load_balancer_helper'; @@ -40,7 +40,7 @@ export class ChildLoadBalancerHandler implements LoadBalancer { createSubchannel( subchannelAddress: SubchannelAddress, subchannelArgs: ChannelOptions - ): Subchannel { + ): SubchannelInterface { return this.parent.channelControlHelper.createSubchannel( subchannelAddress, subchannelArgs diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 6cdb7cb91..c7033f589 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -31,13 +31,13 @@ import { PickResultType, UnavailablePicker, } from './picker'; -import { Subchannel, ConnectivityStateListener } from './subchannel'; import { SubchannelAddress, subchannelAddressToString, } from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; +import { SubchannelInterface, ConnectivityStateListener } from './subchannel-interface'; const TRACER_NAME = 'pick_first'; @@ -77,7 +77,7 @@ export class PickFirstLoadBalancingConfig implements LoadBalancingConfig { * picked subchannel. */ class PickFirstPicker implements Picker { - constructor(private subchannel: Subchannel) {} + constructor(private subchannel: SubchannelInterface) {} pick(pickArgs: PickArgs): CompletePickResult { return { @@ -107,7 +107,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { * The list of subchannels this load balancer is currently attempting to * connect to. */ - private subchannels: Subchannel[] = []; + private subchannels: SubchannelInterface[] = []; /** * The current connectivity state of the load balancer. */ @@ -124,7 +124,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { * and only if the load balancer's current state is READY. In that case, * the subchannel's current state is also READY. */ - private currentPick: Subchannel | null = null; + private currentPick: SubchannelInterface | null = null; /** * Listener callback attached to each subchannel in the `subchannels` list * while establishing a connection. @@ -157,7 +157,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { [ConnectivityState.TRANSIENT_FAILURE]: 0, }; this.subchannelStateListener = ( - subchannel: Subchannel, + subchannel: SubchannelInterface, previousState: ConnectivityState, newState: ConnectivityState ) => { @@ -219,7 +219,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { } }; this.pickedSubchannelStateListener = ( - subchannel: Subchannel, + subchannel: SubchannelInterface, previousState: ConnectivityState, newState: ConnectivityState ) => { @@ -310,7 +310,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { }, CONNECTION_DELAY_INTERVAL_MS); } - private pickSubchannel(subchannel: Subchannel) { + private pickSubchannel(subchannel: SubchannelInterface) { trace('Pick subchannel with address ' + subchannel.getAddress()); if (this.currentPick !== null) { this.currentPick.unref(); diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 918afab25..8a4094a02 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -30,13 +30,13 @@ import { PickResultType, UnavailablePicker, } from './picker'; -import { Subchannel, ConnectivityStateListener } from './subchannel'; import { SubchannelAddress, subchannelAddressToString, } from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; +import { ConnectivityStateListener, SubchannelInterface } from './subchannel-interface'; const TRACER_NAME = 'round_robin'; @@ -67,7 +67,7 @@ class RoundRobinLoadBalancingConfig implements LoadBalancingConfig { class RoundRobinPicker implements Picker { constructor( - private readonly subchannelList: Subchannel[], + private readonly subchannelList: SubchannelInterface[], private nextIndex = 0 ) {} @@ -88,7 +88,7 @@ class RoundRobinPicker implements Picker { * balancer implementation to preserve this part of the picker state if * possible when a subchannel connects or disconnects. */ - peekNextSubchannel(): Subchannel { + peekNextSubchannel(): SubchannelInterface { return this.subchannelList[this.nextIndex]; } } @@ -102,7 +102,7 @@ interface ConnectivityStateCounts { } export class RoundRobinLoadBalancer implements LoadBalancer { - private subchannels: Subchannel[] = []; + private subchannels: SubchannelInterface[] = []; private currentState: ConnectivityState = ConnectivityState.IDLE; @@ -121,7 +121,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer { [ConnectivityState.TRANSIENT_FAILURE]: 0, }; this.subchannelStateListener = ( - subchannel: Subchannel, + subchannel: SubchannelInterface, previousState: ConnectivityState, newState: ConnectivityState ) => { diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index 19b79764c..48930c7db 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -21,6 +21,7 @@ import { SubchannelAddress } from './subchannel-address'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; import { ChannelRef, SubchannelRef } from './channelz'; +import { SubchannelInterface } from './subchannel-interface'; /** * A collection of functions associated with a channel that a load balancer @@ -35,7 +36,7 @@ export interface ChannelControlHelper { createSubchannel( subchannelAddress: SubchannelAddress, subchannelArgs: ChannelOptions - ): Subchannel; + ): SubchannelInterface; /** * Passes a new subchannel picker up to the channel. This is called if either * the connectivity state changes or if a different picker is needed for any diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index 7aeed89b3..f366a6919 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -21,6 +21,7 @@ import { Metadata } from './metadata'; import { Status } from './constants'; import { LoadBalancer } from './load-balancer'; import { FilterFactory, Filter } from './filter'; +import { SubchannelInterface } from './subchannel-interface'; export enum PickResultType { COMPLETE, @@ -36,7 +37,7 @@ export interface PickResult { * `pickResultType` is COMPLETE. If null, indicates that the call should be * dropped. */ - subchannel: Subchannel | null; + subchannel: SubchannelInterface | null; /** * The status object to end the call with. Populated if and only if * `pickResultType` is TRANSIENT_FAILURE. @@ -53,7 +54,7 @@ export interface PickResult { export interface CompletePickResult extends PickResult { pickResultType: PickResultType.COMPLETE; - subchannel: Subchannel | null; + subchannel: SubchannelInterface | null; status: null; extraFilterFactories: FilterFactory[]; onCallStarted: (() => void) | null; diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts new file mode 100644 index 000000000..cdf99176d --- /dev/null +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -0,0 +1,82 @@ +/* + * Copyright 2022 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 { SubchannelRef } from "./channelz"; +import { ConnectivityState } from "./connectivity-state"; +import { Subchannel } from "./subchannel"; + +export type ConnectivityStateListener = ( + subchannel: SubchannelInterface, + previousState: ConnectivityState, + newState: ConnectivityState +) => void; + +/** + * This is an interface for load balancing policies to use to interact with + * subchannels. This allows load balancing policies to wrap and unwrap + * subchannels. + * + * Any load balancing policy that wraps subchannels must unwrap the subchannel + * in the picker, so that other load balancing policies consistently have + * access to their own wrapper objects. + */ +export interface SubchannelInterface { + getConnectivityState(): ConnectivityState; + addConnectivityStateListener(listener: ConnectivityStateListener): void; + removeConnectivityStateListener(listener: ConnectivityStateListener): void; + startConnecting(): void; + getAddress(): string; + ref(): void; + unref(): void; + getChannelzRef(): SubchannelRef; + /** + * If this is a wrapper, return the wrapped subchannel, otherwise return this + */ + getRealSubchannel(): Subchannel; +} + +export abstract class BaseSubchannelWrapper implements SubchannelInterface { + constructor(private child: SubchannelInterface) {} + + getConnectivityState(): ConnectivityState { + return this.child.getConnectivityState(); + } + addConnectivityStateListener(listener: ConnectivityStateListener): void { + this.child.addConnectivityStateListener(listener); + } + removeConnectivityStateListener(listener: ConnectivityStateListener): void { + this.child.removeConnectivityStateListener(listener); + } + startConnecting(): void { + this.child.startConnecting(); + } + getAddress(): string { + return this.child.getAddress(); + } + ref(): void { + this.child.ref(); + } + unref(): void { + this.child.unref(); + } + getChannelzRef(): SubchannelRef { + return this.child.getChannelzRef(); + } + getRealSubchannel(): Subchannel { + return this.child.getRealSubchannel(); + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 6f9471bb9..3a1f70487 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -37,6 +37,7 @@ import { subchannelAddressToString, } from './subchannel-address'; import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, SocketInfo, SocketRef, unregisterChannelzRef, registerChannelzSocket, TlsInfo } from './channelz'; +import { ConnectivityStateListener } from './subchannel-interface'; const clientVersion = require('../../package.json').version; @@ -54,12 +55,6 @@ const BACKOFF_JITTER = 0.2; const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); const KEEPALIVE_TIMEOUT_MS = 20000; -export type ConnectivityStateListener = ( - subchannel: Subchannel, - previousState: ConnectivityState, - newState: ConnectivityState -) => void; - export interface SubchannelCallStatsTracker { addMessageSent(): void; addMessageReceived(): void; @@ -949,4 +944,8 @@ export class Subchannel { getChannelzRef(): SubchannelRef { return this.channelzRef; } + + getRealSubchannel(): this { + return this; + } } From 0a5a2321b4ea1c6442627b843c80e2bb101a280f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 Jan 2022 09:31:09 -0800 Subject: [PATCH 189/694] grpc-js: Fix pick first shutdown reference handling --- .../grpc-js/src/load-balancer-pick-first.ts | 10 ++- .../test/test-local-subchannel-pool.ts | 73 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 packages/grpc-js/test/test-local-subchannel-pool.ts diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 6cdb7cb91..2f638ae68 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -449,11 +449,15 @@ export class PickFirstLoadBalancer implements LoadBalancer { destroy() { this.resetSubchannelList(); if (this.currentPick !== null) { - this.currentPick.unref(); - this.currentPick.removeConnectivityStateListener( + /* Unref can cause a state change, which can cause a change in the value + * of this.currentPick, so we hold a local reference to make sure that + * does not impact this function. */ + const currentPick = this.currentPick; + currentPick.unref(); + currentPick.removeConnectivityStateListener( this.pickedSubchannelStateListener ); - this.channelControlHelper.removeChannelzChild(this.currentPick.getChannelzRef()); + this.channelControlHelper.removeChannelzChild(currentPick.getChannelzRef()); } } diff --git a/packages/grpc-js/test/test-local-subchannel-pool.ts b/packages/grpc-js/test/test-local-subchannel-pool.ts new file mode 100644 index 000000000..081b2d3dc --- /dev/null +++ b/packages/grpc-js/test/test-local-subchannel-pool.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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 * as assert from 'assert'; +import * as path from 'path'; +import * as grpc from '../src'; +import { sendUnaryData, Server, ServerCredentials, ServerUnaryCall, ServiceClientConstructor, ServiceError } from "../src"; +import { loadProtoFile } from './common'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; + +describe('Local subchannel pool', () => { + let server: Server; + let serverPort: number; + + before(done => { + + server = new Server(); + server.addService(echoService.service, { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, call.request); + }, + }); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + serverPort = port; + server.start(); + done(); + } + ); + }); + + after(done => { + server.tryShutdown(done); + }); + + it('should complete the client lifecycle without error', done => { + const client = new echoService( + `localhost:${serverPort}`, + grpc.credentials.createInsecure(), + {'grpc.use_local_subchannel_pool': 1} + ); + client.echo( + { value: 'test value', value2: 3 }, + (error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + client.close(); + done(); + } + ); + }); +}); \ No newline at end of file From 27bae2009dc22e0fb39fbd61a57201c80f2e705b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 Jan 2022 09:36:44 -0800 Subject: [PATCH 190/694] grpc-js: Increase version to 1.5.3 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index e84ae854f..3b62aa5ed 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.2", + "version": "1.5.3", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From ba70f7168b41c8533bdaed0bde2cbf573cbdcf1e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 31 Jan 2022 13:54:40 -0800 Subject: [PATCH 191/694] grpc-js: Fix exitIdle propagation and DNS IP result backoff --- packages/grpc-js/src/load-balancer-child-handler.ts | 4 ++-- packages/grpc-js/src/resolver-dns.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index c53914942..3379ec8a7 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -125,9 +125,9 @@ export class ChildLoadBalancerHandler implements LoadBalancer { } exitIdle(): void { if (this.currentChild) { - this.currentChild.resetBackoff(); + this.currentChild.exitIdle(); if (this.pendingChild) { - this.pendingChild.resetBackoff(); + this.pendingChild.exitIdle(); } } } diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 160b3d326..8ad24ed05 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -144,6 +144,7 @@ class DnsResolver implements Resolver { if (this.ipResult !== null) { trace('Returning IP address for target ' + uriToString(this.target)); setImmediate(() => { + this.backoff.reset(); this.listener.onSuccessfulResolution( this.ipResult!, null, From f49ed624766d1c40d1b9f215781bf84231688563 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 31 Jan 2022 13:55:07 -0800 Subject: [PATCH 192/694] grpc-js: Increase version to 1.5.4 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 3b62aa5ed..8c65b9365 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.3", + "version": "1.5.4", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 2334ca9dfa24915e50304c7c8a472d8e9f34c1ea Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Mon, 7 Feb 2022 16:47:56 -0800 Subject: [PATCH 193/694] Add isTracerEnabled to logging --- packages/grpc-js/src/logging.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts index 549cd860e..ec845c1a5 100644 --- a/packages/grpc-js/src/logging.ts +++ b/packages/grpc-js/src/logging.ts @@ -108,10 +108,12 @@ export function trace( tracer: string, text: string ): void { - if ( - !disabledTracers.has(tracer) && - (allEnabled || enabledTracers.has(tracer)) - ) { + if (isTracerEnabled(tracer)) { log(severity, new Date().toISOString() + ' | ' + tracer + ' | ' + text); } } + +export function isTracerEnabled(tracer: string): boolean { + return !disabledTracers.has(tracer) && + (allEnabled || enabledTracers.has(tracer)); +} From ae2a2ac7d0880433b6b8fc584885a14e2ec4fd97 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Mon, 7 Feb 2022 17:21:14 -0800 Subject: [PATCH 194/694] Add HTTP/2 settings frame tracing. This adds HTTP/2 settings frame information to debug logs. HTTP/2 settings frame contains important information like max_concurrent_streams and initial_window_size useful for debugging concurrency, latency, and throughput issues. --- packages/grpc-js/src/subchannel.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 6f9471bb9..9945b9881 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -555,6 +555,24 @@ export class Subchannel { (error as Error).message ); }); + if (logging.isTracerEnabled(TRACER_NAME)) { + session.on('remoteSettings', (settings: http2.Settings) => { + this.trace( + 'new settings received' + + (this.session !== session ? ' on the old connection' : '') + + ': ' + + JSON.stringify(settings) + ); + }); + session.on('localSettings', (settings: http2.Settings) => { + this.trace( + 'local settings acknowledged by remote' + + (this.session !== session ? ' on the old connection' : '') + + ': ' + + JSON.stringify(settings) + ); + }); + } } private startConnectingInternal() { From da66707d3bb9f5cf253b732888294a589c4dbc88 Mon Sep 17 00:00:00 2001 From: Yuri Golobokov Date: Tue, 8 Feb 2022 15:53:34 -0800 Subject: [PATCH 195/694] HTTP/2 flow control tracing subchannel_flowctrl tracer, if enabled, logs local and remote window sizes of subchannel's HTTP2 session to debug log on the start of every call. --- doc/environment_variables.md | 1 + packages/grpc-js/src/subchannel.ts | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/doc/environment_variables.md b/doc/environment_variables.md index f2a32294c..f4310e625 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -37,6 +37,7 @@ can be set. - `server_call` - Traces server handling of individual requests - `subchannel` - Traces subchannel connectivity state and errors - `subchannel_refcount` - Traces subchannel refcount changes + - `subchannel_flowctrl` - Traces HTTP/2 flow control The following tracers are added by the `@grpc/grpc-js-xds` library: - `cds_balancer` - Traces the CDS load balancing policy diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 9945b9881..faa68c145 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -41,6 +41,7 @@ import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, const clientVersion = require('../../package.json').version; const TRACER_NAME = 'subchannel'; +const FLOW_CONTROL_TRACER_NAME = 'subchannel_flowctrl'; const MIN_CONNECT_TIMEOUT_MS = 20000; const INITIAL_BACKOFF_MS = 1000; @@ -324,6 +325,10 @@ export class Subchannel { logging.trace(LogVerbosity.DEBUG, 'subchannel_refcount', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); } + private flowControlTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + private handleBackoffTimer() { if (this.continueConnecting) { this.transitionToState( @@ -848,6 +853,12 @@ export class Subchannel { ' with headers\n' + headersString ); + this.flowControlTrace( + 'local window size: ' + + this.session!.state.localWindowSize + + ' remote window size: ' + + this.session!.state.remoteWindowSize + ); const streamSession = this.session; let statsTracker: SubchannelCallStatsTracker; if (this.channelzEnabled) { From 5bb11e02a0eded5696eaec06f9bbdab6c9234f72 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 10 Feb 2022 10:12:36 -0800 Subject: [PATCH 196/694] grpc-js: Increase version to 1.5.5 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 8c65b9365..faad0991d 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.4", + "version": "1.5.5", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From d51dfffcfeab1ac448b0ac51eebd3560623ccc7d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 23 Feb 2022 09:50:11 -0800 Subject: [PATCH 197/694] grpc-js: Add session state logging at call start --- doc/environment_variables.md | 5 +++-- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel.ts | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/environment_variables.md b/doc/environment_variables.md index f4310e625..2eb429f92 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -36,8 +36,9 @@ can be set. - `server` - Traces high-level server events - `server_call` - Traces server handling of individual requests - `subchannel` - Traces subchannel connectivity state and errors - - `subchannel_refcount` - Traces subchannel refcount changes - - `subchannel_flowctrl` - Traces HTTP/2 flow control + - `subchannel_refcount` - Traces subchannel refcount changes. Includes per-call logs. + - `subchannel_flowctrl` - Traces HTTP/2 flow control. Includes per-call logs. + - `subchannel_internals` - Traces HTTP/2 session state. Includes per-call logs. The following tracers are added by the `@grpc/grpc-js-xds` library: - `cds_balancer` - Traces the CDS load balancing policy diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index faad0991d..d7708194e 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.5", + "version": "1.5.6", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index faa68c145..524e36316 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -329,6 +329,10 @@ export class Subchannel { logging.trace(LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); } + private internalsTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'subchannel_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + private handleBackoffTimer() { if (this.continueConnecting) { this.transitionToState( @@ -860,6 +864,13 @@ export class Subchannel { this.session!.state.remoteWindowSize ); const streamSession = this.session; + this.internalsTrace( + 'session.closed=' + + streamSession!.closed + + ' session.destroyed=' + + streamSession!.destroyed + + ' session.socket.destroyed=' + + streamSession!.socket.destroyed); let statsTracker: SubchannelCallStatsTracker; if (this.channelzEnabled) { this.callTracker.addCallStarted(); From 55087d21c4d4752731c7812d6649eb75dccb275b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 24 Feb 2022 09:09:54 -0800 Subject: [PATCH 198/694] grpc-js: Transition subchannel to TRANSIENT_FAILURE when the socket closes --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index d7708194e..f41de376d 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.6", + "version": "1.5.7", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 524e36316..24f02144d 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -669,9 +669,15 @@ export class Subchannel { switch (newState) { case ConnectivityState.READY: this.stopBackoff(); - this.session!.socket.once('close', () => { - for (const listener of this.disconnectListeners) { - listener(); + const session = this.session!; + session.socket.once('close', () => { + if (this.session === session) { + this.transitionToState( + [ConnectivityState.READY], + ConnectivityState.TRANSIENT_FAILURE); + for (const listener of this.disconnectListeners) { + listener(); + } } }); if (this.keepaliveWithoutCalls) { From a3ecb4513232557dc9132b81aea9d67ef9544c15 Mon Sep 17 00:00:00 2001 From: Dacio Romero Date: Tue, 1 Mar 2022 14:48:25 -0800 Subject: [PATCH 199/694] grpc-js: Return never from functions that always throw --- packages/grpc-js/src/channel-credentials.ts | 2 +- packages/grpc-js/src/index.ts | 4 ++-- packages/grpc-js/src/resolving-load-balancer.ts | 2 +- packages/grpc-js/src/server.ts | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index 94b66c1bd..fd9d7b571 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -173,7 +173,7 @@ class InsecureChannelCredentialsImpl extends ChannelCredentials { super(callCredentials); } - compose(callCredentials: CallCredentials): ChannelCredentials { + compose(callCredentials: CallCredentials): never { throw new Error('Cannot compose insecure credentials'); } diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 4bf1d9363..fa782aaa4 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -207,13 +207,13 @@ export type Call = /* eslint-disable @typescript-eslint/no-explicit-any */ -export const loadObject = (value: any, options: any) => { +export const loadObject = (value: any, options: any): never => { throw new Error( 'Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead' ); }; -export const load = (filename: any, format: any, options: any) => { +export const load = (filename: any, format: any, options: any): never => { throw new Error( 'Not available in this library. Use @grpc/proto-loader and loadPackageDefinition instead' ); diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 5bc861283..907067dfc 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -312,7 +312,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig | null - ) { + ): never { throw new Error('updateAddressList not supported on ResolvingLoadBalancer'); } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 33057defa..e0074807e 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -231,7 +231,7 @@ export class Server { } - addProtoService(): void { + addProtoService(): never { throw new Error('Not implemented. Use addService() instead'); } @@ -311,7 +311,7 @@ export class Server { }); } - bind(port: string, creds: ServerCredentials): void { + bind(port: string, creds: ServerCredentials): never { throw new Error('Not implemented. Use bindAsync() instead'); } @@ -696,7 +696,7 @@ export class Server { } } - addHttp2Port(): void { + addHttp2Port(): never { throw new Error('Not yet implemented'); } From 2062062a5cb6741818d31fa3d9c516741389d236 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 19 Jan 2022 10:17:51 -0800 Subject: [PATCH 200/694] grpc-js: Add outlier detection LB policy --- packages/grpc-js/gulpfile.ts | 1 + packages/grpc-js/src/duration.ts | 36 ++ packages/grpc-js/src/experimental.ts | 3 +- packages/grpc-js/src/index.ts | 2 + .../src/load-balancer-outlier-detection.ts | 569 ++++++++++++++++++ packages/grpc-js/src/service-config.ts | 6 +- packages/grpc-js/src/subchannel-interface.ts | 2 +- .../grpc-js/test/test-outlier-detection.ts | 121 ++++ 8 files changed, 733 insertions(+), 7 deletions(-) create mode 100644 packages/grpc-js/src/duration.ts create mode 100644 packages/grpc-js/src/load-balancer-outlier-detection.ts create mode 100644 packages/grpc-js/test/test-outlier-detection.ts diff --git a/packages/grpc-js/gulpfile.ts b/packages/grpc-js/gulpfile.ts index 6d8d20943..d85900364 100644 --- a/packages/grpc-js/gulpfile.ts +++ b/packages/grpc-js/gulpfile.ts @@ -67,6 +67,7 @@ const compile = checkTask(() => execNpmCommand('compile')); const copyTestFixtures = checkTask(() => ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`)); const runTests = checkTask(() => { + process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION = 'true'; return gulp.src(`${outDir}/test/**/*.js`) .pipe(mocha({reporter: 'mocha-jenkins-reporter', require: ['ts-node/register']})); diff --git a/packages/grpc-js/src/duration.ts b/packages/grpc-js/src/duration.ts new file mode 100644 index 000000000..278c9ae54 --- /dev/null +++ b/packages/grpc-js/src/duration.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2022 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. + * + */ + +export interface Duration { + seconds: number; + nanos: number; +} + +export function msToDuration(millis: number): Duration { + return { + seconds: (millis / 1000) | 0, + nanos: (millis % 1000) * 1_000_000 | 0 + }; +} + +export function durationToMs(duration: Duration): number { + return (duration.seconds * 1000 + duration.nanos / 1_000_000) | 0; +} + +export function isDuration(value: any): value is Duration { + return (typeof value.seconds === 'number') && (typeof value.nanos === 'number'); +} \ No newline at end of file diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 7356cb81a..a9d76406b 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -6,7 +6,8 @@ export { ConfigSelector, } from './resolver'; export { GrpcUri, uriToString } from './uri-parser'; -export { ServiceConfig, Duration } from './service-config'; +export { Duration } from './duration'; +export { ServiceConfig } from './service-config'; export { BackoffTimeout } from './backoff-timeout'; export { LoadBalancer, diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 4bf1d9363..b7771be29 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -273,6 +273,7 @@ import * as resolver_uds from './resolver-uds'; import * as resolver_ip from './resolver-ip'; import * as load_balancer_pick_first from './load-balancer-pick-first'; import * as load_balancer_round_robin from './load-balancer-round-robin'; +import * as load_balancer_outlier_detection from './load-balancer-outlier-detection'; import * as channelz from './channelz'; const clientVersion = require('../../package.json').version; @@ -284,5 +285,6 @@ const clientVersion = require('../../package.json').version; resolver_ip.setup(); load_balancer_pick_first.setup(); load_balancer_round_robin.setup(); + load_balancer_outlier_detection.setup(); channelz.setup(); })(); diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts new file mode 100644 index 000000000..ffca1c68e --- /dev/null +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -0,0 +1,569 @@ +/* + * Copyright 2022 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 { ChannelOptions, connectivityState, StatusObject } from "."; +import { Call } from "./call-stream"; +import { ConnectivityState } from "./connectivity-state"; +import { Status } from "./constants"; +import { durationToMs, isDuration, msToDuration } from "./duration"; +import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental"; +import { BaseFilter, Filter, FilterFactory } from "./filter"; +import { getFirstUsableConfig, LoadBalancer, LoadBalancingConfig, validateLoadBalancingConfig } from "./load-balancer"; +import { ChildLoadBalancerHandler } from "./load-balancer-child-handler"; +import { PickArgs, Picker, PickResult, PickResultType, QueuePicker, UnavailablePicker } from "./picker"; +import { Subchannel } from "./subchannel"; +import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address"; +import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface"; + + +const TYPE_NAME = 'outlier_detection'; + +const OUTLIER_DETECTION_ENABLED = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION === 'true'; + +interface SuccessRateEjectionConfig { + readonly stdev_factor: number; + readonly enforcement_percentage: number; + readonly minimum_hosts: number; + readonly request_volume: number; +} + +interface FailurePercentageEjectionConfig { + readonly threshold: number; + readonly enforcement_percentage: number; + readonly minimum_hosts: number; + readonly request_volume: number; +} + +const defaultSuccessRateEjectionConfig: SuccessRateEjectionConfig = { + stdev_factor: 1900, + enforcement_percentage: 100, + minimum_hosts: 5, + request_volume: 100 +}; + +const defaultFailurePercentageEjectionConfig: FailurePercentageEjectionConfig = { + threshold: 85, + enforcement_percentage: 100, + minimum_hosts: 5, + request_volume: 50 +} + +type TypeofValues = 'object' | 'boolean' | 'function' | 'number' | 'string' | 'undefined'; + +function validateFieldType(obj: any, fieldName: string, expectedType: TypeofValues, objectName?: string) { + if (fieldName in obj && typeof obj[fieldName] !== expectedType) { + const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; + throw new Error(`outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[fieldName]}`); + } +} + +function validatePositiveDuration(obj: any, fieldName: string, objectName?: string) { + const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; + if (fieldName in obj) { + if (!isDuration(obj[fieldName])) { + throw new Error(`outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[fieldName]}`); + } + if (!(obj[fieldName].seconds >= 0 && obj[fieldName].seconds <= 315_576_000_000 && obj[fieldName].nanos >= 0 && obj[fieldName].nanos <= 999_999_999)) { + throw new Error(`outlier detection config ${fullFieldName} parse error: values out of range for non-negative Duaration`); + } + } +} + +function validatePercentage(obj: any, fieldName: string, objectName?: string) { + const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; + validateFieldType(obj, fieldName, 'number', objectName); + if (fieldName in obj && !(obj[fieldName] >= 0 && obj[fieldName] <= 100)) { + throw new Error(`outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)`); + } +} + +export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig { + constructor( + private readonly intervalMs: number, + private readonly baseEjectionTimeMs: number, + private readonly maxEjectionTimeMs: number, + private readonly maxEjectionPercent: number, + private readonly successRateEjection: SuccessRateEjectionConfig | null, + private readonly failurePercentageEjection: FailurePercentageEjectionConfig | null, + private readonly childPolicy: LoadBalancingConfig[] + ) {} + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + return { + interval: msToDuration(this.intervalMs), + base_ejection_time: msToDuration(this.baseEjectionTimeMs), + max_ejection_time: msToDuration(this.maxEjectionTimeMs), + max_ejection_percent: this.maxEjectionPercent, + success_rate_ejection: this.successRateEjection, + failure_percentage_ejection: this.failurePercentageEjection, + child_policy: this.childPolicy.map(policy => policy.toJsonObject()) + }; + } + + getIntervalMs(): number { + return this.intervalMs; + } + getBaseEjectionTimeMs(): number { + return this.baseEjectionTimeMs; + } + getMaxEjectionTimeMs(): number { + return this.maxEjectionTimeMs; + } + getMaxEjectionPercent(): number { + return this.maxEjectionPercent; + } + getSuccessRateEjectionConfig(): SuccessRateEjectionConfig | null { + return this.successRateEjection; + } + getFailurePercentageEjectionConfig(): FailurePercentageEjectionConfig | null { + return this.failurePercentageEjection; + } + getChildPolicy(): LoadBalancingConfig[] { + return this.childPolicy; + } + static createFromJson(obj: any): OutlierDetectionLoadBalancingConfig { + validatePositiveDuration(obj, 'interval'); + validatePositiveDuration(obj, 'base_ejection_time'); + validatePositiveDuration(obj, 'max_ejection_time'); + validatePercentage(obj, 'max_ejection_percent'); + if ('success_rate_ejection' in obj) { + if (typeof obj.success_rate_ejection !== 'object') { + throw new Error('outlier detection config success_rate_ejection must be an object'); + } + validateFieldType(obj.success_rate_ejection, 'stdev_factor', 'number', 'success_rate_ejection'); + validatePercentage(obj.success_rate_ejection, 'enforcement_percentage', 'success_rate_ejection'); + validateFieldType(obj.success_rate_ejection, 'minimum_hosts', 'number', 'success_rate_ejection'); + validateFieldType(obj.success_rate_ejection, 'request_volume', 'number', 'success_rate_ejection'); + } + if ('failure_percentage_ejection' in obj) { + if (typeof obj.failure_percentage_ejection !== 'object') { + throw new Error('outlier detection config failure_percentage_ejection must be an object'); + } + validatePercentage(obj.failure_percentage_ejection, 'threshold', 'failure_percentage_ejection'); + validatePercentage(obj.failure_percentage_ejection, 'enforcement_percentage', 'failure_percentage_ejection'); + validateFieldType(obj.failure_percentage_ejection, 'minimum_hosts', 'number', 'failure_percentage_ejection'); + validateFieldType(obj.failure_percentage_ejection, 'request_volume', 'number', 'failure_percentage_ejection'); + } + + return new OutlierDetectionLoadBalancingConfig( + obj.interval ? durationToMs(obj.interval) : 10_000, + obj.base_ejection_time ? durationToMs(obj.base_ejection_time) : 30_000, + obj.max_ejection_time ? durationToMs(obj.max_ejection_time) : 300_000, + obj.max_ejection_percent ?? 10, + obj.success_rate_ejection ? {...defaultSuccessRateEjectionConfig, ...obj.success_rate_ejection} : null, + obj.failure_percentage_ejection ? {...defaultFailurePercentageEjectionConfig, ...obj.failure_percentage_ejection} : null, + obj.child_policy.map(validateLoadBalancingConfig) + ); + } +} + +class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { + private childSubchannelState: ConnectivityState = ConnectivityState.IDLE; + private stateListeners: ConnectivityStateListener[] = []; + private ejected: boolean = false; + private refCount: number = 0; + constructor(childSubchannel: SubchannelInterface, private mapEntry?: MapEntry) { + super(childSubchannel); + childSubchannel.addConnectivityStateListener((subchannel, previousState, newState) => { + this.childSubchannelState = newState; + if (!this.ejected) { + for (const listener of this.stateListeners) { + listener(this, previousState, newState); + } + } + }); + } + + /** + * Add a listener function to be called whenever the wrapper's + * connectivity state changes. + * @param listener + */ + addConnectivityStateListener(listener: ConnectivityStateListener) { + this.stateListeners.push(listener); + } + + /** + * Remove a listener previously added with `addConnectivityStateListener` + * @param listener A reference to a function previously passed to + * `addConnectivityStateListener` + */ + removeConnectivityStateListener(listener: ConnectivityStateListener) { + const listenerIndex = this.stateListeners.indexOf(listener); + if (listenerIndex > -1) { + this.stateListeners.splice(listenerIndex, 1); + } + } + + ref() { + this.child.ref(); + this.refCount += 1; + } + + unref() { + this.child.unref(); + this.refCount -= 1; + if (this.refCount <= 0) { + if (this.mapEntry) { + const index = this.mapEntry.subchannelWrappers.indexOf(this); + if (index >= 0) { + this.mapEntry.subchannelWrappers.splice(index, 1); + } + } + } + } + + eject() { + this.ejected = true; + for (const listener of this.stateListeners) { + listener(this, this.childSubchannelState, ConnectivityState.TRANSIENT_FAILURE); + } + } + + uneject() { + this.ejected = false; + for (const listener of this.stateListeners) { + listener(this, ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState); + } + } + + getMapEntry(): MapEntry | undefined { + return this.mapEntry; + } + + getWrappedSubchannel(): SubchannelInterface { + return this.child; + } +} + +interface CallCountBucket { + success: number; + failure: number; +} + +function createEmptyBucket(): CallCountBucket { + return { + success: 0, + failure: 0 + } +} + +class CallCounter { + private activeBucket: CallCountBucket = createEmptyBucket(); + private inactiveBucket: CallCountBucket = createEmptyBucket(); + addSuccess() { + this.activeBucket.success += 1; + } + addFailure() { + this.activeBucket.failure += 1; + } + switchBuckets() { + this.inactiveBucket = this.activeBucket; + this.activeBucket = createEmptyBucket(); + } + getLastSuccesses() { + return this.inactiveBucket.success; + } + getLastFailures() { + return this.inactiveBucket.failure; + } +} + +interface MapEntry { + counter: CallCounter; + currentEjectionTimestamp: Date | null; + ejectionTimeMultiplier: number; + subchannelWrappers: OutlierDetectionSubchannelWrapper[]; +} + +class OutlierDetectionCounterFilter extends BaseFilter implements Filter { + constructor(private callCounter: CallCounter) { + super(); + } + receiveTrailers(status: StatusObject): StatusObject { + if (status.code === Status.OK) { + this.callCounter.addSuccess(); + } else { + this.callCounter.addFailure(); + } + return status; + } +} + +class OutlierDetectionCounterFilterFactory implements FilterFactory { + constructor(private callCounter: CallCounter) {} + createFilter(callStream: Call): OutlierDetectionCounterFilter { + return new OutlierDetectionCounterFilter(this.callCounter); + } + +} + +class OutlierDetectionPicker implements Picker { + constructor(private wrappedPicker: Picker) {} + pick(pickArgs: PickArgs): PickResult { + const wrappedPick = this.wrappedPicker.pick(pickArgs); + if (wrappedPick.pickResultType === PickResultType.COMPLETE) { + const subchannelWrapper = wrappedPick.subchannel as OutlierDetectionSubchannelWrapper; + const mapEntry = subchannelWrapper.getMapEntry(); + if (mapEntry) { + return { + ...wrappedPick, + subchannel: subchannelWrapper.getWrappedSubchannel(), + extraFilterFactories: [...wrappedPick.extraFilterFactories, new OutlierDetectionCounterFilterFactory(mapEntry.counter)] + }; + } else { + return wrappedPick; + } + } else { + return wrappedPick; + } + } + +} + +export class OutlierDetectionLoadBalancer implements LoadBalancer { + private childBalancer: ChildLoadBalancerHandler; + private addressMap: Map = new Map(); + private latestConfig: OutlierDetectionLoadBalancingConfig | null = null; + private ejectionTimer: NodeJS.Timer; + + constructor(channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { + createSubchannel: (subchannelAddress: SubchannelAddress, subchannelArgs: ChannelOptions) => { + const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); + const mapEntry = this.addressMap.get(subchannelAddressToString(subchannelAddress)); + const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry); + mapEntry?.subchannelWrappers.push(subchannelWrapper); + return subchannelWrapper; + }, + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + if (connectivityState === ConnectivityState.READY) { + channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker)); + } else { + channelControlHelper.updateState(connectivityState, picker); + } + } + })); + this.ejectionTimer = setInterval(() => {}, 0); + clearInterval(this.ejectionTimer); + } + + private getCurrentEjectionPercent() { + let ejectionCount = 0; + for (const mapEntry of this.addressMap.values()) { + if (mapEntry.currentEjectionTimestamp !== null) { + ejectionCount += 1; + } + } + return (ejectionCount * 100) / this.addressMap.size; + } + + private runSuccessRateCheck(ejectionTimestamp: Date) { + if (!this.latestConfig) { + return; + } + const successRateConfig = this.latestConfig.getSuccessRateEjectionConfig(); + if (!successRateConfig) { + return; + } + // Step 1 + const targetRequestVolume = successRateConfig.request_volume; + let addresesWithTargetVolume = 0; + const successRates: number[] = [] + for (const mapEntry of this.addressMap.values()) { + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + if (successes + failures >= targetRequestVolume) { + addresesWithTargetVolume += 1; + successRates.push(successes/(successes + failures)); + } + } + if (addresesWithTargetVolume < successRateConfig.minimum_hosts) { + return; + } + + // Step 2 + const successRateMean = successRates.reduce((a, b) => a + b); + let successRateVariance = 0; + for (const rate of successRates) { + const deviation = rate - successRateMean; + successRateVariance += deviation * deviation; + } + const successRateStdev = Math.sqrt(successRateVariance); + const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000); + + // Step 3 + for (const mapEntry of this.addressMap.values()) { + // Step 3.i + if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { + break; + } + // Step 3.ii + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + if (successes + failures < targetRequestVolume) { + continue; + } + // Step 3.iii + const successRate = successes / (successes + failures); + if (successRate < ejectionThreshold) { + const randomNumber = Math.random() * 100; + if (randomNumber < successRateConfig.enforcement_percentage) { + this.eject(mapEntry, ejectionTimestamp); + } + } + } + } + + private runFailurePercentageCheck(ejectionTimestamp: Date) { + if (!this.latestConfig) { + return; + } + const failurePercentageConfig = this.latestConfig.getFailurePercentageEjectionConfig() + if (!failurePercentageConfig) { + return; + } + // Step 1 + if (this.addressMap.size < failurePercentageConfig.minimum_hosts) { + return; + } + + // Step 2 + for (const mapEntry of this.addressMap.values()) { + // Step 2.i + if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { + break; + } + // Step 2.ii + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + if (successes + failures < failurePercentageConfig.request_volume) { + continue; + } + // Step 2.iii + const failurePercentage = (failures * 100) / (failures + successes); + if (failurePercentage > failurePercentageConfig.threshold) { + const randomNumber = Math.random() * 100; + if (randomNumber < failurePercentageConfig.enforcement_percentage) { + this.eject(mapEntry, ejectionTimestamp); + } + } + } + } + + private eject(mapEntry: MapEntry, ejectionTimestamp: Date) { + mapEntry.currentEjectionTimestamp = new Date(); + mapEntry.ejectionTimeMultiplier += 1; + for (const subchannelWrapper of mapEntry.subchannelWrappers) { + subchannelWrapper.eject(); + } + } + + private uneject(mapEntry: MapEntry) { + mapEntry.currentEjectionTimestamp = null; + for (const subchannelWrapper of mapEntry.subchannelWrappers) { + subchannelWrapper.uneject(); + } + } + + private runChecks() { + const ejectionTimestamp = new Date(); + + for (const mapEntry of this.addressMap.values()) { + mapEntry.counter.switchBuckets(); + } + + if (!this.latestConfig) { + return; + } + + this.runSuccessRateCheck(ejectionTimestamp); + this.runFailurePercentageCheck(ejectionTimestamp); + + for (const mapEntry of this.addressMap.values()) { + if (mapEntry.currentEjectionTimestamp === null) { + if (mapEntry.ejectionTimeMultiplier > 0) { + mapEntry.ejectionTimeMultiplier -= 1; + } + } else { + const baseEjectionTimeMs = this.latestConfig.getBaseEjectionTimeMs(); + const maxEjectionTimeMs = this.latestConfig.getMaxEjectionTimeMs(); + const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime()); + returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs))); + if (returnTime < new Date()) { + this.uneject(mapEntry); + } + } + } + } + + updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) { + return; + } + const subchannelAddresses = new Set(); + for (const address of addressList) { + subchannelAddresses.add(subchannelAddressToString(address)); + } + for (const address of subchannelAddresses) { + if (!this.addressMap.has(address)) { + this.addressMap.set(address, { + counter: new CallCounter(), + currentEjectionTimestamp: null, + ejectionTimeMultiplier: 0, + subchannelWrappers: [] + }); + } + } + for (const key of this.addressMap.keys()) { + if (!subchannelAddresses.has(key)) { + this.addressMap.delete(key); + } + } + const childPolicy: LoadBalancingConfig = getFirstUsableConfig( + lbConfig.getChildPolicy(), + true + ); + this.childBalancer.updateAddressList(addressList, childPolicy, attributes); + + if (this.latestConfig === null || this.latestConfig.getIntervalMs() !== lbConfig.getIntervalMs()) { + clearInterval(this.ejectionTimer); + this.ejectionTimer = setInterval(() => this.runChecks(), lbConfig.getIntervalMs()); + } + this.latestConfig = lbConfig; + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + this.childBalancer.destroy(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + if (OUTLIER_DETECTION_ENABLED) { + registerLoadBalancerType(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig); + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index 3f0a00868..f310597e9 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -27,6 +27,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as os from 'os'; +import { Duration } from './duration'; import { LoadBalancingConfig, validateLoadBalancingConfig, @@ -37,11 +38,6 @@ export interface MethodConfigName { method?: string; } -export interface Duration { - seconds: number; - nanos: number; -} - export interface MethodConfig { name: MethodConfigName[]; waitForReady?: boolean; diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts index cdf99176d..082a8b3c0 100644 --- a/packages/grpc-js/src/subchannel-interface.ts +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -50,7 +50,7 @@ export interface SubchannelInterface { } export abstract class BaseSubchannelWrapper implements SubchannelInterface { - constructor(private child: SubchannelInterface) {} + constructor(protected child: SubchannelInterface) {} getConnectivityState(): ConnectivityState { return this.child.getConnectivityState(); diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts new file mode 100644 index 000000000..977a3058f --- /dev/null +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -0,0 +1,121 @@ +/* + * Copyright 2022 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 * as assert from 'assert'; +import * as path from 'path'; +import * as grpc from '../src'; +import { loadProtoFile } from './common'; + +function multiDone(done: Mocha.Done, target: number) { + let count = 0; + return (error?: any) => { + if (error) { + done(error); + } + count++; + if (count >= target) { + done(); + } + } +} + +const defaultOutlierDetectionServiceConfig = { + methodConfig: [], + loadBalancingConfig: [ + { + outlier_detection: { + success_rate_ejection: {}, + failure_percentage_ejection: {}, + child_policy: [{round_robin: {}}] + } + } + ] +}; + +const defaultOutlierDetectionServiceConfigString = JSON.stringify(defaultOutlierDetectionServiceConfig); + +const goodService = { + echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + callback(null, call.request) + } +}; + +const badService = { + echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + callback({ + code: grpc.status.PERMISSION_DENIED, + details: 'Permission denied' + }) + } +} + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const EchoService = loadProtoFile(protoFile) + .EchoService as grpc.ServiceClientConstructor; + +describe('Outlier detection', () => { + const GOOD_PORTS = 4; + let goodServer: grpc.Server; + let badServer: grpc.Server; + const goodPorts: number[] = []; + let badPort: number; + before(done => { + const eachDone = multiDone(() => { + goodServer.start(); + badServer.start(); + done(); + }, GOOD_PORTS + 1); + goodServer = new grpc.Server(); + goodServer.addService(EchoService.service, goodService); + for (let i = 0; i < GOOD_PORTS; i++) { + goodServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + eachDone(error); + return; + } + goodPorts.push(port); + eachDone(); + }); + } + badServer = new grpc.Server(); + badServer.addService(EchoService.service, badService); + badServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + eachDone(error); + return; + } + badPort = port; + eachDone(); + }); + }); + after(() => { + goodServer.forceShutdown(); + badServer.forceShutdown(); + }); + + it('Should allow normal operation with one server', done => { + const client = new EchoService(`localhost:${goodPorts[0]}`, grpc.credentials.createInsecure(), {'grpc.service_config': defaultOutlierDetectionServiceConfigString}); + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); +}); \ No newline at end of file From 5a601b0f78dd124fc0a9056f43d2e4d85d5c1246 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 14 Mar 2022 11:55:38 -0700 Subject: [PATCH 201/694] grpc-js: Consistently log subchannel and call IDs in traces --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/call-stream.ts | 4 ++++ packages/grpc-js/src/channel.ts | 17 +++++++++++------ packages/grpc-js/src/subchannel.ts | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index f41de376d..f617223bc 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.7", + "version": "1.5.8", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index f2e045aef..601218886 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -795,6 +795,10 @@ export class Http2CallStream implements Call { this.filterStack.push(extraFilters); } + getCallNumber() { + return this.callNumber; + } + startRead() { /* If the stream has ended with an error, we should not emit any more * messages and we should communicate that the stream has ended */ diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 8fa2c592a..9d73c5df8 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -404,11 +404,16 @@ export class ChannelImplementation implements Channel { metadata: callMetadata, extraPickInfo: callConfig.pickInformation, }); + const subchannelString = pickResult.subchannel ? + '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : + '' + pickResult.subchannel; this.trace( - 'Pick result: ' + + 'Pick result for call [' + + callStream.getCallNumber() + + ']: ' + PickResultType[pickResult.pickResultType] + ' subchannel: ' + - pickResult.subchannel?.getAddress() + + subchannelString + ' status: ' + pickResult.status?.code + ' ' + @@ -433,7 +438,7 @@ export class ChannelImplementation implements Channel { log( LogVerbosity.ERROR, 'Error: COMPLETE pick result subchannel ' + - pickResult.subchannel!.getAddress() + + subchannelString + ' has state ' + ConnectivityState[pickResult.subchannel!.getConnectivityState()] ); @@ -480,7 +485,7 @@ export class ChannelImplementation implements Channel { * tryPick */ this.trace( 'Failed to start call on picked subchannel ' + - pickResult.subchannel!.getAddress() + + subchannelString + ' with error ' + (error as Error).message + '. Retrying pick', @@ -490,7 +495,7 @@ export class ChannelImplementation implements Channel { } else { this.trace( 'Failed to start call on picked subchanel ' + - pickResult.subchannel!.getAddress() + + subchannelString + ' with error ' + (error as Error).message + '. Ending call', @@ -509,7 +514,7 @@ export class ChannelImplementation implements Channel { * block above */ this.trace( 'Picked subchannel ' + - pickResult.subchannel!.getAddress() + + subchannelString + ' has state ' + ConnectivityState[subchannelState] + ' after metadata filters. Retrying pick', diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 24f02144d..22c243fed 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -857,7 +857,7 @@ export class Subchannel { logging.trace( LogVerbosity.DEBUG, 'call_stream', - 'Starting stream on subchannel ' + + 'Starting stream [' + callStream.getCallNumber() + '] on subchannel ' + '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' with headers\n' + From 109020ef02a443a76ad109a9cd520b49fca3e8af Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 15 Mar 2022 12:07:11 -0700 Subject: [PATCH 202/694] Add credentials scope in oauth2_auth_token interop test --- test/interop/interop_client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/interop/interop_client.js b/test/interop/interop_client.js index d7df128da..57f4f1846 100644 --- a/test/interop/interop_client.js +++ b/test/interop/interop_client.js @@ -484,7 +484,7 @@ function getApplicationCreds(scope, callback) { } function getOauth2Creds(scope, callback) { - (new GoogleAuth()).getAccessToken().then((token) => { + (new GoogleAuth({scopes: scope})).getAccessToken().then((token) => { var updateMd = function(service_url, callback) { var metadata = new grpc.Metadata(); metadata.add('authorization', 'Bearer ' + token); From 97584dcf3193662388802abba1ffb224953bd1fa Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 16 Mar 2022 11:27:05 -0700 Subject: [PATCH 203/694] grpc-js: Add channel construction stacktrace traces --- doc/environment_variables.md | 1 + packages/grpc-js/src/channel.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/environment_variables.md b/doc/environment_variables.md index 2eb429f92..0237dff7e 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -39,6 +39,7 @@ can be set. - `subchannel_refcount` - Traces subchannel refcount changes. Includes per-call logs. - `subchannel_flowctrl` - Traces HTTP/2 flow control. Includes per-call logs. - `subchannel_internals` - Traces HTTP/2 session state. Includes per-call logs. + - `channel_stacktrace` - Traces channel construction events with stack traces. The following tracers are added by the `@grpc/grpc-js-xds` library: - `cds_balancer` - Traces the CDS load balancing policy diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 9d73c5df8..d1c9bd028 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -335,6 +335,8 @@ export class ChannelImplementation implements Channel { new CompressionFilterFactory(this, this.options), ]); this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2)); + const error = new Error(); + trace(LogVerbosity.DEBUG, 'channel_stacktrace', '(' + this.channelzRef.id + ') ' + 'Channel constructed \n' + error.stack?.substring(error.stack.indexOf('\n')+1)); } private getChannelzInfo(): ChannelInfo { From 39f027e284c9c23591fad25eb2f4d6b80ab26e2e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 16 Mar 2022 11:34:33 -0700 Subject: [PATCH 204/694] grpc-js: Trace call end when actually ending the call --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/call-stream.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index f617223bc..c15678c83 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.8", + "version": "1.5.9", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 601218886..4eff39eb8 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -312,6 +312,13 @@ export class Http2CallStream implements Call { const filteredStatus = this.filterStack.receiveTrailers( this.finalStatus! ); + this.trace( + 'ended with status: code=' + + filteredStatus.code + + ' details="' + + filteredStatus.details + + '"' + ); this.statusWatchers.forEach(watcher => watcher(filteredStatus)); /* We delay the actual action of bubbling up the status to insulate the * cleanup code in this class from any errors that may be thrown in the @@ -346,13 +353,6 @@ export class Http2CallStream implements Call { /* If the status is OK and a new status comes in (e.g. from a * deserialization failure), that new status takes priority */ if (this.finalStatus === null || this.finalStatus.code === Status.OK) { - this.trace( - 'ended with status: code=' + - status.code + - ' details="' + - status.details + - '"' - ); this.finalStatus = status; this.maybeOutputStatus(); } From 81e08e84df4cfda5e5479f8455a86bd3d27964e7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 16 Mar 2022 12:39:36 -0700 Subject: [PATCH 205/694] grpc-js: Transparently retry session destroyed error --- packages/grpc-js/src/channel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 9d73c5df8..8ca11dd58 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -466,9 +466,9 @@ export class ChannelImplementation implements Channel { callConfig.onCommitted?.(); pickResult.onCallStarted?.(); } catch (error) { - if ( - (error as NodeJS.ErrnoException).code === - 'ERR_HTTP2_GOAWAY_SESSION' + const errorCode = (error as NodeJS.ErrnoException).code; + if (errorCode === 'ERR_HTTP2_GOAWAY_SESSION' || + errorCode === 'ERR_HTTP2_INVALID_SESSION' ) { /* An error here indicates that something went wrong with * the picked subchannel's http2 stream right before we From b5b0703bcd7006eef554f977070827e881863d08 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 18 Mar 2022 12:40:30 -0700 Subject: [PATCH 206/694] grpc-js-xds: Add outlier detection configuration handling --- packages/grpc-js-xds/src/environment.ts | 3 +- packages/grpc-js-xds/src/load-balancer-cds.ts | 62 ++++++++++++++++++- packages/grpc-js-xds/src/load-balancer-eds.ts | 34 ++++++++-- .../src/xds-stream-state/cds-state.ts | 48 ++++++++++++++ packages/grpc-js/src/experimental.ts | 5 +- .../src/load-balancer-outlier-detection.ts | 49 ++++++++++----- 6 files changed, 177 insertions(+), 24 deletions(-) diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index e1c2d8158..b8b518da4 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -15,4 +15,5 @@ * */ -export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; \ No newline at end of file +export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; +export const EXPERIMENTAL_OUTLIER_DETECTION = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION === 'true'; \ No newline at end of file diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 4829edd44..a6dbf42f0 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -25,8 +25,14 @@ import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; import LoadBalancingConfig = experimental.LoadBalancingConfig; +import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; +import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; +import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; import { EdsLoadBalancingConfig } from './load-balancer-eds'; import { Watcher } from './xds-stream-state/xds-stream-state'; +import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; +import { Duration__Output } from './generated/google/protobuf/Duration'; +import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; const TRACER_NAME = 'cds_balancer'; @@ -64,6 +70,52 @@ export class CdsLoadBalancingConfig implements LoadBalancingConfig { } } +function durationToMs(duration: Duration__Output): number { + return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; +} + +function translateOutlierDetectionConfig(outlierDetection: OutlierDetection__Output | null): OutlierDetectionLoadBalancingConfig | undefined { + if (!EXPERIMENTAL_OUTLIER_DETECTION) { + return undefined; + } + if (!outlierDetection) { + /* No-op outlier detection config, with max possible interval and no + * ejection criteria configured. */ + return new OutlierDetectionLoadBalancingConfig(~(1<<31), null, null, null, null, null, []); + } + let successRateConfig: Partial | null = null; + /* Success rate ejection is enabled by default, so we only disable it if + * enforcing_success_rate is set and it has the value 0 */ + if (!outlierDetection.enforcing_success_rate || outlierDetection.enforcing_success_rate.value > 0) { + successRateConfig = { + enforcement_percentage: outlierDetection.enforcing_success_rate?.value, + minimum_hosts: outlierDetection.success_rate_minimum_hosts?.value, + request_volume: outlierDetection.success_rate_request_volume?.value, + stdev_factor: outlierDetection.success_rate_stdev_factor?.value + }; + } + let failurePercentageConfig: Partial | null = null; + /* Failure percentage ejection is disabled by default, so we only enable it + * if enforcing_failure_percentage is set and it has a value greater than 0 */ + if (outlierDetection.enforcing_failure_percentage && outlierDetection.enforcing_failure_percentage.value > 0) { + failurePercentageConfig = { + enforcement_percentage: outlierDetection.enforcing_failure_percentage.value, + minimum_hosts: outlierDetection.failure_percentage_minimum_hosts?.value, + request_volume: outlierDetection.failure_percentage_request_volume?.value, + threshold: outlierDetection.failure_percentage_threshold?.value + } + } + return new OutlierDetectionLoadBalancingConfig( + outlierDetection.interval ? durationToMs(outlierDetection.interval) : null, + outlierDetection.base_ejection_time ? durationToMs(outlierDetection.base_ejection_time) : null, + outlierDetection.max_ejection_time ? durationToMs(outlierDetection.max_ejection_time) : null, + outlierDetection.max_ejection_percent?.value ?? null, + successRateConfig, + failurePercentageConfig, + [] + ); +} + export class CdsLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; private watcher: Watcher; @@ -90,7 +142,15 @@ export class CdsLoadBalancer implements LoadBalancer { * used for load reporting as for other xDS operations. Setting * lrsLoadReportingServerName to the empty string sets that behavior. * Otherwise, if the field is omitted, load reporting is disabled. */ - const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig(update.name, [], [], update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, update.lrs_server?.self ? '' : undefined, maxConcurrentRequests); + const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig( + /* cluster= */ update.name, + /* localityPickingPolicy= */ [], + /* endpointPickingPolicy= */ [], + /* edsServiceName= */ update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, + /* lrsLoadReportingServerName= */update.lrs_server?.self ? '' : undefined, + /* maxConcurrentRequests= */ maxConcurrentRequests, + /* outlierDetection= */ translateOutlierDetectionConfig(update.outlier_detection) + ); trace('Child update EDS config: ' + JSON.stringify(edsConfig)); this.childBalancer.updateAddressList( [], diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index 55ad714a9..e7aac0571 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -38,6 +38,8 @@ import Filter = experimental.Filter; import BaseFilter = experimental.BaseFilter; import FilterFactory = experimental.FilterFactory; import CallStream = experimental.CallStream; +import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; +import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; const TRACER_NAME = 'eds_balancer'; @@ -71,12 +73,15 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { if (this.lrsLoadReportingServerName !== undefined) { jsonObj.lrs_load_reporting_server_name = this.lrsLoadReportingServerName; } + if (this.outlierDetection !== undefined) { + jsonObj.outlier_detection = this.outlierDetection.toJsonObject(); + } return { [TYPE_NAME]: jsonObj }; } - constructor(private cluster: string, private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string, maxConcurrentRequests?: number) { + constructor(private cluster: string, private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string, maxConcurrentRequests?: number, private outlierDetection?: OutlierDetectionLoadBalancingConfig) { this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; } @@ -104,6 +109,10 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { return this.maxConcurrentRequests; } + getOutlierDetection() { + return this.outlierDetection; + } + static createFromJson(obj: any): EdsLoadBalancingConfig { if (!('cluster' in obj && typeof obj.cluster === 'string')) { throw new Error('eds config must have a string field cluster'); @@ -123,7 +132,17 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { if ('max_concurrent_requests' in obj && (!obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { throw new Error('eds config max_concurrent_requests must be a number if provided'); } - return new EdsLoadBalancingConfig(obj.cluster, obj.locality_picking_policy.map(validateLoadBalancingConfig), obj.endpoint_picking_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name, obj.max_concurrent_requests); + let validatedOutlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined = undefined; + if (EXPERIMENTAL_OUTLIER_DETECTION) { + if ('outlier_detection' in obj) { + const outlierDetectionConfig = validateLoadBalancingConfig(obj.outlier_detection); + if (!(outlierDetectionConfig instanceof OutlierDetectionLoadBalancingConfig)) { + throw new Error('eds config outlier_detection must be a valid outlier detection config if provided'); + } + validatedOutlierDetectionConfig = outlierDetectionConfig; + } + } + return new EdsLoadBalancingConfig(obj.cluster, obj.locality_picking_policy.map(validateLoadBalancingConfig), obj.endpoint_picking_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name, obj.max_concurrent_requests, validatedOutlierDetectionConfig); } } @@ -449,10 +468,15 @@ export class EdsLoadBalancer implements LoadBalancer { } } + const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); + let outlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined; + if (EXPERIMENTAL_OUTLIER_DETECTION) { + outlierDetectionConfig = this.lastestConfig.getOutlierDetection()?.copyWithChildPolicy([weightedTargetConfig]); + } + const priorityChildConfig = outlierDetectionConfig ?? weightedTargetConfig; + priorityChildren.set(newPriorityName, { - config: [ - new WeightedTargetLoadBalancingConfig(childTargets), - ], + config: [priorityChildConfig], }); } /* Contract the priority names array if it is sparse. This config only diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 7737dc961..8c3c4d739 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -16,8 +16,11 @@ */ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; +import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; import { Any__Output } from "../generated/google/protobuf/Any"; +import { Duration__Output } from "../generated/google/protobuf/Duration"; +import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; import { EdsState } from "./eds-state"; import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; @@ -102,6 +105,26 @@ export class CdsState implements XdsStreamState { return Array.from(this.watchers.keys()); } + private validateNonnegativeDuration(duration: Duration__Output | null): boolean { + if (!duration) { + return true; + } + /* The maximum values here come from the official Protobuf documentation: + * https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration + */ + return Number(duration.seconds) >= 0 && + Number(duration.seconds) <= 315_576_000_000 && + duration.nanos >= 0 && + duration.nanos <= 999_999_999; + } + + private validatePercentage(percentage: UInt32Value__Output | null): boolean { + if (!percentage) { + return true; + } + return percentage.value >=0 && percentage.value <= 100; + } + private validateResponse(message: Cluster__Output): boolean { if (message.type !== 'EDS') { return false; @@ -117,6 +140,31 @@ export class CdsState implements XdsStreamState { return false; } } + if (EXPERIMENTAL_OUTLIER_DETECTION) { + if (message.outlier_detection) { + if (!this.validateNonnegativeDuration(message.outlier_detection.interval)) { + return false; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.base_ejection_time)) { + return false; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.max_ejection_time)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.max_ejection_percent)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_success_rate)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.failure_percentage_threshold)) { + return false; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_failure_percentage)) { + return false; + } + } + } return true; } diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index a9d76406b..fcafbeb01 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -6,7 +6,7 @@ export { ConfigSelector, } from './resolver'; export { GrpcUri, uriToString } from './uri-parser'; -export { Duration } from './duration'; +export { Duration, durationToMs } from './duration'; export { ServiceConfig } from './service-config'; export { BackoffTimeout } from './backoff-timeout'; export { @@ -35,4 +35,5 @@ export { Call as CallStream } from './call-stream'; export { Filter, BaseFilter, FilterFactory } from './filter'; export { FilterStackFactory } from './filter-stack'; export { registerAdminService } from './admin'; -export { SubchannelInterface, BaseSubchannelWrapper, ConnectivityStateListener } from './subchannel-interface' +export { SubchannelInterface, BaseSubchannelWrapper, ConnectivityStateListener } from './subchannel-interface'; +export { OutlierDetectionLoadBalancingConfig, SuccessRateEjectionConfig, FailurePercentageEjectionConfig } from './load-balancer-outlier-detection'; diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index ffca1c68e..e69e2ef99 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -34,14 +34,14 @@ const TYPE_NAME = 'outlier_detection'; const OUTLIER_DETECTION_ENABLED = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION === 'true'; -interface SuccessRateEjectionConfig { +export interface SuccessRateEjectionConfig { readonly stdev_factor: number; readonly enforcement_percentage: number; readonly minimum_hosts: number; readonly request_volume: number; } -interface FailurePercentageEjectionConfig { +export interface FailurePercentageEjectionConfig { readonly threshold: number; readonly enforcement_percentage: number; readonly minimum_hosts: number; @@ -92,15 +92,29 @@ function validatePercentage(obj: any, fieldName: string, objectName?: string) { } export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig { + private readonly intervalMs: number; + private readonly baseEjectionTimeMs: number; + private readonly maxEjectionTimeMs: number; + private readonly maxEjectionPercent: number; + private readonly successRateEjection: SuccessRateEjectionConfig | null; + private readonly failurePercentageEjection: FailurePercentageEjectionConfig | null; + constructor( - private readonly intervalMs: number, - private readonly baseEjectionTimeMs: number, - private readonly maxEjectionTimeMs: number, - private readonly maxEjectionPercent: number, - private readonly successRateEjection: SuccessRateEjectionConfig | null, - private readonly failurePercentageEjection: FailurePercentageEjectionConfig | null, + intervalMs: number | null, + baseEjectionTimeMs: number | null, + maxEjectionTimeMs: number | null, + maxEjectionPercent: number | null, + successRateEjection: Partial | null, + failurePercentageEjection: Partial | null, private readonly childPolicy: LoadBalancingConfig[] - ) {} + ) { + this.intervalMs = intervalMs ?? 10_000; + this.baseEjectionTimeMs = baseEjectionTimeMs ?? 30_000; + this.maxEjectionTimeMs = maxEjectionTimeMs ?? 300_000; + this.maxEjectionPercent = maxEjectionPercent ?? 10; + this.successRateEjection = successRateEjection ? {...defaultSuccessRateEjectionConfig, ...successRateEjection} : null; + this.failurePercentageEjection = failurePercentageEjection ? {...defaultFailurePercentageEjectionConfig, ...failurePercentageEjection}: null; + } getLoadBalancerName(): string { return TYPE_NAME; } @@ -137,6 +151,11 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig getChildPolicy(): LoadBalancingConfig[] { return this.childPolicy; } + + copyWithChildPolicy(childPolicy: LoadBalancingConfig[]): OutlierDetectionLoadBalancingConfig { + return new OutlierDetectionLoadBalancingConfig(this.intervalMs, this.baseEjectionTimeMs, this.maxEjectionTimeMs, this.maxEjectionPercent, this.successRateEjection, this.failurePercentageEjection, childPolicy); + } + static createFromJson(obj: any): OutlierDetectionLoadBalancingConfig { validatePositiveDuration(obj, 'interval'); validatePositiveDuration(obj, 'base_ejection_time'); @@ -162,12 +181,12 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig } return new OutlierDetectionLoadBalancingConfig( - obj.interval ? durationToMs(obj.interval) : 10_000, - obj.base_ejection_time ? durationToMs(obj.base_ejection_time) : 30_000, - obj.max_ejection_time ? durationToMs(obj.max_ejection_time) : 300_000, - obj.max_ejection_percent ?? 10, - obj.success_rate_ejection ? {...defaultSuccessRateEjectionConfig, ...obj.success_rate_ejection} : null, - obj.failure_percentage_ejection ? {...defaultFailurePercentageEjectionConfig, ...obj.failure_percentage_ejection} : null, + obj.interval ? durationToMs(obj.interval) : null, + obj.base_ejection_time ? durationToMs(obj.base_ejection_time) : null, + obj.max_ejection_time ? durationToMs(obj.max_ejection_time) : null, + obj.max_ejection_percent ?? null, + obj.success_rate_ejection, + obj.failure_percentage_ejection, obj.child_policy.map(validateLoadBalancingConfig) ); } From 680ff7cc08e12d2bdf57687079fa06ceeafe80c4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 24 Mar 2022 12:45:28 -0700 Subject: [PATCH 207/694] grpc-js: Improve coverage of channelzEnabled checks in server code --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/server.ts | 147 +++++++++++++++++++++------------ 2 files changed, 95 insertions(+), 54 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index c15678c83..50896e963 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.9", + "version": "1.5.10", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 9c28a276f..85f492ff3 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -430,27 +430,36 @@ export class Server { port: boundAddress.port } } - const channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null + let channelzRef: SocketRef; + if (this.channelzEnabled) { + channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }); + this.listenerChildrenTracker.refChild(channelzRef); + } else { + channelzRef = { + kind: 'socket', + id: -1, + name: '' }; - }); - this.listenerChildrenTracker.refChild(channelzRef); + } this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); resolve('port' in boundSubchannelAddress ? boundSubchannelAddress.port : portNum); @@ -499,27 +508,36 @@ export class Server { host: boundAddress.address, port: boundAddress.port }; - const channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null + let channelzRef: SocketRef; + if (this.channelzEnabled) { + channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }); + this.listenerChildrenTracker.refChild(channelzRef); + } else { + channelzRef = { + kind: 'socket', + id: -1, + name: '' }; - }); - this.listenerChildrenTracker.refChild(channelzRef); + } this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); resolve( @@ -599,8 +617,10 @@ export class Server { for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { if (http2Server.listening) { http2Server.close(() => { - this.listenerChildrenTracker.unrefChild(ref); - unregisterChannelzRef(ref); + if (this.channelzEnabled) { + this.listenerChildrenTracker.unrefChild(ref); + unregisterChannelzRef(ref); + } }); } } @@ -616,7 +636,9 @@ export class Server { session.destroy(http2.constants.NGHTTP2_CANCEL as any); }); this.sessions.clear(); - unregisterChannelzRef(this.channelzRef); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } } register( @@ -665,7 +687,9 @@ export class Server { tryShutdown(callback: (error?: Error) => void): void { const wrappedCallback = (error?: Error) => { - unregisterChannelzRef(this.channelzRef); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } callback(error); }; let pendingChecks = 0; @@ -685,8 +709,10 @@ export class Server { if (http2Server.listening) { pendingChecks++; http2Server.close(() => { - this.listenerChildrenTracker.unrefChild(ref); - unregisterChannelzRef(ref); + if (this.channelzEnabled) { + this.listenerChildrenTracker.unrefChild(ref); + unregisterChannelzRef(ref); + } maybeCallback(); }); } @@ -727,8 +753,10 @@ export class Server { 'stream', (stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) => { const channelzSessionInfo = this.sessions.get(stream.session as http2.ServerHttp2Session); - this.callTracker.addCallStarted(); - channelzSessionInfo?.streamTracker.addCallStarted(); + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + channelzSessionInfo?.streamTracker.addCallStarted(); + } const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE]; if ( @@ -743,7 +771,9 @@ export class Server { { endStream: true } ); this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); + if (this.channelzEnabled) { + channelzSessionInfo?.streamTracker.addCallFailed(); + } return; } @@ -786,7 +816,7 @@ export class Server { this.callTracker.addCallFailed(); } }); - if (channelzSessionInfo) { + if (this.channelzEnabled && channelzSessionInfo) { call.once('streamEnd', (success: boolean) => { if (success) { channelzSessionInfo.streamTracker.addCallSucceeded(); @@ -841,8 +871,10 @@ export class Server { } catch (err) { if (!call) { call = new Http2ServerCallStream(stream, null!, this.options); - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed() + if (this.channelzEnabled) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed() + } } if (err.code === undefined) { @@ -860,7 +892,16 @@ export class Server { return; } - const channelzRef = registerChannelzSocket(session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session)); + let channelzRef: SocketRef; + if (this.channelzEnabled) { + channelzRef = registerChannelzSocket(session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session)); + } else { + channelzRef = { + kind: 'socket', + id: -1, + name: '' + } + } const channelzSessionInfo: ChannelzSessionInfo = { ref: channelzRef, From 260aee93dac27bad97172fe38ce2f61ce5886af4 Mon Sep 17 00:00:00 2001 From: Kamil Skalski Date: Fri, 25 Mar 2022 09:22:23 -0500 Subject: [PATCH 208/694] Expose MetadataOptions interface in grpc-js. --- packages/grpc-js/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index ed452547e..2e6fcb5bf 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -48,7 +48,7 @@ import { ServiceClientConstructor, ServiceDefinition, } from './make-client'; -import { Metadata, MetadataValue } from './metadata'; +import { Metadata, MetadataOptions, MetadataValue } from './metadata'; import { Server, UntypedHandleCall, From 8d7d3f3d23e3690b732fb4f8ab3e7666ac72be41 Mon Sep 17 00:00:00 2001 From: Kamil Skalski Date: Fri, 25 Mar 2022 09:22:23 -0500 Subject: [PATCH 209/694] Expose MetadataOptions interface in grpc-js. --- packages/grpc-js/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 2e6fcb5bf..b4855fb59 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -119,7 +119,7 @@ export const credentials = { /**** Metadata ****/ -export { Metadata, MetadataValue }; +export { Metadata, MetadataOptions, MetadataValue }; /**** Constants ****/ From 052af317a3cbd0b8888502c2f232ef76f0af1873 Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Mon, 28 Mar 2022 14:06:04 -0700 Subject: [PATCH 210/694] grpc-js: Avoid surfacing errors without gRPC error codes --- packages/grpc-js/src/call-stream.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 4eff39eb8..e8f312752 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -839,7 +839,16 @@ export class Http2CallStream implements Call { message, flags: context.flags, }; - const cb: WriteCallback = context.callback ?? (() => {}); + const cb: WriteCallback = (error?: Error | null) => { + let code: Status = Status.UNAVAILABLE; + if ((error as NodeJS.ErrnoException)?.code === 'ERR_STREAM_WRITE_AFTER_END') { + code = Status.INTERNAL; + } + if (error) { + this.cancelWithStatus(code, `Write error: ${error.message}`); + } + context.callback?.(); + }; this.isWriteFilterPending = true; this.filterStack.sendMessage(Promise.resolve(writeObj)).then((message) => { this.isWriteFilterPending = false; From 9fcf1659b6f2b36bedb0e337e57d8820be3e835a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 29 Mar 2022 00:15:49 -0700 Subject: [PATCH 211/694] Update version to 1.6.0 --- packages/grpc-js-xds/README.md | 3 ++- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js/package.json | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index 1ac235c40..20bd924a8 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -27,4 +27,5 @@ const client = new MyServiceClient('xds:///example.com:123'); - [xDS Timeouts](https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md) - [xDS Circuit Breaking](https://github.com/grpc/proposal/blob/master/A32-xds-circuit-breaking.md) - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) - - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) \ No newline at end of file + - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) + - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) (experimental, disabled by default, enabled by setting the environment variable `GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION=true`) \ No newline at end of file diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index b4e0a49d7..c797f63f2 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.5.2", + "version": "1.6.0", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -47,7 +47,7 @@ "re2-wasm": "^1.0.1" }, "peerDependencies": { - "@grpc/grpc-js": "~1.5.0" + "@grpc/grpc-js": "~1.6.0" }, "engines": { "node": ">=10.10.0" diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 50896e963..928260be6 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.5.10", + "version": "1.6.0", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From e58371033f6cd086fcf9406526a4eb7f30ccc494 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 1 Apr 2022 10:46:39 -0700 Subject: [PATCH 212/694] grpc-js-xds: Don't stop backoff timers for ADS streams --- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/xds-client.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index c797f63f2..9062403cd 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.6.0", + "version": "1.6.1", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 75a567a54..7a12af1f6 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -688,7 +688,6 @@ export class XdsClient { ack(serviceKind: AdsServiceKind) { /* An ack is the best indication of a successful interaction between the * client and the server, so we can reset the backoff timer here. */ - this.adsBackoff.stop(); this.adsBackoff.reset(); this.updateNames(serviceKind); From 8a503031f3db9d83f45a887b78db15d14fd275c2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 1 Apr 2022 10:01:47 -0700 Subject: [PATCH 213/694] grpc-js: Add support for grpc.dns_min_time_between_resolutions_ms channel arg --- packages/grpc-js/README.md | 1 + packages/grpc-js/package.json | 2 +- packages/grpc-js/src/channel-options.ts | 2 ++ packages/grpc-js/src/resolver-dns.ts | 45 +++++++++++++++++++++---- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 71ec937ed..11a8ca9aa 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -58,6 +58,7 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.enable_http_proxy` - `grpc.default_compression_algorithm` - `grpc.enable_channelz` + - `grpc.dns_min_time_between_resolutions_ms` - `grpc-node.max_session_memory` - `channelOverride` - `channelFactoryOverride` diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 928260be6..ecc3e30cd 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.0", + "version": "1.6.1", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index 688317227..b7fc92fa9 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -43,6 +43,7 @@ export interface ChannelOptions { 'grpc.http_connect_creds'?: string; 'grpc.default_compression_algorithm'?: CompressionAlgorithms; 'grpc.enable_channelz'?: number; + 'grpc.dns_min_time_between_resolutions_ms'?: number; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; @@ -69,6 +70,7 @@ export const recognizedOptions = { 'grpc.max_receive_message_length': true, 'grpc.enable_http_proxy': true, 'grpc.enable_channelz': true, + 'grpc.dns_min_time_between_resolutions_ms': true, 'grpc-node.max_session_memory': true, }; diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 8ad24ed05..c4cb64a74 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -45,6 +45,8 @@ function trace(text: string): void { */ const DEFAULT_PORT = 443; +const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000; + const resolveTxtPromise = util.promisify(dns.resolveTxt); const dnsLookupPromise = util.promisify(dns.lookup); @@ -79,6 +81,12 @@ class DnsResolver implements Resolver { private readonly ipResult: SubchannelAddress[] | null; private readonly dnsHostname: string | null; private readonly port: number | null; + /** + * Minimum time between resolutions, measured as the time between starting + * successive resolution requests. Only applies to successful resolutions. + * Failures are handled by the backoff timer. + */ + private readonly minTimeBetweenResolutionsMs: number; private pendingLookupPromise: Promise | null = null; private pendingTxtPromise: Promise | null = null; private latestLookupResult: TcpSubchannelAddress[] | null = null; @@ -88,6 +96,8 @@ class DnsResolver implements Resolver { private defaultResolutionError: StatusObject; private backoff: BackoffTimeout; private continueResolving = false; + private nextResolutionTimer: NodeJS.Timer; + private isNextResolutionTimerRunning = false; constructor( private target: GrpcUri, private listener: ResolverListener, @@ -134,6 +144,10 @@ class DnsResolver implements Resolver { } }, backoffOptions); this.backoff.unref(); + + this.minTimeBetweenResolutionsMs = channelOptions['grpc.dns_min_time_between_resolutions_ms'] ?? DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS; + this.nextResolutionTimer = setTimeout(() => {}, 0); + clearTimeout(this.nextResolutionTimer); } /** @@ -183,6 +197,7 @@ class DnsResolver implements Resolver { (addressList) => { this.pendingLookupPromise = null; this.backoff.reset(); + this.backoff.stop(); const ip4Addresses: dns.LookupAddress[] = addressList.filter( (addr) => addr.family === 4 ); @@ -229,6 +244,7 @@ class DnsResolver implements Resolver { (err as Error).message ); this.pendingLookupPromise = null; + this.stopNextResolutionTimer(); this.listener.onError(this.defaultResolutionError); } ); @@ -282,17 +298,34 @@ class DnsResolver implements Resolver { } } + private startNextResolutionTimer() { + this.nextResolutionTimer = setTimeout(() => { + this.stopNextResolutionTimer(); + if (this.continueResolving) { + this.startResolutionWithBackoff(); + } + }, this.minTimeBetweenResolutionsMs).unref?.(); + this.isNextResolutionTimerRunning = true; + } + + private stopNextResolutionTimer() { + clearTimeout(this.nextResolutionTimer); + this.isNextResolutionTimerRunning = false; + } + private startResolutionWithBackoff() { this.startResolution(); this.backoff.runOnce(); + this.startNextResolutionTimer(); } updateResolution() { /* If there is a pending lookup, just let it finish. Otherwise, if the - * backoff timer is running, do another lookup when it ends, and if not, - * do another lookup immeidately. */ + * nextResolutionTimer or backoff timer is running, set the + * continueResolving flag to resolve when whichever of those timers + * fires. Otherwise, start resolving immediately. */ if (this.pendingLookupPromise === null) { - if (this.backoff.isRunning()) { + if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) { this.continueResolving = true; } else { this.startResolutionWithBackoff(); @@ -301,9 +334,9 @@ class DnsResolver implements Resolver { } destroy() { - /* Do nothing. There is not a practical way to cancel in-flight DNS - * requests, and after this function is called we can expect that - * updateResolution will not be called again. */ + this.continueResolving = false; + this.backoff.stop(); + this.stopNextResolutionTimer(); } /** From c106d05ac4cfa17746a2d9d6ee357a8056045d68 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 1 Apr 2022 14:31:24 -0700 Subject: [PATCH 214/694] grpc-js: Make backoff timer reset apply to the currently running timer --- packages/grpc-js/src/backoff-timeout.ts | 83 +++++++++++++++++++++---- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/packages/grpc-js/src/backoff-timeout.ts b/packages/grpc-js/src/backoff-timeout.ts index 7f2ab5ebf..dc7be277a 100644 --- a/packages/grpc-js/src/backoff-timeout.ts +++ b/packages/grpc-js/src/backoff-timeout.ts @@ -37,14 +37,47 @@ export interface BackoffOptions { } export class BackoffTimeout { - private initialDelay: number = INITIAL_BACKOFF_MS; - private multiplier: number = BACKOFF_MULTIPLIER; - private maxDelay: number = MAX_BACKOFF_MS; - private jitter: number = BACKOFF_JITTER; + /** + * The delay time at the start, and after each reset. + */ + private readonly initialDelay: number = INITIAL_BACKOFF_MS; + /** + * The exponential backoff multiplier. + */ + private readonly multiplier: number = BACKOFF_MULTIPLIER; + /** + * The maximum delay time + */ + private readonly maxDelay: number = MAX_BACKOFF_MS; + /** + * The maximum fraction by which the delay time can randomly vary after + * applying the multiplier. + */ + private readonly jitter: number = BACKOFF_JITTER; + /** + * The delay time for the next time the timer runs. + */ private nextDelay: number; + /** + * The handle of the underlying timer. If running is false, this value refers + * to an object representing a timer that has ended, but it can still be + * interacted with without error. + */ private timerId: NodeJS.Timer; + /** + * Indicates whether the timer is currently running. + */ private running = false; + /** + * Indicates whether the timer should keep the Node process running if no + * other async operation is doing so. + */ private hasRef = true; + /** + * The time that the currently running timer was started. Only valid if + * running is true. + */ + private startTime: Date = new Date(); constructor(private callback: () => void, options?: BackoffOptions) { if (options) { @@ -66,18 +99,23 @@ export class BackoffTimeout { clearTimeout(this.timerId); } - /** - * Call the callback after the current amount of delay time - */ - runOnce() { - this.running = true; + private runTimer(delay: number) { this.timerId = setTimeout(() => { this.callback(); this.running = false; - }, this.nextDelay); + }, delay); if (!this.hasRef) { this.timerId.unref?.(); } + } + + /** + * Call the callback after the current amount of delay time + */ + runOnce() { + this.running = true; + this.startTime = new Date(); + this.runTimer(this.nextDelay); const nextBackoff = Math.min( this.nextDelay * this.multiplier, this.maxDelay @@ -97,21 +135,44 @@ export class BackoffTimeout { } /** - * Reset the delay time to its initial value. + * Reset the delay time to its initial value. If the timer is still running, + * retroactively apply that reset to the current timer. */ reset() { this.nextDelay = this.initialDelay; + if (this.running) { + const now = new Date(); + const newEndTime = this.startTime; + newEndTime.setMilliseconds(newEndTime.getMilliseconds() + this.nextDelay); + clearTimeout(this.timerId); + if (now < newEndTime) { + this.runTimer(newEndTime.getTime() - now.getTime()); + } else { + this.running = false; + } + } } + /** + * Check whether the timer is currently running. + */ isRunning() { return this.running; } + /** + * Set that while the timer is running, it should keep the Node process + * running. + */ ref() { this.hasRef = true; this.timerId.ref?.(); } + /** + * Set that while the timer is running, it should not keep the Node process + * running. + */ unref() { this.hasRef = false; this.timerId.unref?.(); From f19563d45ce955a343f018e0def877aa8e2b32c1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 4 Apr 2022 09:37:13 -0700 Subject: [PATCH 215/694] grpc-js: Update to version 1.6.2 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index ecc3e30cd..bb8adff8f 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.1", + "version": "1.6.2", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From c80b25e2a52f383a7f6eaf2f09548069ab29623a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 6 Apr 2022 17:15:22 -0700 Subject: [PATCH 216/694] grpc-js: Use real channelz IDs when channelz is disabled --- packages/grpc-js/src/channel.ts | 9 +-- packages/grpc-js/src/channelz.ts | 24 ++++--- packages/grpc-js/src/server.ts | 112 +++++++++++------------------ packages/grpc-js/src/subchannel.ts | 11 +-- 4 files changed, 62 insertions(+), 94 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 635b52d6f..3d014607e 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -219,16 +219,9 @@ export class ChannelImplementation implements Channel { } this.channelzTrace = new ChannelzTrace(); + this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo(), this.channelzEnabled); if (this.channelzEnabled) { - this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); this.channelzTrace.addTrace('CT_INFO', 'Channel created'); - } else { - // Dummy channelz ref that will never be used - this.channelzRef = { - kind: 'channel', - id: -1, - name: '' - }; } if (this.options['grpc.default_authority']) { diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 14c94fd0c..5a7a54760 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -347,31 +347,39 @@ const subchannels: (SubchannelEntry | undefined)[] = []; const servers: (ServerEntry | undefined)[] = []; const sockets: (SocketEntry | undefined)[] = []; -export function registerChannelzChannel(name: string, getInfo: () => ChannelInfo): ChannelRef { +export function registerChannelzChannel(name: string, getInfo: () => ChannelInfo, channelzEnabled: boolean): ChannelRef { const id = getNextId(); const ref: ChannelRef = {id, name, kind: 'channel'}; - channels[id] = { ref, getInfo }; + if (channelzEnabled) { + channels[id] = { ref, getInfo }; + } return ref; } -export function registerChannelzSubchannel(name: string, getInfo:() => SubchannelInfo): SubchannelRef { +export function registerChannelzSubchannel(name: string, getInfo:() => SubchannelInfo, channelzEnabled: boolean): SubchannelRef { const id = getNextId(); const ref: SubchannelRef = {id, name, kind: 'subchannel'}; - subchannels[id] = { ref, getInfo }; + if (channelzEnabled) { + subchannels[id] = { ref, getInfo }; + } return ref; } -export function registerChannelzServer(getInfo: () => ServerInfo): ServerRef { +export function registerChannelzServer(getInfo: () => ServerInfo, channelzEnabled: boolean): ServerRef { const id = getNextId(); const ref: ServerRef = {id, kind: 'server'}; - servers[id] = { ref, getInfo }; + if (channelzEnabled) { + servers[id] = { ref, getInfo }; + } return ref; } -export function registerChannelzSocket(name: string, getInfo: () => SocketInfo): SocketRef { +export function registerChannelzSocket(name: string, getInfo: () => SocketInfo, channelzEnabled: boolean): SocketRef { const id = getNextId(); const ref: SocketRef = {id, name, kind: 'socket'}; - sockets[id] = { ref, getInfo}; + if (channelzEnabled) { + sockets[id] = { ref, getInfo}; + } return ref; } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 829941faa..974c3dfc6 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -161,17 +161,11 @@ export class Server { if (this.options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; } + this.channelzRef = registerChannelzServer(() => this.getChannelzInfo(), this.channelzEnabled); if (this.channelzEnabled) { - this.channelzRef = registerChannelzServer(() => this.getChannelzInfo()); this.channelzTrace.addTrace('CT_INFO', 'Server created'); - this.trace('Server constructed'); - } else { - // Dummy channelz ref that will never be used - this.channelzRef = { - kind: 'server', - id: -1 - }; } + this.trace('Server constructed'); } private getChannelzInfo(): ServerInfo { @@ -431,34 +425,28 @@ export class Server { } } let channelzRef: SocketRef; + channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }, this.channelzEnabled); if (this.channelzEnabled) { - channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null - }; - }); this.listenerChildrenTracker.refChild(channelzRef); - } else { - channelzRef = { - kind: 'socket', - id: -1, - name: '' - }; } this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); @@ -509,34 +497,28 @@ export class Server { port: boundAddress.port }; let channelzRef: SocketRef; + channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null + }; + }, this.channelzEnabled); if (this.channelzEnabled) { - channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null - }; - }); this.listenerChildrenTracker.refChild(channelzRef); - } else { - channelzRef = { - kind: 'socket', - id: -1, - name: '' - }; } this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); @@ -893,15 +875,7 @@ export class Server { } let channelzRef: SocketRef; - if (this.channelzEnabled) { - channelzRef = registerChannelzSocket(session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session)); - } else { - channelzRef = { - kind: 'socket', - id: -1, - name: '' - } - } + channelzRef = registerChannelzSocket(session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session), this.channelzEnabled); const channelzSessionInfo: ChannelzSessionInfo = { ref: channelzRef, diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 800274f99..5ef06b7bd 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -227,16 +227,9 @@ export class Subchannel { this.channelzEnabled = false; } this.channelzTrace = new ChannelzTrace(); + this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); if (this.channelzEnabled) { - this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo()); this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); - } else { - // Dummy channelz ref that will never be used - this.channelzRef = { - kind: 'subchannel', - id: -1, - name: '' - }; } this.trace('Subchannel constructed with options ' + JSON.stringify(options, undefined, 2)); } @@ -484,8 +477,8 @@ export class Subchannel { connectionOptions ); this.session = session; + this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!, this.channelzEnabled); if (this.channelzEnabled) { - this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!); this.childrenTracker.refChild(this.channelzSocketRef); } session.unref(); From 12c58c29237ea48f9278fa3d01f4889a1f558bc1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Apr 2022 17:57:18 -0700 Subject: [PATCH 217/694] grpc-js: Disable per-session memory limit by default --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index bb8adff8f..9fab8d838 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.2", + "version": "1.6.3", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 800274f99..474a05562 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -407,6 +407,12 @@ export class Subchannel { connectionOptions.maxSessionMemory = this.options[ 'grpc-node.max_session_memory' ]; + } else { + /* By default, set a very large max session memory limit, to effectively + * disable enforcement of the limit. Some testing indicates that Node's + * behavior degrades badly when this limit is reached, so we solve that + * by disabling the check entirely. */ + connectionOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; } let addressScheme = 'http://'; if ('secureContext' in connectionOptions) { From 7ac345e4dc7c5122b9a4707613f8a9aa506f800b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 8 Apr 2022 10:13:46 -0700 Subject: [PATCH 218/694] grpc-js: Add more details to keepalive ping tracing --- packages/grpc-js/src/subchannel.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 800274f99..58f995d7b 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -328,6 +328,10 @@ export class Subchannel { logging.trace(LogVerbosity.DEBUG, 'subchannel_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); } + private keepaliveTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'keepalive', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + private handleBackoffTimer() { if (this.continueConnecting) { this.transitionToState( @@ -358,18 +362,15 @@ export class Subchannel { if (this.channelzEnabled) { this.keepalivesSent += 1; } - logging.trace( - LogVerbosity.DEBUG, - 'keepalive', - '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + - 'Sending ping' - ); + this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); this.keepaliveTimeoutId = setTimeout(() => { + this.keepaliveTrace('Ping timeout passed without response'); this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); }, this.keepaliveTimeoutMs); this.keepaliveTimeoutId.unref?.(); this.session!.ping( (err: Error | null, duration: number, payload: Buffer) => { + this.keepaliveTrace('Received ping response'); clearTimeout(this.keepaliveTimeoutId); } ); From abbaf13c62fa02da001a5d5598bf3cdf1a86563a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 8 Apr 2022 10:25:15 -0700 Subject: [PATCH 219/694] grpc-js-xds: Don't stop backoff timers for LRS streams --- packages/grpc-js-xds/src/xds-client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 7a12af1f6..bdc123959 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -781,7 +781,6 @@ export class XdsClient { trace('Received LRS response'); /* Once we get any response from the server, we assume that the stream is * in a good state, so we can reset the backoff timer. */ - this.lrsBackoff.stop(); this.lrsBackoff.reset(); if ( !this.receivedLrsSettingsForCurrentStream || From ae93d556ec44cd372fff78e69c674904ba63b223 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 8 Apr 2022 11:23:00 -0700 Subject: [PATCH 220/694] grpc-js: Don't clear ping timeout when still connected --- packages/grpc-js/src/subchannel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 800274f99..22c3363c7 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -773,7 +773,7 @@ export class Subchannel { } this.backoffTimeout.unref(); if (!this.keepaliveWithoutCalls) { - this.stopKeepalivePings(); + clearInterval(this.keepaliveIntervalId); } this.checkBothRefcounts(); } From 57d7827ab8341ca90c4c512a371a212760a41aaf Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Apr 2022 10:12:24 -0700 Subject: [PATCH 221/694] grpc-js-xds: Include Node ID in XdsClient status errors --- packages/grpc-js-xds/src/xds-client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 7a12af1f6..2eabc5863 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -726,7 +726,7 @@ export class XdsClient { if (serviceKind) { this.adsState[serviceKind].reportStreamError({ code: status.UNAVAILABLE, - details: message, + details: message + ' Node ID=' + this.adsNodeV3!.id, metadata: new Metadata() }); resourceNames = this.adsState[serviceKind].getResourceNames(); @@ -771,6 +771,7 @@ export class XdsClient { } private reportStreamError(status: StatusObject) { + status = {...status, details: status.details + ' Node ID=' + this.adsNodeV3!.id}; this.adsState.eds.reportStreamError(status); this.adsState.cds.reportStreamError(status); this.adsState.rds.reportStreamError(status); From 1e1f73236387bbce8dd216317643b739ad464fa8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Apr 2022 10:42:42 -0700 Subject: [PATCH 222/694] grpc-js-xds: Reject EDS updates with duplicate locality/priority pairs --- .../grpc-js-xds/src/xds-stream-state/eds-state.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index fe9f3c624..a050b44c9 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -17,6 +17,7 @@ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { isIPv4, isIPv6 } from "net"; +import { Locality__Output } from "../generated/envoy/config/core/v3/Locality"; import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; import { Any__Output } from "../generated/google/protobuf/Any"; import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; @@ -27,6 +28,10 @@ function trace(text: string): void { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); } +function localitiesEqual(a: Locality__Output, b: Locality__Output) { + return a.region === b.region && a.sub_zone === b.sub_zone && a.zone === b.zone; +} + export class EdsState implements XdsStreamState { public versionInfo = ''; public nonce = ''; @@ -112,7 +117,17 @@ export class EdsState implements XdsStreamState { * @param message */ private validateResponse(message: ClusterLoadAssignment__Output) { + const seenLocalities: {locality: Locality__Output, priority: number}[] = []; for (const endpoint of message.endpoints) { + if (!endpoint.locality) { + return false; + } + for (const {locality, priority} of seenLocalities) { + if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) { + return false; + } + } + seenLocalities.push({locality: endpoint.locality, priority: endpoint.priority}); for (const lb of endpoint.lb_endpoints) { const socketAddress = lb.endpoint?.address?.socket_address; if (!socketAddress) { From 553fb7a819cdb555d03b44c24ca4750a0d85ac31 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Apr 2022 14:43:18 -0700 Subject: [PATCH 223/694] grpc-js: Add comment about stopKeepalivePings usage --- packages/grpc-js/src/subchannel.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 22c3363c7..39ccc01bb 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -384,6 +384,11 @@ export class Subchannel { * sending pings should also involve some network activity. */ } + /** + * Stop keepalive pings when terminating a connection. This discards the + * outstanding ping timeout, so it should not be called if the same + * connection will still be used. + */ private stopKeepalivePings() { clearInterval(this.keepaliveIntervalId); clearTimeout(this.keepaliveTimeoutId); From 6c686772cbee628651910dbbd1a8da5b488deeda Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 12 Apr 2022 16:16:57 -0700 Subject: [PATCH 224/694] grpc-js: Fix handling of calls after resolution failure --- packages/grpc-js/src/channel.ts | 30 +++++++++++++++---- .../grpc-js/src/resolving-load-balancer.ts | 5 ++-- packages/grpc-js/test/test-client.ts | 24 +++++++++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 3d014607e..88bf3a7e0 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -20,6 +20,7 @@ import { Call, Http2CallStream, CallStreamOptions, + StatusObject, } from './call-stream'; import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; @@ -170,6 +171,14 @@ export class ChannelImplementation implements Channel { */ private callRefTimer: NodeJS.Timer; private configSelector: ConfigSelector | null = null; + /** + * This is the error from the name resolver if it failed most recently. It + * is only used to end calls that start while there is no config selector + * and the name resolver is in backoff, so it should be nulled if + * configSelector becomes set or the channel state becomes anything other + * than TRANSIENT_FAILURE. + */ + private currentResolutionError: StatusObject | null = null; // Channelz info private readonly channelzEnabled: boolean = true; @@ -290,6 +299,7 @@ export class ChannelImplementation implements Channel { this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); } this.configSelector = configSelector; + this.currentResolutionError = null; /* We process the queue asynchronously to ensure that the corresponding * load balancer update has completed. */ process.nextTick(() => { @@ -309,6 +319,9 @@ export class ChannelImplementation implements Channel { if (this.configSelectionQueue.length > 0) { this.trace('Name resolution failed with calls queued for config selection'); } + if (this.configSelector === null) { + this.currentResolutionError = status; + } const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; this.callRefTimerUnref(); @@ -591,6 +604,9 @@ export class ChannelImplementation implements Channel { watcherObject.callback(); } } + if (newState !== ConnectivityState.TRANSIENT_FAILURE) { + this.currentResolutionError = null; + } } private tryGetConfig(stream: Http2CallStream, metadata: Metadata) { @@ -605,11 +621,15 @@ export class ChannelImplementation implements Channel { * ResolvingLoadBalancer may be idle and if so it needs to be kicked * because it now has a pending request. */ this.resolvingLoadBalancer.exitIdle(); - this.configSelectionQueue.push({ - callStream: stream, - callMetadata: metadata, - }); - this.callRefTimerRef(); + if (this.currentResolutionError && !metadata.getOptions().waitForReady) { + stream.cancelWithStatus(this.currentResolutionError.code, this.currentResolutionError.details); + } else { + this.configSelectionQueue.push({ + callStream: stream, + callMetadata: metadata, + }); + this.callRefTimerRef(); + } } else { const callConfig = this.configSelector(stream.getMethod(), metadata); if (callConfig.status === Status.OK) { diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 907067dfc..7b9f92cb8 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -268,6 +268,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { if (this.currentState === ConnectivityState.IDLE) { this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); } + this.backoffTimeout.runOnce(); } private updateState(connectivityState: ConnectivityState, picker: Picker) { @@ -294,18 +295,16 @@ export class ResolvingLoadBalancer implements LoadBalancer { ); this.onFailedResolution(error); } - this.backoffTimeout.runOnce(); } exitIdle() { this.childLoadBalancer.exitIdle(); - if (this.currentState === ConnectivityState.IDLE) { + if (this.currentState === ConnectivityState.IDLE || this.currentState === ConnectivityState.TRANSIENT_FAILURE) { if (this.backoffTimeout.isRunning()) { this.continueResolving = true; } else { this.updateResolution(); } - this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); } } diff --git a/packages/grpc-js/test/test-client.ts b/packages/grpc-js/test/test-client.ts index 4a02ff3de..21dad99f1 100644 --- a/packages/grpc-js/test/test-client.ts +++ b/packages/grpc-js/test/test-client.ts @@ -92,4 +92,28 @@ describe('Client without a server', () => { }); }); }); +}); + +describe('Client with a nonexistent target domain', () => { + let client: Client; + before(() => { + // DNS name that does not exist per RFC 6761 section 6.4 + client = new Client('host.invalid', clientInsecureCreds); + }); + after(() => { + client.close(); + }); + it('should fail multiple calls', function(done) { + this.timeout(5000); + // Regression test for https://github.com/grpc/grpc-node/issues/1411 + client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + done(); + }); + }); + }); }); \ No newline at end of file From c112d167bb0af504c4a1121d2f6f28c9454d3e4d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Apr 2022 11:27:31 -0700 Subject: [PATCH 225/694] grpc-js: Update version to 1.6.4 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 9fab8d838..408168e9d 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.3", + "version": "1.6.4", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 478900d191d8f4e6c2e57e9ff732ad974253ceef Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 14 Apr 2022 16:51:16 -0700 Subject: [PATCH 226/694] grpc-js: Consistently re-resolve when idle --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/resolving-load-balancer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 408168e9d..09a88b5a5 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.4", + "version": "1.6.5", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 7b9f92cb8..985b6e31e 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -298,7 +298,6 @@ export class ResolvingLoadBalancer implements LoadBalancer { } exitIdle() { - this.childLoadBalancer.exitIdle(); if (this.currentState === ConnectivityState.IDLE || this.currentState === ConnectivityState.TRANSIENT_FAILURE) { if (this.backoffTimeout.isRunning()) { this.continueResolving = true; @@ -306,6 +305,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { this.updateResolution(); } } + this.childLoadBalancer.exitIdle(); } updateAddressList( From 8cbc3dc8258fa31d29a15f59ef230abb69651bb0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 14 Apr 2022 17:28:22 -0700 Subject: [PATCH 227/694] grpc-js: Make a reachable code path for requestReresolution in pick_first --- .../grpc-js/src/load-balancer-pick-first.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 884af50b7..240f0e9ff 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -184,8 +184,10 @@ export class PickFirstLoadBalancer implements LoadBalancer { ) { /* If all of the subchannels are IDLE we should go back to a * basic IDLE state where there is no subchannel list to avoid - * holding unused resources */ - this.resetSubchannelList(); + * holding unused resources. We do not reset triedAllSubchannels + * because that is a reminder to request reresolution the next time + * this LB policy needs to connect. */ + this.resetSubchannelList(false); this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); return; } @@ -337,7 +339,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.channelControlHelper.updateState(newState, picker); } - private resetSubchannelList() { + private resetSubchannelList(resetTriedAllSubchannels = true) { for (const subchannel of this.subchannels) { subchannel.removeConnectivityStateListener(this.subchannelStateListener); subchannel.unref(); @@ -352,7 +354,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { [ConnectivityState.TRANSIENT_FAILURE]: 0, }; this.subchannels = []; - this.triedAllSubchannels = false; + if (resetTriedAllSubchannels) { + this.triedAllSubchannels = false; + } } /** @@ -425,6 +429,12 @@ export class PickFirstLoadBalancer implements LoadBalancer { } exitIdle() { + if ( + this.currentState === ConnectivityState.IDLE || + this.triedAllSubchannels + ) { + this.channelControlHelper.requestReresolution(); + } for (const subchannel of this.subchannels) { subchannel.startConnecting(); } @@ -433,12 +443,6 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.connectToAddressList(); } } - if ( - this.currentState === ConnectivityState.IDLE || - this.triedAllSubchannels - ) { - this.channelControlHelper.requestReresolution(); - } } resetBackoff() { From cf11b60ce20123394dc6cb21488c665c7787d84e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Apr 2022 09:36:57 -0700 Subject: [PATCH 228/694] grpc-js: End calls when keepalive pings time out --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel.ts | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 09a88b5a5..a0df55736 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.5", + "version": "1.6.6", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 5880f3f97..5d479a73a 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -358,7 +358,7 @@ export class Subchannel { this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); this.keepaliveTimeoutId = setTimeout(() => { this.keepaliveTrace('Ping timeout passed without response'); - this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); + this.handleDisconnect(); }, this.keepaliveTimeoutMs); this.keepaliveTimeoutId.unref?.(); this.session!.ping( @@ -642,6 +642,15 @@ export class Subchannel { ); } + private handleDisconnect() { + this.transitionToState( + [ConnectivityState.READY], + ConnectivityState.TRANSIENT_FAILURE); + for (const listener of this.disconnectListeners) { + listener(); + } + } + /** * Initiate a state transition from any element of oldStates to the new * state. If the current connectivityState is not in oldStates, do nothing. @@ -672,12 +681,7 @@ export class Subchannel { const session = this.session!; session.socket.once('close', () => { if (this.session === session) { - this.transitionToState( - [ConnectivityState.READY], - ConnectivityState.TRANSIENT_FAILURE); - for (const listener of this.disconnectListeners) { - listener(); - } + this.handleDisconnect(); } }); if (this.keepaliveWithoutCalls) { From c9b7d4d285176a6be437b724c118c85f3f08c66d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Apr 2022 09:56:06 -0700 Subject: [PATCH 229/694] grpc-js: DNS: unset continueResolving when starting a resolution attempt --- packages/grpc-js/src/resolver-dns.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index c4cb64a74..47de8dbca 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -314,6 +314,7 @@ class DnsResolver implements Resolver { } private startResolutionWithBackoff() { + this.continueResolving = false; this.startResolution(); this.backoff.runOnce(); this.startNextResolutionTimer(); From 964c7a68aabd3e45f87b0d0045bb604e1bd67ae7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 19 Apr 2022 10:21:49 -0700 Subject: [PATCH 230/694] grpc-js: Fix double resolver calls in DNS resolver --- packages/grpc-js/src/backoff-timeout.ts | 1 + packages/grpc-js/src/resolver-dns.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/backoff-timeout.ts b/packages/grpc-js/src/backoff-timeout.ts index dc7be277a..f523e259a 100644 --- a/packages/grpc-js/src/backoff-timeout.ts +++ b/packages/grpc-js/src/backoff-timeout.ts @@ -100,6 +100,7 @@ export class BackoffTimeout { } private runTimer(delay: number) { + clearTimeout(this.timerId); this.timerId = setTimeout(() => { this.callback(); this.running = false; diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 47de8dbca..14de26561 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -158,7 +158,6 @@ class DnsResolver implements Resolver { if (this.ipResult !== null) { trace('Returning IP address for target ' + uriToString(this.target)); setImmediate(() => { - this.backoff.reset(); this.listener.onSuccessfulResolution( this.ipResult!, null, @@ -167,6 +166,8 @@ class DnsResolver implements Resolver { {} ); }); + this.backoff.stop(); + this.backoff.reset(); return; } if (this.dnsHostname === null) { @@ -178,7 +179,11 @@ class DnsResolver implements Resolver { metadata: new Metadata(), }); }); + this.stopNextResolutionTimer(); } else { + if (this.pendingLookupPromise !== null) { + return; + } trace('Looking up DNS hostname ' + this.dnsHostname); /* We clear out latestLookupResult here to ensure that it contains the * latest result since the last time we started resolving. That way, the @@ -299,6 +304,7 @@ class DnsResolver implements Resolver { } private startNextResolutionTimer() { + clearTimeout(this.nextResolutionTimer); this.nextResolutionTimer = setTimeout(() => { this.stopNextResolutionTimer(); if (this.continueResolving) { @@ -314,10 +320,12 @@ class DnsResolver implements Resolver { } private startResolutionWithBackoff() { + if (this.pendingLookupPromise === null) { this.continueResolving = false; this.startResolution(); this.backoff.runOnce(); this.startNextResolutionTimer(); + } } updateResolution() { From 5311c03867c7e3bdb1c167fcd76d44d6b5d2e63a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 19 Apr 2022 13:18:59 -0700 Subject: [PATCH 231/694] grpc-js: Report error when no message received for unary response --- packages/grpc-js/src/client.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index ed9407cd8..20f56bcb5 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -338,7 +338,15 @@ export class Client { } receivedStatus = true; if (status.code === Status.OK) { - callProperties.callback!(null, responseMessage!); + if (responseMessage === null) { + callProperties.callback!(callErrorFromStatus({ + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata + })); + } else { + callProperties.callback!(null, responseMessage); + } } else { callProperties.callback!(callErrorFromStatus(status)); } @@ -455,7 +463,15 @@ export class Client { } receivedStatus = true; if (status.code === Status.OK) { - callProperties.callback!(null, responseMessage!); + if (responseMessage === null) { + callProperties.callback!(callErrorFromStatus({ + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata + })); + } else { + callProperties.callback!(null, responseMessage); + } } else { callProperties.callback!(callErrorFromStatus(status)); } From 32514224ce1c85cd6e3c0c2e4a8c197acc90e0b9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 20 Apr 2022 13:06:43 -0700 Subject: [PATCH 232/694] grpc-js: Fix shutting down subchannels in separate pools --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel-pool.ts | 14 +++++--------- packages/grpc-js/src/subchannel.ts | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index a0df55736..723b108ff 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.6", + "version": "1.6.7", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts index cd74cad81..b7ef362c3 100644 --- a/packages/grpc-js/src/subchannel-pool.ts +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -49,10 +49,8 @@ export class SubchannelPool { /** * A pool of subchannels use for making connections. Subchannels with the * exact same parameters will be reused. - * @param global If true, this is the global subchannel pool. Otherwise, it - * is the pool for a single channel. */ - constructor(private global: boolean) {} + constructor() {} /** * Unrefs all unused subchannels and cancels the cleanup task if all @@ -95,7 +93,7 @@ export class SubchannelPool { * Ensures that the cleanup task is spawned. */ ensureCleanupTask(): void { - if (this.global && this.cleanupTimer === null) { + if (this.cleanupTimer === null) { this.cleanupTimer = setInterval(() => { this.unrefUnusedSubchannels(); }, REF_CHECK_INTERVAL); @@ -156,14 +154,12 @@ export class SubchannelPool { channelCredentials, subchannel, }); - if (this.global) { - subchannel.ref(); - } + subchannel.ref(); return subchannel; } } -const globalSubchannelPool = new SubchannelPool(true); +const globalSubchannelPool = new SubchannelPool(); /** * Get either the global subchannel pool, or a new subchannel pool. @@ -173,6 +169,6 @@ export function getSubchannelPool(global: boolean): SubchannelPool { if (global) { return globalSubchannelPool; } else { - return new SubchannelPool(false); + return new SubchannelPool(); } } diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 5d479a73a..372ab5160 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -741,7 +741,7 @@ export class Subchannel { } this.transitionToState( [ConnectivityState.CONNECTING, ConnectivityState.READY], - ConnectivityState.TRANSIENT_FAILURE + ConnectivityState.IDLE ); if (this.channelzEnabled) { unregisterChannelzRef(this.channelzRef); From b07ea8b35418ecfd88bccf856bffe8b75eef3727 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 21 Apr 2022 14:42:04 -0700 Subject: [PATCH 233/694] grpc-js: Update outlier detection to address recent spec changes --- .../src/load-balancer-outlier-detection.ts | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index e69e2ef99..3bd062110 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -334,17 +334,21 @@ class OutlierDetectionCounterFilterFactory implements FilterFactory = new Map(); private latestConfig: OutlierDetectionLoadBalancingConfig | null = null; private ejectionTimer: NodeJS.Timer; + private timerStartTime: Date | null = null; constructor(channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { @@ -373,7 +378,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { }, updateState: (connectivityState: ConnectivityState, picker: Picker) => { if (connectivityState === ConnectivityState.READY) { - channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker)); + channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled())); } else { channelControlHelper.updateState(connectivityState, picker); } @@ -383,6 +388,12 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { clearInterval(this.ejectionTimer); } + private isCountingEnabled(): boolean { + return this.latestConfig !== null && + (this.latestConfig.getSuccessRateEjectionConfig() !== null || + this.latestConfig.getFailurePercentageEjectionConfig() !== null); + } + private getCurrentEjectionPercent() { let ejectionCount = 0; for (const mapEntry of this.addressMap.values()) { @@ -501,16 +512,26 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } } - private runChecks() { - const ejectionTimestamp = new Date(); - + private switchAllBuckets() { for (const mapEntry of this.addressMap.values()) { mapEntry.counter.switchBuckets(); } + } + + private startTimer(delayMs: number) { + this.ejectionTimer = setTimeout(() => this.runChecks(), delayMs); + } + + private runChecks() { + const ejectionTimestamp = new Date(); + + this.switchAllBuckets(); if (!this.latestConfig) { return; } + this.timerStartTime = ejectionTimestamp; + this.startTimer(this.latestConfig.getIntervalMs()); this.runSuccessRateCheck(ejectionTimestamp); this.runFailurePercentageCheck(ejectionTimestamp); @@ -561,10 +582,21 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { ); this.childBalancer.updateAddressList(addressList, childPolicy, attributes); - if (this.latestConfig === null || this.latestConfig.getIntervalMs() !== lbConfig.getIntervalMs()) { - clearInterval(this.ejectionTimer); - this.ejectionTimer = setInterval(() => this.runChecks(), lbConfig.getIntervalMs()); + if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) { + if (this.timerStartTime) { + clearTimeout(this.ejectionTimer); + const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime()); + this.startTimer(remainingDelay); + } else { + this.timerStartTime = new Date(); + this.startTimer(lbConfig.getIntervalMs()); + this.switchAllBuckets(); + } + } else { + this.timerStartTime = null; + clearTimeout(this.ejectionTimer); } + this.latestConfig = lbConfig; } exitIdle(): void { @@ -574,6 +606,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { this.childBalancer.resetBackoff(); } destroy(): void { + clearTimeout(this.ejectionTimer); this.childBalancer.destroy(); } getTypeName(): string { From cd58695674679bdf070fea3390b86d51dddf0996 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 21 Apr 2022 16:09:24 -0700 Subject: [PATCH 234/694] grpc-js: Add regression tests for repeated DNS requests --- packages/grpc-js/test/test-resolver.ts | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index 354413ea6..512740cad 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -356,6 +356,70 @@ describe('Name Resolver', () => { const resolver2 = resolverManager.createResolver(target2, listener, {}); resolver2.updateResolution(); }); + it('should not keep repeating successful resolutions', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('localhost')!)!; + let resultCount = 0; + const resolver = resolverManager.createResolver(target, { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 443 + ) + ); + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 443 + ) + ); + resultCount += 1; + if (resultCount === 1) { + process.nextTick(() => resolver.updateResolution()); + } + }, + onError: (error: StatusObject) => { + assert.ifError(error); + }, + }, {'grpc.dns_min_time_between_resolutions_ms': 2000}); + resolver.updateResolution(); + setTimeout(() => { + assert.strictEqual(resultCount, 2, `resultCount ${resultCount} !== 2`); + done(); + }, 10_000); + }).timeout(15_000); + it('should not keep repeating failed resolutions', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('host.invalid')!)!; + let resultCount = 0; + const resolver = resolverManager.createResolver(target, { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert.fail('Resolution succeeded unexpectedly'); + }, + onError: (error: StatusObject) => { + resultCount += 1; + if (resultCount === 1) { + process.nextTick(() => resolver.updateResolution()); + } + }, + }, {}); + resolver.updateResolution(); + setTimeout(() => { + assert.strictEqual(resultCount, 2, `resultCount ${resultCount} !== 2`); + done(); + }, 10_000); + }).timeout(15_000); }); describe('UDS Names', () => { it('Should handle a relative Unix Domain Socket name', done => { From db65d566e651c96cdba909f22e28bbf7be312778 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 26 Apr 2022 10:09:22 -0700 Subject: [PATCH 235/694] grpc-js: Fix mean calculation in outlier detection LB policy --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 3bd062110..2c231c406 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -429,7 +429,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } // Step 2 - const successRateMean = successRates.reduce((a, b) => a + b); + const successRateMean = successRates.reduce((a, b) => a + b) / successRates.length; let successRateVariance = 0; for (const rate of successRates) { const deviation = rate - successRateMean; From 01823377be02b78869c92d8c147944a1b789139b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 28 Apr 2022 10:34:30 -0700 Subject: [PATCH 236/694] grpc-js: Add calling context to call errors --- packages/grpc-js/src/call.ts | 6 ++++-- packages/grpc-js/src/client.ts | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/src/call.ts b/packages/grpc-js/src/call.ts index fcc3159db..10b606a44 100644 --- a/packages/grpc-js/src/call.ts +++ b/packages/grpc-js/src/call.ts @@ -76,9 +76,11 @@ export type ClientDuplexStream< * error is not necessarily a problem in gRPC itself. * @param status */ -export function callErrorFromStatus(status: StatusObject): ServiceError { +export function callErrorFromStatus(status: StatusObject, callerStack: string): ServiceError { const message = `${status.code} ${Status[status.code]}: ${status.details}`; - return Object.assign(new Error(message), status); + const error = new Error(message); + const stack = `${error.stack}\nfor call at\n${callerStack}`; + return Object.assign(new Error(message), status, {stack}); } export class ClientUnaryCallImpl diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index 20f56bcb5..747c5c877 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -321,6 +321,7 @@ export class Client { } let responseMessage: ResponseType | null = null; let receivedStatus = false; + const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); call.start(callProperties.metadata, { onReceiveMetadata: (metadata) => { emitter.emit('metadata', metadata); @@ -343,12 +344,12 @@ export class Client { code: Status.INTERNAL, details: 'No message received', metadata: status.metadata - })); + }, callerStack)); } else { callProperties.callback!(null, responseMessage); } } else { - callProperties.callback!(callErrorFromStatus(status)); + callProperties.callback!(callErrorFromStatus(status, callerStack)); } emitter.emit('status', status); }, @@ -446,6 +447,7 @@ export class Client { } let responseMessage: ResponseType | null = null; let receivedStatus = false; + const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); call.start(callProperties.metadata, { onReceiveMetadata: (metadata) => { emitter.emit('metadata', metadata); @@ -468,12 +470,12 @@ export class Client { code: Status.INTERNAL, details: 'No message received', metadata: status.metadata - })); + }, callerStack)); } else { callProperties.callback!(null, responseMessage); } } else { - callProperties.callback!(callErrorFromStatus(status)); + callProperties.callback!(callErrorFromStatus(status, callerStack)); } emitter.emit('status', status); }, @@ -575,6 +577,7 @@ export class Client { call.setCredentials(callProperties.callOptions.credentials); } let receivedStatus = false; + const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); call.start(callProperties.metadata, { onReceiveMetadata(metadata: Metadata) { stream.emit('metadata', metadata); @@ -590,7 +593,7 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { - stream.emit('error', callErrorFromStatus(status)); + stream.emit('error', callErrorFromStatus(status, callerStack)); } stream.emit('status', status); }, @@ -672,6 +675,7 @@ export class Client { call.setCredentials(callProperties.callOptions.credentials); } let receivedStatus = false; + const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); call.start(callProperties.metadata, { onReceiveMetadata(metadata: Metadata) { stream.emit('metadata', metadata); @@ -686,7 +690,7 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { - stream.emit('error', callErrorFromStatus(status)); + stream.emit('error', callErrorFromStatus(status, callerStack)); } stream.emit('status', status); }, From 3388765cbb723907de1cc74349b666cc548a891e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 2 May 2022 10:54:03 -0700 Subject: [PATCH 237/694] proto-loader: Update to Long 5.x --- packages/proto-loader/package.json | 4 ++-- packages/proto-loader/src/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 9b9431dc1..c9998bafb 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.9", + "version": "0.6.10", "author": "Google Inc.", "contributors": [ { @@ -47,7 +47,7 @@ "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", + "long": "^5.2.0", "protobufjs": "^6.10.0", "yargs": "^16.2.0" }, diff --git a/packages/proto-loader/src/index.ts b/packages/proto-loader/src/index.ts index 24a249400..d607668a9 100644 --- a/packages/proto-loader/src/index.ts +++ b/packages/proto-loader/src/index.ts @@ -22,9 +22,9 @@ import * as descriptor from 'protobufjs/ext/descriptor'; import { loadProtosWithOptionsSync, loadProtosWithOptions, Options, addCommonProtos } from './util'; -export { Long } from 'long'; +import Long = require('long'); -export { Options }; +export { Options, Long }; /** * This type exists for use with code generated by the proto-loader-gen-types From 2b67d5b010db5713d12a0f93ae4c0f26c6d7384c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 May 2022 10:16:54 -0700 Subject: [PATCH 238/694] proto-loader: Don't force long@5 --- packages/proto-loader/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index c9998bafb..4b177e87c 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.10", + "version": "0.6.11", "author": "Google Inc.", "contributors": [ { @@ -47,7 +47,7 @@ "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "long": "^5.2.0", + "long": "^4.0.0 || ^5.2.0", "protobufjs": "^6.10.0", "yargs": "^16.2.0" }, From d8d957bf8cda8139d46d5da5e2fad912868a4191 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 5 May 2022 09:27:48 -0700 Subject: [PATCH 239/694] proto-loader: Switch long dependency back to 4.x --- packages/proto-loader/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 4b177e87c..759e54713 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.11", + "version": "0.6.12", "author": "Google Inc.", "contributors": [ { @@ -47,7 +47,7 @@ "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "long": "^4.0.0 || ^5.2.0", + "long": "^4.0.0", "protobufjs": "^6.10.0", "yargs": "^16.2.0" }, From 067bb13f275cf2bbcb2fbacf4a757a7a3c7d4035 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 12 May 2022 17:18:55 -0700 Subject: [PATCH 240/694] grpc-js-xds: Refactor xDS stream state and add resource timer --- packages/grpc-js-xds/src/xds-client.ts | 10 +- .../src/xds-stream-state/cds-state.ts | 156 +-------------- .../src/xds-stream-state/eds-state.ts | 130 +----------- .../src/xds-stream-state/lds-state.ts | 135 ++----------- .../src/xds-stream-state/rds-state.ts | 122 +---------- .../src/xds-stream-state/xds-stream-state.ts | 189 +++++++++++++++++- 6 files changed, 236 insertions(+), 506 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 7a12af1f6..5f75a2cab 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -309,7 +309,7 @@ export class XdsClient { const edsState = new EdsState(() => { this.updateNames('eds'); }); - const cdsState = new CdsState(edsState, () => { + const cdsState = new CdsState(() => { this.updateNames('cds'); }); const rdsState = new RdsState(() => { @@ -630,6 +630,7 @@ export class XdsClient { this.updateNames(service); } } + this.reportAdsStreamStarted(); } } @@ -777,6 +778,13 @@ export class XdsClient { this.adsState.lds.reportStreamError(status); } + private reportAdsStreamStarted() { + this.adsState.eds.reportAdsStreamStart(); + this.adsState.cds.reportAdsStreamStart(); + this.adsState.rds.reportAdsStreamStart(); + this.adsState.lds.reportAdsStreamStart(); + } + private handleLrsResponse(message: LoadStatsResponse__Output) { trace('Received LRS response'); /* Once we get any response from the server, we assume that the stream is diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 8c3c4d739..9fd12d6ed 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -15,94 +15,21 @@ * */ -import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; -import { Any__Output } from "../generated/google/protobuf/Any"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; -import { EdsState } from "./eds-state"; -import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; +import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; -const TRACER_NAME = 'xds_client'; - -function trace(text: string): void { - experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); -} - -export class CdsState implements XdsStreamState { - versionInfo = ''; - nonce = ''; - - private watchers: Map[]> = new Map< - string, - Watcher[] - >(); - - private latestResponses: Cluster__Output[] = []; - private latestIsV2 = false; - - constructor( - private edsState: EdsState, - private updateResourceNames: () => void - ) {} - - /** - * Add the watcher to the watcher list. Returns true if the list of resource - * names has changed, and false otherwise. - * @param clusterName - * @param watcher - */ - addWatcher(clusterName: string, watcher: Watcher): void { - trace('Adding CDS watcher for clusterName ' + clusterName); - let watchersEntry = this.watchers.get(clusterName); - let addedServiceName = false; - if (watchersEntry === undefined) { - addedServiceName = true; - watchersEntry = []; - this.watchers.set(clusterName, watchersEntry); - } - watchersEntry.push(watcher); - - /* If we have already received an update for the requested edsServiceName, - * immediately pass that update along to the watcher */ - const isV2 = this.latestIsV2; - for (const message of this.latestResponses) { - if (message.name === clusterName) { - /* These updates normally occur asynchronously, so we ensure that - * the same happens here */ - process.nextTick(() => { - trace('Reporting existing CDS update for new watcher for clusterName ' + clusterName); - watcher.onValidUpdate(message, isV2); - }); - } - } - if (addedServiceName) { - this.updateResourceNames(); - } +export class CdsState extends BaseXdsStreamState implements XdsStreamState { + protected isStateOfTheWorld(): boolean { + return true; } - - removeWatcher(clusterName: string, watcher: Watcher): void { - trace('Removing CDS watcher for clusterName ' + clusterName); - const watchersEntry = this.watchers.get(clusterName); - let removedServiceName = false; - if (watchersEntry !== undefined) { - const entryIndex = watchersEntry.indexOf(watcher); - if (entryIndex >= 0) { - watchersEntry.splice(entryIndex, 1); - } - if (watchersEntry.length === 0) { - removedServiceName = true; - this.watchers.delete(clusterName); - } - } - if (removedServiceName) { - this.updateResourceNames(); - } + protected getResourceName(resource: Cluster__Output): string { + return resource.name; } - - getResourceNames(): string[] { - return Array.from(this.watchers.keys()); + protected getProtocolName(): string { + return 'CDS'; } private validateNonnegativeDuration(duration: Duration__Output | null): boolean { @@ -125,7 +52,7 @@ export class CdsState implements XdsStreamState { return percentage.value >=0 && percentage.value <= 100; } - private validateResponse(message: Cluster__Output): boolean { + public validateResponse(message: Cluster__Output): boolean { if (message.type !== 'EDS') { return false; } @@ -167,69 +94,4 @@ export class CdsState implements XdsStreamState { } return true; } - - /** - * Given a list of clusterNames (which may actually be the cluster name), - * for each watcher watching a name not on the list, call that watcher's - * onResourceDoesNotExist method. - * @param allClusterNames - */ - private handleMissingNames(allClusterNames: Set): string[] { - const missingNames: string[] = []; - for (const [clusterName, watcherList] of this.watchers.entries()) { - if (!allClusterNames.has(clusterName)) { - trace('Reporting CDS resource does not exist for clusterName ' + clusterName); - missingNames.push(clusterName); - for (const watcher of watcherList) { - watcher.onResourceDoesNotExist(); - } - } - } - return missingNames; - } - - handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { - const validResponses: Cluster__Output[] = []; - const result: HandleResponseResult = { - accepted: [], - rejected: [], - missing: [] - } - for (const {resource, raw} of responses) { - if (this.validateResponse(resource)) { - validResponses.push(resource); - result.accepted.push({ - name: resource.name, - raw: raw}); - } else { - trace('CDS validation failed for message ' + JSON.stringify(resource)); - result.rejected.push({ - name: resource.name, - raw: raw, - error: `Cluster validation failed for resource ${resource.name}` - }); - } - } - this.latestResponses = validResponses; - this.latestIsV2 = isV2; - const allClusterNames: Set = new Set(); - for (const message of validResponses) { - allClusterNames.add(message.name); - const watchers = this.watchers.get(message.name) ?? []; - for (const watcher of watchers) { - watcher.onValidUpdate(message, isV2); - } - } - trace('Received CDS updates for cluster names [' + Array.from(allClusterNames) + ']'); - result.missing = this.handleMissingNames(allClusterNames); - return result; - } - - reportStreamError(status: StatusObject): void { - for (const watcherList of this.watchers.values()) { - for (const watcher of watcherList) { - watcher.onTransientError(status); - } - } - } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index fe9f3c624..fb2a99485 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -19,7 +19,7 @@ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { isIPv4, isIPv6 } from "net"; import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; import { Any__Output } from "../generated/google/protobuf/Any"; -import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; +import { BaseXdsStreamState, HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; const TRACER_NAME = 'xds_client'; @@ -27,83 +27,15 @@ function trace(text: string): void { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); } -export class EdsState implements XdsStreamState { - public versionInfo = ''; - public nonce = ''; - - private watchers: Map< - string, - Watcher[] - > = new Map[]>(); - - private latestResponses: ClusterLoadAssignment__Output[] = []; - private latestIsV2 = false; - - constructor(private updateResourceNames: () => void) {} - - /** - * Add the watcher to the watcher list. Returns true if the list of resource - * names has changed, and false otherwise. - * @param edsServiceName - * @param watcher - */ - addWatcher( - edsServiceName: string, - watcher: Watcher - ): void { - let watchersEntry = this.watchers.get(edsServiceName); - let addedServiceName = false; - if (watchersEntry === undefined) { - addedServiceName = true; - watchersEntry = []; - this.watchers.set(edsServiceName, watchersEntry); - } - trace('Adding EDS watcher (' + watchersEntry.length + ' ->' + (watchersEntry.length + 1) + ') for edsServiceName ' + edsServiceName); - watchersEntry.push(watcher); - - /* If we have already received an update for the requested edsServiceName, - * immediately pass that update along to the watcher */ - const isV2 = this.latestIsV2; - for (const message of this.latestResponses) { - if (message.cluster_name === edsServiceName) { - /* These updates normally occur asynchronously, so we ensure that - * the same happens here */ - process.nextTick(() => { - trace('Reporting existing EDS update for new watcher for edsServiceName ' + edsServiceName); - watcher.onValidUpdate(message, isV2); - }); - } - } - if (addedServiceName) { - this.updateResourceNames(); - } +export class EdsState extends BaseXdsStreamState implements XdsStreamState { + protected getResourceName(resource: ClusterLoadAssignment__Output): string { + return resource.cluster_name; } - - removeWatcher( - edsServiceName: string, - watcher: Watcher - ): void { - trace('Removing EDS watcher for edsServiceName ' + edsServiceName); - const watchersEntry = this.watchers.get(edsServiceName); - let removedServiceName = false; - if (watchersEntry !== undefined) { - const entryIndex = watchersEntry.indexOf(watcher); - if (entryIndex >= 0) { - trace('Removed EDS watcher (' + watchersEntry.length + ' -> ' + (watchersEntry.length - 1) + ') for edsServiceName ' + edsServiceName); - watchersEntry.splice(entryIndex, 1); - } - if (watchersEntry.length === 0) { - removedServiceName = true; - this.watchers.delete(edsServiceName); - } - } - if (removedServiceName) { - this.updateResourceNames(); - } + protected getProtocolName(): string { + return 'EDS'; } - - getResourceNames(): string[] { - return Array.from(this.watchers.keys()); + protected isStateOfTheWorld(): boolean { + return false; } /** @@ -111,7 +43,7 @@ export class EdsState implements XdsStreamState { * https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md#clusterloadassignment-proto * @param message */ - private validateResponse(message: ClusterLoadAssignment__Output) { + public validateResponse(message: ClusterLoadAssignment__Output) { for (const endpoint of message.endpoints) { for (const lb of endpoint.lb_endpoints) { const socketAddress = lb.endpoint?.address?.socket_address; @@ -128,48 +60,4 @@ export class EdsState implements XdsStreamState { } return true; } - - handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { - const validResponses: ClusterLoadAssignment__Output[] = []; - let result: HandleResponseResult = { - accepted: [], - rejected: [], - missing: [] - } - for (const {resource, raw} of responses) { - if (this.validateResponse(resource)) { - validResponses.push(resource); - result.accepted.push({ - name: resource.cluster_name, - raw: raw}); - } else { - trace('EDS validation failed for message ' + JSON.stringify(resource)); - result.rejected.push({ - name: resource.cluster_name, - raw: raw, - error: `ClusterLoadAssignment validation failed for resource ${resource.cluster_name}` - }); - } - } - this.latestResponses = validResponses; - this.latestIsV2 = isV2; - const allClusterNames: Set = new Set(); - for (const message of validResponses) { - allClusterNames.add(message.cluster_name); - const watchers = this.watchers.get(message.cluster_name) ?? []; - for (const watcher of watchers) { - watcher.onValidUpdate(message, isV2); - } - } - trace('Received EDS updates for cluster names [' + Array.from(allClusterNames) + ']'); - return result; - } - - reportStreamError(status: StatusObject): void { - for (const watcherList of this.watchers.values()) { - for (const watcher of watcherList) { - watcher.onTransientError(status); - } - } - } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index 7c27c948f..bd5b6423f 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -15,16 +15,13 @@ * */ -import * as protoLoader from '@grpc/proto-loader'; -import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; +import { experimental, logVerbosity } from "@grpc/grpc-js"; import { Listener__Output } from '../generated/envoy/config/listener/v3/Listener'; import { RdsState } from "./rds-state"; -import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; -import { HttpConnectionManager__Output } from '../generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; +import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V2, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from '../resources'; import { getTopLevelFilterUrl, validateTopLevelFilter } from '../http-filter'; import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; -import { Any__Output } from '../generated/google/protobuf/Any'; const TRACER_NAME = 'xds_client'; @@ -34,69 +31,22 @@ function trace(text: string): void { const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; -export class LdsState implements XdsStreamState { - versionInfo = ''; - nonce = ''; - - private watchers: Map[]> = new Map[]>(); - private latestResponses: Listener__Output[] = []; - private latestIsV2 = false; - - constructor(private rdsState: RdsState, private updateResourceNames: () => void) {} - - addWatcher(targetName: string, watcher: Watcher) { - trace('Adding RDS watcher for targetName ' + targetName); - let watchersEntry = this.watchers.get(targetName); - let addedServiceName = false; - if (watchersEntry === undefined) { - addedServiceName = true; - watchersEntry = []; - this.watchers.set(targetName, watchersEntry); - } - watchersEntry.push(watcher); - - /* If we have already received an update for the requested edsServiceName, - * immediately pass that update along to the watcher */ - const isV2 = this.latestIsV2; - for (const message of this.latestResponses) { - if (message.name === targetName) { - /* These updates normally occur asynchronously, so we ensure that - * the same happens here */ - process.nextTick(() => { - trace('Reporting existing RDS update for new watcher for targetName ' + targetName); - watcher.onValidUpdate(message, isV2); - }); - } - } - if (addedServiceName) { - this.updateResourceNames(); - } +export class LdsState extends BaseXdsStreamState implements XdsStreamState { + protected getResourceName(resource: Listener__Output): string { + return resource.name; } - - removeWatcher(targetName: string, watcher: Watcher): void { - trace('Removing RDS watcher for targetName ' + targetName); - const watchersEntry = this.watchers.get(targetName); - let removedServiceName = false; - if (watchersEntry !== undefined) { - const entryIndex = watchersEntry.indexOf(watcher); - if (entryIndex >= 0) { - watchersEntry.splice(entryIndex, 1); - } - if (watchersEntry.length === 0) { - removedServiceName = true; - this.watchers.delete(targetName); - } - } - if (removedServiceName) { - this.updateResourceNames(); - } + protected getProtocolName(): string { + return 'LDS'; + } + protected isStateOfTheWorld(): boolean { + return true; } - getResourceNames(): string[] { - return Array.from(this.watchers.keys()); + constructor(private rdsState: RdsState, updateResourceNames: () => void) { + super(updateResourceNames); } - private validateResponse(message: Listener__Output, isV2: boolean): boolean { + public validateResponse(message: Listener__Output, isV2: boolean): boolean { if ( !( message.api_listener?.api_listener && @@ -143,63 +93,4 @@ export class LdsState implements XdsStreamState { } return false; } - - private handleMissingNames(allTargetNames: Set): string[] { - const missingNames: string[] = []; - for (const [targetName, watcherList] of this.watchers.entries()) { - if (!allTargetNames.has(targetName)) { - missingNames.push(targetName); - for (const watcher of watcherList) { - watcher.onResourceDoesNotExist(); - } - } - } - return missingNames; - } - - handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { - const validResponses: Listener__Output[] = []; - let result: HandleResponseResult = { - accepted: [], - rejected: [], - missing: [] - } - for (const {resource, raw} of responses) { - if (this.validateResponse(resource, isV2)) { - validResponses.push(resource); - result.accepted.push({ - name: resource.name, - raw: raw - }); - } else { - trace('LDS validation failed for message ' + JSON.stringify(resource)); - result.rejected.push({ - name: resource.name, - raw: raw, - error: `Listener validation failed for resource ${resource.name}` - }); - } - } - this.latestResponses = validResponses; - this.latestIsV2 = isV2; - const allTargetNames = new Set(); - for (const message of validResponses) { - allTargetNames.add(message.name); - const watchers = this.watchers.get(message.name) ?? []; - for (const watcher of watchers) { - watcher.onValidUpdate(message, isV2); - } - } - trace('Received LDS response with listener names [' + Array.from(allTargetNames) + ']'); - result.missing = this.handleMissingNames(allTargetNames); - return result; - } - - reportStreamError(status: StatusObject): void { - for (const watcherList of this.watchers.values()) { - for (const watcher of watcherList) { - watcher.onTransientError(status); - } - } - } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index bc1c4a818..77a84469b 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -15,20 +15,10 @@ * */ -import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { EXPERIMENTAL_FAULT_INJECTION } from "../environment"; import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; -import { Any__Output } from "../generated/google/protobuf/Any"; import { validateOverrideFilter } from "../http-filter"; -import { CdsLoadBalancingConfig } from "../load-balancer-cds"; -import { HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; -import ServiceConfig = experimental.ServiceConfig; - -const TRACER_NAME = 'xds_client'; - -function trace(text: string): void { - experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); -} +import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; const SUPPORTED_PATH_SPECIFIERS = ['prefix', 'path', 'safe_regex']; const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ @@ -40,68 +30,16 @@ const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ 'suffix_match']; const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; -export class RdsState implements XdsStreamState { - versionInfo = ''; - nonce = ''; - - private watchers: Map[]> = new Map[]>(); - private latestResponses: RouteConfiguration__Output[] = []; - private latestIsV2 = false; - - constructor(private updateResourceNames: () => void) {} - - addWatcher(routeConfigName: string, watcher: Watcher) { - trace('Adding RDS watcher for routeConfigName ' + routeConfigName); - let watchersEntry = this.watchers.get(routeConfigName); - let addedServiceName = false; - if (watchersEntry === undefined) { - addedServiceName = true; - watchersEntry = []; - this.watchers.set(routeConfigName, watchersEntry); - } - watchersEntry.push(watcher); - - /* If we have already received an update for the requested edsServiceName, - * immediately pass that update along to the watcher */ - const isV2 = this.latestIsV2; - for (const message of this.latestResponses) { - if (message.name === routeConfigName) { - /* These updates normally occur asynchronously, so we ensure that - * the same happens here */ - process.nextTick(() => { - trace('Reporting existing RDS update for new watcher for routeConfigName ' + routeConfigName); - watcher.onValidUpdate(message, isV2); - }); - } - } - if (addedServiceName) { - this.updateResourceNames(); - } +export class RdsState extends BaseXdsStreamState implements XdsStreamState { + protected isStateOfTheWorld(): boolean { + return false; } - - removeWatcher(routeConfigName: string, watcher: Watcher): void { - trace('Removing RDS watcher for routeConfigName ' + routeConfigName); - const watchersEntry = this.watchers.get(routeConfigName); - let removedServiceName = false; - if (watchersEntry !== undefined) { - const entryIndex = watchersEntry.indexOf(watcher); - if (entryIndex >= 0) { - watchersEntry.splice(entryIndex, 1); - } - if (watchersEntry.length === 0) { - removedServiceName = true; - this.watchers.delete(routeConfigName); - } - } - if (removedServiceName) { - this.updateResourceNames(); - } + protected getResourceName(resource: RouteConfiguration__Output): string { + return resource.name; } - - getResourceNames(): string[] { - return Array.from(this.watchers.keys()); + protected getProtocolName(): string { + return 'RDS'; } - validateResponse(message: RouteConfiguration__Output, isV2: boolean): boolean { // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation for (const virtualHost of message.virtual_hosts) { @@ -172,48 +110,4 @@ export class RdsState implements XdsStreamState { } return true; } - - handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { - const validResponses: RouteConfiguration__Output[] = []; - let result: HandleResponseResult = { - accepted: [], - rejected: [], - missing: [] - } - for (const {resource, raw} of responses) { - if (this.validateResponse(resource, isV2)) { - validResponses.push(resource); - result.accepted.push({ - name: resource.name, - raw: raw}); - } else { - trace('RDS validation failed for message ' + JSON.stringify(resource)); - result.rejected.push({ - name: resource.name, - raw: raw, - error: `Route validation failed for resource ${resource.name}` - }); - } - } - this.latestResponses = validResponses; - this.latestIsV2 = isV2; - const allRouteConfigNames = new Set(); - for (const message of validResponses) { - allRouteConfigNames.add(message.name); - const watchers = this.watchers.get(message.name) ?? []; - for (const watcher of watchers) { - watcher.onValidUpdate(message, isV2); - } - } - trace('Received RDS response with route config names [' + Array.from(allRouteConfigNames) + ']'); - return result; - } - - reportStreamError(status: StatusObject): void { - for (const watcherList of this.watchers.values()) { - for (const watcher of watcherList) { - watcher.onTransientError(status); - } - } - } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index c8cbc41cf..2ff5130c4 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -15,9 +15,11 @@ * */ -import { StatusObject } from "@grpc/grpc-js"; +import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { Any__Output } from "../generated/google/protobuf/Any"; +const TRACER_NAME = 'xds_client'; + export interface Watcher { /* Including the isV2 flag here is a bit of a kludge. It would probably be * better for XdsStreamState#handleResponses to transform the protobuf @@ -63,4 +65,189 @@ export interface XdsStreamState { handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult; reportStreamError(status: StatusObject): void; + reportAdsStreamStart(): void; + + addWatcher(name: string, watcher: Watcher): void; + removeWatcher(resourceName: string, watcher: Watcher): void; +} + +interface SubscriptionEntry { + watchers: Watcher[]; + cachedResponse: ResponseType | null; + resourceTimer: NodeJS.Timer; +} + +const RESOURCE_TIMEOUT_MS = 15_000; + +export abstract class BaseXdsStreamState implements XdsStreamState { + versionInfo = ''; + nonce = ''; + + private subscriptions: Map> = new Map>(); + private latestIsV2 = false; + private isAdsStreamRunning = false; + + constructor(private updateResourceNames: () => void) {} + + protected trace(text: string) { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, this.getProtocolName() + ' | ' + text); + } + + private startResourceTimer(subscriptionEntry: SubscriptionEntry) { + clearTimeout(subscriptionEntry.resourceTimer); + subscriptionEntry.resourceTimer = setTimeout(() => { + for (const watcher of subscriptionEntry.watchers) { + watcher.onResourceDoesNotExist(); + } + }, RESOURCE_TIMEOUT_MS); + } + + addWatcher(name: string, watcher: Watcher): void { + this.trace('Adding watcher for name ' + name); + let subscriptionEntry = this.subscriptions.get(name); + let addedName = false; + if (subscriptionEntry === undefined) { + addedName = true; + subscriptionEntry = { + watchers: [], + cachedResponse: null, + resourceTimer: setTimeout(() => {}, 0) + }; + this.startResourceTimer(subscriptionEntry); + this.subscriptions.set(name, subscriptionEntry); + } + subscriptionEntry.watchers.push(watcher); + if (subscriptionEntry.cachedResponse !== null) { + const cachedResponse = subscriptionEntry.cachedResponse; + /* These updates normally occur asynchronously, so we ensure that + * the same happens here */ + process.nextTick(() => { + this.trace('Reporting existing update for new watcher for name ' + name); + watcher.onValidUpdate(cachedResponse, this.latestIsV2); + }); + } + if (addedName) { + this.updateResourceNames(); + } + } + removeWatcher(resourceName: string, watcher: Watcher): void { + this.trace('Removing watcher for name ' + resourceName); + const subscriptionEntry = this.subscriptions.get(resourceName); + if (subscriptionEntry !== undefined) { + const entryIndex = subscriptionEntry.watchers.indexOf(watcher); + if (entryIndex >= 0) { + subscriptionEntry.watchers.splice(entryIndex, 1); + } + if (subscriptionEntry.watchers.length === 0) { + clearTimeout(subscriptionEntry.resourceTimer); + this.subscriptions.delete(resourceName); + this.updateResourceNames(); + } + } + } + + getResourceNames(): string[] { + return Array.from(this.subscriptions.keys()); + } + handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { + const validResponses: ResponseType[] = []; + let result: HandleResponseResult = { + accepted: [], + rejected: [], + missing: [] + } + for (const {resource, raw} of responses) { + const resourceName = this.getResourceName(resource); + if (this.validateResponse(resource, isV2)) { + validResponses.push(resource); + result.accepted.push({ + name: resourceName, + raw: raw}); + } else { + this.trace('Validation failed for message ' + JSON.stringify(resource)); + result.rejected.push({ + name: resourceName, + raw: raw, + error: `Validation failed for resource ${resourceName}` + }); + } + } + this.latestIsV2 = isV2; + const allResourceNames = new Set(); + for (const resource of validResponses) { + const resourceName = this.getResourceName(resource); + allResourceNames.add(resourceName); + const subscriptionEntry = this.subscriptions.get(resourceName); + if (subscriptionEntry) { + const watchers = subscriptionEntry.watchers; + for (const watcher of watchers) { + watcher.onValidUpdate(resource, isV2); + } + clearTimeout(subscriptionEntry.resourceTimer); + subscriptionEntry.cachedResponse = resource; + } + } + result.missing = this.handleMissingNames(allResourceNames); + this.trace('Received response with resource names [' + Array.from(allResourceNames) + ']'); + return result; + } + reportStreamError(status: StatusObject): void { + for (const subscriptionEntry of this.subscriptions.values()) { + for (const watcher of subscriptionEntry.watchers) { + watcher.onTransientError(status); + } + clearTimeout(subscriptionEntry.resourceTimer); + } + this.isAdsStreamRunning = false; + } + + reportAdsStreamStart() { + this.isAdsStreamRunning = true; + for (const subscriptionEntry of this.subscriptions.values()) { + if (subscriptionEntry.cachedResponse === null) { + this.startResourceTimer(subscriptionEntry); + } + } + } + + private handleMissingNames(allResponseNames: Set): string[] { + if (this.isStateOfTheWorld()) { + const missingNames: string[] = []; + for (const [resourceName, subscriptionEntry] of this.subscriptions.entries()) { + if (!allResponseNames.has(resourceName) && subscriptionEntry.cachedResponse !== null) { + this.trace('Reporting resource does not exist named ' + resourceName); + missingNames.push(resourceName); + for (const watcher of subscriptionEntry.watchers) { + watcher.onResourceDoesNotExist(); + } + } + } + return missingNames; + } else { + return []; + } + } + + /** + * Apply the validation rules for this resource type to this resource + * instance. + * This function is public so that the LDS validateResponse can call into + * the RDS validateResponse. + * @param resource The resource object sent by the xDS server + * @param isV2 If true, the resource is an xDS V2 resource instead of xDS V3 + */ + public abstract validateResponse(resource: ResponseType, isV2: boolean): boolean; + /** + * Get the name of a resource object. The name is some field of the object, so + * getting it depends on the specific type. + * @param resource + */ + protected abstract getResourceName(resource: ResponseType): string; + protected abstract getProtocolName(): string; + /** + * Indicates whether responses are "state of the world", i.e. that they + * contain all resources and that omitted previously-seen resources should + * be treated as removed. + */ + protected abstract isStateOfTheWorld(): boolean; } \ No newline at end of file From a041056e712975e8ded07d032512ee70063c8cb1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 12 May 2022 17:19:46 -0700 Subject: [PATCH 241/694] Borrow Linux test job for xDS tests --- test/kokoro/linux.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/kokoro/linux.cfg b/test/kokoro/linux.cfg index 63f88d399..c13d03f35 100644 --- a/test/kokoro/linux.cfg +++ b/test/kokoro/linux.cfg @@ -14,10 +14,10 @@ # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. -build_file: "grpc-node/test/kokoro.sh" -timeout_mins: 60 +build_file: "grpc-node/packages/grpc-js-xds/scripts/xds.sh" +timeout_mins: 360 action { define_artifacts { - regex: "github/grpc-node/reports/**/sponge_log.xml" + regex: "github/grpc/reports/**" } -} \ No newline at end of file +} From 65075e50a742c04d4544252f55a2f24471e45e31 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 12 May 2022 17:34:25 -0700 Subject: [PATCH 242/694] Only start the timer if the ADS stream is running --- packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 2ff5130c4..1d96728a2 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -113,7 +113,9 @@ export abstract class BaseXdsStreamState implements XdsStreamState cachedResponse: null, resourceTimer: setTimeout(() => {}, 0) }; - this.startResourceTimer(subscriptionEntry); + if (this.isAdsStreamRunning) { + this.startResourceTimer(subscriptionEntry); + } this.subscriptions.set(name, subscriptionEntry); } subscriptionEntry.watchers.push(watcher); From 9035327af12c989933946047f7125e4d636b6611 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 12 May 2022 17:38:28 -0700 Subject: [PATCH 243/694] Clear the nonce when the stream ends --- packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 1d96728a2..0b806f843 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -201,6 +201,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState clearTimeout(subscriptionEntry.resourceTimer); } this.isAdsStreamRunning = false; + this.nonce = ''; } reportAdsStreamStart() { From 9502f5265de4c1ebc5aeb4bd4eb8c9c6ca829ded Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 13 May 2022 09:48:36 -0700 Subject: [PATCH 244/694] Revert "Borrow Linux test job for xDS tests" This reverts commit a041056e712975e8ded07d032512ee70063c8cb1. --- test/kokoro/linux.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/kokoro/linux.cfg b/test/kokoro/linux.cfg index c13d03f35..63f88d399 100644 --- a/test/kokoro/linux.cfg +++ b/test/kokoro/linux.cfg @@ -14,10 +14,10 @@ # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. -build_file: "grpc-node/packages/grpc-js-xds/scripts/xds.sh" -timeout_mins: 360 +build_file: "grpc-node/test/kokoro.sh" +timeout_mins: 60 action { define_artifacts { - regex: "github/grpc/reports/**" + regex: "github/grpc-node/reports/**/sponge_log.xml" } -} +} \ No newline at end of file From 0a0e13eedecedb31e91ad2d250c5fca6f31e46b6 Mon Sep 17 00:00:00 2001 From: Bart Slinger Date: Thu, 19 May 2022 23:45:11 +0200 Subject: [PATCH 245/694] handle disconnectListeners in reverse to allow listener removal in loop --- packages/grpc-js/src/subchannel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 5d479a73a..eb7d0840c 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -646,7 +646,8 @@ export class Subchannel { this.transitionToState( [ConnectivityState.READY], ConnectivityState.TRANSIENT_FAILURE); - for (const listener of this.disconnectListeners) { + for (let i = this.disconnectListeners.length - 1; i >= 0; i--) { + const listener = this.disconnectListeners[i]; listener(); } } From 97717003f4dce64c64748517cff4a2077484ba04 Mon Sep 17 00:00:00 2001 From: Bart Slinger Date: Fri, 20 May 2022 08:27:01 +0200 Subject: [PATCH 246/694] grpc-js: Use Set instead of Array for disconnectListeners --- packages/grpc-js/src/subchannel.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index eb7d0840c..4d18ca9ca 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -108,7 +108,7 @@ export class Subchannel { * socket disconnects. Used for ending active calls with an UNAVAILABLE * status. */ - private disconnectListeners: Array<() => void> = []; + private disconnectListeners: Set<() => void> = new Set(); private backoffTimeout: BackoffTimeout; @@ -646,8 +646,7 @@ export class Subchannel { this.transitionToState( [ConnectivityState.READY], ConnectivityState.TRANSIENT_FAILURE); - for (let i = this.disconnectListeners.length - 1; i >= 0; i--) { - const listener = this.disconnectListeners[i]; + for (const listener of this.disconnectListeners.values()) { listener(); } } @@ -972,14 +971,11 @@ export class Subchannel { } addDisconnectListener(listener: () => void) { - this.disconnectListeners.push(listener); + this.disconnectListeners.add(listener); } removeDisconnectListener(listener: () => void) { - const listenerIndex = this.disconnectListeners.indexOf(listener); - if (listenerIndex > -1) { - this.disconnectListeners.splice(listenerIndex, 1); - } + this.disconnectListeners.delete(listener); } /** From aa97aa8a1c3e936be6fa4938f6467e044bc66ec6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 23 May 2022 11:28:39 -0700 Subject: [PATCH 247/694] grpc-js-xds: Add support for k8s interop test framework --- packages/grpc-js-xds/interop/Dockerfile | 30 ++++ packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 166 +++++++++++++++++++++ test/kokoro/xds_k8s_lb.cfg | 26 ++++ 3 files changed, 222 insertions(+) create mode 100644 packages/grpc-js-xds/interop/Dockerfile create mode 100755 packages/grpc-js-xds/scripts/xds_k8s_lb.sh create mode 100644 test/kokoro/xds_k8s_lb.cfg diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile new file mode 100644 index 000000000..dbb4433b3 --- /dev/null +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -0,0 +1,30 @@ +# Copyright 2022 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. + +# Dockerfile for building the xDS interop client. To build the image, run the +# following command from grpc-node directory: +# docker build -t -f packages/grpc-js-xds/interop/Dockerfile . + +FROM node:16-alpine + +# Make a grpc-node directory and copy the repo into it. +WORKDIR /node/src/grpc-node +COPY . . + +WORKDIR /node/src/grpc-node/packages/grpc-js +RUN npm install +WORKDIR /node/src/grpc-node/packages/grpc-js-xds +RUN npm install + +ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh new file mode 100755 index 000000000..7672251d6 --- /dev/null +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# Copyright 2022 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. + +set -eo pipefail + +# Constants +readonly GITHUB_REPOSITORY_NAME="grpc-node" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +## xDS test client Docker images +readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:v1.46.x" +readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" +readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" +readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" +readonly LANGUAGE_NAME="Node" + +####################################### +# Builds test app Docker images and pushes them to GCR +# Globals: +# BUILD_APP_PATH +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# Arguments: +# None +# Outputs: +# Writes the output of `gcloud builds submit` to stdout, stderr +####################################### +build_test_app_docker_images() { + echo "Building ${LANGUAGE_NAME} xDS interop test app Docker images" + + pushd "${SRC_DIR}" + docker build \ + -f "${BUILD_APP_PATH}" \ + -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + . + + popd + + gcloud -q auth configure-docker + + docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" +} + +####################################### +# Builds test app and its docker images unless they already exist +# Globals: +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# FORCE_IMAGE_BUILD +# Arguments: +# None +# Outputs: +# Writes the output to stdout, stderr +####################################### +build_docker_images_if_needed() { + # Check if images already exist + client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")" + printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" + echo "${client_tags:-Client image not found}" + + # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1 + if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${client_tags}" ]]; then + build_test_app_docker_images + else + echo "Skipping ${LANGUAGE_NAME} test app build" + fi +} + +####################################### +# Executes the test case +# Globals: +# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile +# KUBE_CONTEXT: The name of kubectl context with GKE cluster access +# SECONDARY_KUBE_CONTEXT: The name of kubectl context with secondary GKE cluster access, if any +# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# Arguments: +# Test case name +# Outputs: +# Writes the output of test execution to stdout, stderr +# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml +####################################### +run_test() { + # Test driver usage: + # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage + local test_name="${1:?Usage: run_test test_name}" + # testing_version is used by the framework to determine the supported PSM + # features. It's captured from Kokoro job name of the Node repo, which takes + # the form: + # grpc/node// + python3 -m "tests.${test_name}" \ + --flagfile="${TEST_DRIVER_FLAGFILE}" \ + --kube_context="${KUBE_CONTEXT}" \ + --secondary_kube_context="${SECONDARY_KUBE_CONTEXT}" \ + --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + --server_image="${SERVER_IMAGE_NAME}" \ + --testing_version=$(echo "$KOKORO_JOB_NAME" | sed -E 's|^grpc/node/([^/]+)/.*|\1|') \ + --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" +} + +####################################### +# Main function: provision software necessary to execute tests, and run them +# Globals: +# KOKORO_ARTIFACTS_DIR +# GITHUB_REPOSITORY_NAME +# SRC_DIR: Populated with absolute path to the source repo +# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing +# the test driver +# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code +# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile +# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report +# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build +# GIT_COMMIT: Populated with the SHA-1 of git commit being built +# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built +# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access +# SECONDARY_KUBE_CONTEXT: Populated with name of kubectl context with secondary GKE cluster access, if any +# Arguments: +# None +# Outputs: +# Writes the output of test execution to stdout, stderr +####################################### +main() { + local script_dir + script_dir="$(dirname "$0")" + + # Source the test driver from the master branch. + echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" + source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" + + activate_gke_cluster GKE_CLUSTER_PSM_SECURITY + activate_secondary_gke_cluster GKE_CLUSTER_PSM_SECURITY + + set -x + if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then + kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}" + else + local_setup_test_driver "${script_dir}" + fi + build_docker_images_if_needed + + # Run tests + cd "${TEST_DRIVER_FULL_DIR}" + local failed_tests=0 + test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test") + for test in "${test_suites[@]}"; do + run_test $test || (( failed_tests++ )) + done + echo "Failed test suites: ${failed_tests}" + if (( failed_tests > 0 )); then + exit 1 + fi +} + +main "$@" \ No newline at end of file diff --git a/test/kokoro/xds_k8s_lb.cfg b/test/kokoro/xds_k8s_lb.cfg new file mode 100644 index 000000000..17a3f3b3b --- /dev/null +++ b/test/kokoro/xds_k8s_lb.cfg @@ -0,0 +1,26 @@ +# Copyright 2022 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/xds_k8s_lb.sh" +timeout_mins: 180 +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*sponge_log.log" + strip_prefix: "artifacts" + } +} \ No newline at end of file From ce169c22b483a27e776e003d7630b158bd0cc7f7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 23 May 2022 15:59:01 -0700 Subject: [PATCH 248/694] Reduce docker image size with extra build step --- packages/grpc-js-xds/interop/Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index dbb4433b3..bc85d3b21 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -16,7 +16,7 @@ # following command from grpc-node directory: # docker build -t -f packages/grpc-js-xds/interop/Dockerfile . -FROM node:16-alpine +FROM node:16-alpine as build # Make a grpc-node directory and copy the repo into it. WORKDIR /node/src/grpc-node @@ -27,4 +27,9 @@ RUN npm install WORKDIR /node/src/grpc-node/packages/grpc-js-xds RUN npm install +FROM node:16-alpine +WORKDIR /node/src/grpc-node +COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ +COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ + ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] From 30bf5a1bac096d4eebeb0b848ede389bff27e226 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 23 May 2022 16:19:07 -0700 Subject: [PATCH 249/694] Fix cluster references Co-authored-by: Sergii Tkachenko --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 7672251d6..8ec1760a1 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -139,8 +139,8 @@ main() { echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" - activate_gke_cluster GKE_CLUSTER_PSM_SECURITY - activate_secondary_gke_cluster GKE_CLUSTER_PSM_SECURITY + activate_gke_cluster GKE_CLUSTER_PSM_LB + activate_secondary_gke_cluster GKE_CLUSTER_PSM_LB set -x if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then From 55175ae8c281307e05f147987822aedf5b4d85ee Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 23 May 2022 16:21:27 -0700 Subject: [PATCH 250/694] Add missing newlines at EOF --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- test/kokoro/xds_k8s_lb.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 8ec1760a1..9cf98d2fc 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -163,4 +163,4 @@ main() { fi } -main "$@" \ No newline at end of file +main "$@" diff --git a/test/kokoro/xds_k8s_lb.cfg b/test/kokoro/xds_k8s_lb.cfg index 17a3f3b3b..b9940cfdc 100644 --- a/test/kokoro/xds_k8s_lb.cfg +++ b/test/kokoro/xds_k8s_lb.cfg @@ -23,4 +23,4 @@ action { regex: "artifacts/**/*sponge_log.log" strip_prefix: "artifacts" } -} \ No newline at end of file +} From bbcf471c99512a178f74e9de7d9dcee363649507 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 23 May 2022 16:30:18 -0700 Subject: [PATCH 251/694] grpc-js: Specify 'types' option in tsconfig file --- packages/grpc-js/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/tsconfig.json b/packages/grpc-js/tsconfig.json index ba675db78..310b633c7 100644 --- a/packages/grpc-js/tsconfig.json +++ b/packages/grpc-js/tsconfig.json @@ -6,7 +6,8 @@ "target": "es2017", "module": "commonjs", "resolveJsonModule": true, - "incremental": true + "incremental": true, + "types": ["mocha"] }, "include": [ "src/**/*.ts", From 67cfbae7aac90c3e60320f72c14dc4ec2985cd14 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 23 May 2022 16:46:51 -0700 Subject: [PATCH 252/694] Use cd instead of WORKDIR in Dockerfile Co-authored-by: Sergii Tkachenko --- packages/grpc-js-xds/interop/Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index bc85d3b21..b18d02a4a 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -22,10 +22,8 @@ FROM node:16-alpine as build WORKDIR /node/src/grpc-node COPY . . -WORKDIR /node/src/grpc-node/packages/grpc-js -RUN npm install -WORKDIR /node/src/grpc-node/packages/grpc-js-xds -RUN npm install +RUN cd packages/grpc-js && npm install +RUN cd packages/grpc-js-xds && npm install FROM node:16-alpine WORKDIR /node/src/grpc-node From ad6d650408f4650c6e7c897c02ae4bf7bc3fddb1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 24 May 2022 09:41:05 -0700 Subject: [PATCH 253/694] Revert "Use cd instead of WORKDIR in Dockerfile" --- packages/grpc-js-xds/interop/Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index b18d02a4a..bc85d3b21 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -22,8 +22,10 @@ FROM node:16-alpine as build WORKDIR /node/src/grpc-node COPY . . -RUN cd packages/grpc-js && npm install -RUN cd packages/grpc-js-xds && npm install +WORKDIR /node/src/grpc-node/packages/grpc-js +RUN npm install +WORKDIR /node/src/grpc-node/packages/grpc-js-xds +RUN npm install FROM node:16-alpine WORKDIR /node/src/grpc-node From ee0c6bc9eaa80e43c82f44e40566b038cb49fd24 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 24 May 2022 10:35:55 -0700 Subject: [PATCH 254/694] Add baseline_test to test list --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 9cf98d2fc..efdde91e7 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -153,7 +153,7 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test") + test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test") for test in "${test_suites[@]}"; do run_test $test || (( failed_tests++ )) done From be749bf94f96a03ec5e3bf0aabed2566f5c79f31 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 24 May 2022 11:14:26 -0700 Subject: [PATCH 255/694] Update submodules in test script --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index efdde91e7..0ff34ac4b 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -135,6 +135,8 @@ main() { local script_dir script_dir="$(dirname "$0")" + git submodule update --init --recursive + # Source the test driver from the master branch. echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" From e61c8f9ecd2c20a1baaeaf284aa813bc8e66473c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 24 May 2022 13:11:41 -0700 Subject: [PATCH 256/694] Change directory before submodules update --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 0ff34ac4b..b4c9f81a2 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -135,6 +135,8 @@ main() { local script_dir script_dir="$(dirname "$0")" + cd "${script_dir}" + git submodule update --init --recursive # Source the test driver from the master branch. From e94bd36bf15d75fb3e49f52eeddadc371702b90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Conall=20=C3=93=20Cofaigh?= Date: Mon, 30 May 2022 13:54:05 +0100 Subject: [PATCH 257/694] bump protobufjs to "^6.11.3" --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 759e54713..53b6e829d 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -48,7 +48,7 @@ "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.10.0", + "protobufjs": "^6.11.3", "yargs": "^16.2.0" }, "devDependencies": { From b78db9d2225a391e23a0e6cefd2b85f919e708f1 Mon Sep 17 00:00:00 2001 From: Andrew Matheny Date: Wed, 1 Jun 2022 12:33:31 -0400 Subject: [PATCH 258/694] Propagate callEnd events --- packages/grpc-js/src/server-call.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 315c9d9aa..725199e3a 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -726,6 +726,8 @@ export class Http2ServerCallStream< call.cancelled = true; call.emit('cancelled', reason); }); + + this.once('callEnd', (status) => call.emit('callEnd', status)); } setupReadable( From d846cf51272f2ed03c1dd8b845536fa7a901f396 Mon Sep 17 00:00:00 2001 From: Andrew Matheny Date: Wed, 1 Jun 2022 12:34:11 -0400 Subject: [PATCH 259/694] Expose http path in call --- packages/grpc-js/src/server-call.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 725199e3a..1f7f3eb04 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -80,6 +80,7 @@ export type ServerSurfaceCall = { getPeer(): string; sendMetadata(responseMetadata: Metadata): void; getDeadline(): Deadline; + getPath(): string; } & EventEmitter; export type ServerUnaryCall = ServerSurfaceCall & { @@ -127,6 +128,10 @@ export class ServerUnaryCallImpl getDeadline(): Deadline { return this.call.getDeadline(); } + + getPath(): string { + return this.call.getPath(); + } } export class ServerReadableStreamImpl @@ -165,6 +170,10 @@ export class ServerReadableStreamImpl getDeadline(): Deadline { return this.call.getDeadline(); } + + getPath(): string { + return this.call.getPath(); + } } export class ServerWritableStreamImpl @@ -202,6 +211,10 @@ export class ServerWritableStreamImpl return this.call.getDeadline(); } + getPath(): string { + return this.call.getPath(); + } + _write( chunk: ResponseType, encoding: string, @@ -279,6 +292,10 @@ export class ServerDuplexStreamImpl return this.call.getDeadline(); } + getPath(): string { + return this.call.getPath(); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any end(metadata?: any) { if (metadata) { @@ -901,6 +918,10 @@ export class Http2ServerCallStream< getDeadline(): Deadline { return this.deadline; } + + getPath(): string { + return this.handler.path; + } } /* eslint-disable @typescript-eslint/no-explicit-any */ From 475559f976088deae1c3d8a512fd9c57425ddba3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 6 Jun 2022 14:08:13 -0700 Subject: [PATCH 260/694] proto-loader: Increment version to 0.6.13 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 53b6e829d..65639c501 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.12", + "version": "0.6.13", "author": "Google Inc.", "contributors": [ { From 07b73ad129e0368dfa95fab531d45ab754b9844b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Jun 2022 11:07:47 -0700 Subject: [PATCH 261/694] grpc-js: Add a test for compressing large messages --- .../grpc-js/test/fixtures/test_service.proto | 1 + packages/grpc-js/test/generated/Response.ts | 2 ++ .../grpc-js/test/generated/TestService.ts | 32 +++++++++---------- packages/grpc-js/test/test-server.ts | 16 +++++++++- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/packages/grpc-js/test/fixtures/test_service.proto b/packages/grpc-js/test/fixtures/test_service.proto index f99393d14..64ce0d378 100644 --- a/packages/grpc-js/test/fixtures/test_service.proto +++ b/packages/grpc-js/test/fixtures/test_service.proto @@ -25,6 +25,7 @@ message Request { message Response { int32 count = 1; + string message = 2; } service TestService { diff --git a/packages/grpc-js/test/generated/Response.ts b/packages/grpc-js/test/generated/Response.ts index 217fc75e8..465ab7203 100644 --- a/packages/grpc-js/test/generated/Response.ts +++ b/packages/grpc-js/test/generated/Response.ts @@ -3,8 +3,10 @@ export interface Response { 'count'?: (number); + 'message'?: (string); } export interface Response__Output { 'count': (number); + 'message': (string); } diff --git a/packages/grpc-js/test/generated/TestService.ts b/packages/grpc-js/test/generated/TestService.ts index 75bff33e4..e477c99b5 100644 --- a/packages/grpc-js/test/generated/TestService.ts +++ b/packages/grpc-js/test/generated/TestService.ts @@ -11,28 +11,28 @@ export interface TestServiceClient extends grpc.Client { bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_Request, _Response__Output>; - ClientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; - ClientStream(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; - ClientStream(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; - ClientStream(callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; - clientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; - clientStream(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; - clientStream(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; - clientStream(callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientWritableStream<_Request>; + ClientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + ClientStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + ClientStream(options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + ClientStream(callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; + clientStream(callback: grpc.requestCallback<_Response__Output>): grpc.ClientWritableStream<_Request>; ServerStream(argument: _Request, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; ServerStream(argument: _Request, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; serverStream(argument: _Request, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; serverStream(argument: _Request, options?: grpc.CallOptions): grpc.ClientReadableStream<_Response__Output>; - Unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; - Unary(argument: _Request, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; - Unary(argument: _Request, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; - Unary(argument: _Request, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; - unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; - unary(argument: _Request, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; - unary(argument: _Request, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; - unary(argument: _Request, callback: (error?: grpc.ServiceError, result?: _Response__Output) => void): grpc.ClientUnaryCall; + Unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + Unary(argument: _Request, metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + Unary(argument: _Request, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + Unary(argument: _Request, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, metadata: grpc.Metadata, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, options: grpc.CallOptions, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; + unary(argument: _Request, callback: grpc.requestCallback<_Response__Output>): grpc.ClientUnaryCall; } diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 2513b1fe2..0c0ba168e 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -623,7 +623,7 @@ describe('Generic client and server', () => { describe('Compressed requests', () => { const testServiceHandlers: TestServiceHandlers = { Unary(call, callback) { - callback(null, { count: 500000 }); + callback(null, { count: 500000, message: call.request.message }); }, ClientStream(call, callback) { @@ -847,6 +847,20 @@ describe('Compressed requests', () => { }) }); }); + + it('Should handle large messages', done => { + let longMessage = ''; + for (let i = 0; i < 400000; i++) { + const letter = 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]; + longMessage = longMessage + letter.repeat(10); + } + + client.unary({message: longMessage}, (err, response) => { + assert.ifError(err); + assert.strictEqual(response?.message, longMessage); + done(); + }) + }) /* As of Node 16, Writable and Duplex streams validate the encoding * argument to write, and the flags values we are passing there are not From 0c543aa2b27f9ea2890bbe3d7cb88c696412225d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 15 Jun 2022 10:09:53 -0700 Subject: [PATCH 262/694] xDS k8s interop script: publish version-tagged docker images --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index b4c9f81a2..0d333b7ee 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -45,6 +45,11 @@ build_test_app_docker_images() { -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . + if [[ -n $KOKORO_JOB_NAME ]]; then + branch_name=$(echo "$KOKORO_JOB_NAME" | sed -E 's|^grpc/node/([^/]+)/.*|\1|') + tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${branch_name}" + fi + popd gcloud -q auth configure-docker From e4435a50f6c857dfd8edbade4856c172b8856617 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 21 Jun 2022 15:51:03 -0700 Subject: [PATCH 263/694] Use new TESTING_VERSION variable --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 0d333b7ee..58a951376 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -45,9 +45,8 @@ build_test_app_docker_images() { -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . - if [[ -n $KOKORO_JOB_NAME ]]; then - branch_name=$(echo "$KOKORO_JOB_NAME" | sed -E 's|^grpc/node/([^/]+)/.*|\1|') - tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${branch_name}" + if is_version_branch "${TESTING_VERSION}"; then + tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" fi popd @@ -111,7 +110,7 @@ run_test() { --secondary_kube_context="${SECONDARY_KUBE_CONTEXT}" \ --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ --server_image="${SERVER_IMAGE_NAME}" \ - --testing_version=$(echo "$KOKORO_JOB_NAME" | sed -E 's|^grpc/node/([^/]+)/.*|\1|') \ + --testing_version="${TESTING_VERSION}" \ --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" } From 92cf5df8d57431a6eca94a6bec057363b29a8182 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 23 Jun 2022 14:33:15 -0700 Subject: [PATCH 264/694] Add xDS k8s url_map test script --- .../grpc-js-xds/scripts/xds_k8s_url_map.sh | 152 ++++++++++++++++++ test/kokoro/xds_k8s_url_map.cfg | 26 +++ 2 files changed, 178 insertions(+) create mode 100644 packages/grpc-js-xds/scripts/xds_k8s_url_map.sh create mode 100644 test/kokoro/xds_k8s_url_map.cfg diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh new file mode 100644 index 000000000..ca33cf0d2 --- /dev/null +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# Copyright 2021 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. + +set -eo pipefail + +# Constants +readonly GITHUB_REPOSITORY_NAME="grpc-node" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +## xDS test client Docker images +readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" +readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" +readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" +readonly LANGUAGE_NAME="Node" + +####################################### +# Builds test app Docker images and pushes them to GCR +# Globals: +# BUILD_APP_PATH +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# Arguments: +# None +# Outputs: +# Writes the output of `gcloud builds submit` to stdout, stderr +####################################### +build_test_app_docker_images() { + echo "Building ${LANGUAGE_NAME} xDS interop test app Docker images" + + pushd "${SRC_DIR}" + docker build \ + -f src/python/grpcio_tests/tests_py3_only/interop/Dockerfile.client \ + -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + . + + popd + + gcloud -q auth configure-docker + + docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" +} + +####################################### +# Builds test app and its docker images unless they already exist +# Globals: +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# FORCE_IMAGE_BUILD +# Arguments: +# None +# Outputs: +# Writes the output to stdout, stderr +####################################### +build_docker_images_if_needed() { + # Check if images already exist + client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")" + printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" + echo "${client_tags:-Client image not found}" + + # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1 + if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${client_tags}" ]]; then + build_test_app_docker_images + else + echo "Skipping ${LANGUAGE_NAME} test app build" + fi +} + +####################################### +# Executes the test case +# Globals: +# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile +# KUBE_CONTEXT: The name of kubectl context with GKE cluster access +# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# TESTING_VERSION: version branch under test: used by the framework to determine the supported PSM +# features. +# Arguments: +# Test case name +# Outputs: +# Writes the output of test execution to stdout, stderr +# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml +####################################### +run_test() { + # Test driver usage: + # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage + local test_name="${1:?Usage: run_test test_name}" + set -x + python3 -m "tests.${test_name}" \ + --flagfile="${TEST_DRIVER_FLAGFILE}" \ + --kube_context="${KUBE_CONTEXT}" \ + --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + --testing_version="${TESTING_VERSION}" \ + --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \ + --flagfile="config/url-map.cfg" + set +x +} + +####################################### +# Main function: provision software necessary to execute tests, and run them +# Globals: +# KOKORO_ARTIFACTS_DIR +# GITHUB_REPOSITORY_NAME +# SRC_DIR: Populated with absolute path to the source repo +# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing +# the test driver +# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code +# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile +# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report +# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build +# GIT_COMMIT: Populated with the SHA-1 of git commit being built +# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built +# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access +# Arguments: +# None +# Outputs: +# Writes the output of test execution to stdout, stderr +####################################### +main() { + local script_dir + script_dir="$(dirname "$0")" + + # Source the test driver from the master branch. + echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" + source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" + + activate_gke_cluster GKE_CLUSTER_PSM_BASIC + + set -x + if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then + kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}" + else + local_setup_test_driver "${script_dir}" + fi + build_docker_images_if_needed + # Run tests + cd "${TEST_DRIVER_FULL_DIR}" + run_test url_map +} + +main "$@" diff --git a/test/kokoro/xds_k8s_url_map.cfg b/test/kokoro/xds_k8s_url_map.cfg new file mode 100644 index 000000000..dd4cce76d --- /dev/null +++ b/test/kokoro/xds_k8s_url_map.cfg @@ -0,0 +1,26 @@ +# Copyright 2022 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh" +timeout_mins: 180 +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*sponge_log.log" + strip_prefix: "artifacts" + } +} From 47461134fb1a282517e8c2717f3e98c26488f229 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 23 Jun 2022 15:28:22 -0700 Subject: [PATCH 265/694] Update copyright date --- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index ca33cf0d2..901938ad2 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright 2021 gRPC authors. +# Copyright 2022 gRPC authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 1ad6a58059b6637a408b836d8ad591135e859d3d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 24 Jun 2022 10:20:01 -0700 Subject: [PATCH 266/694] xDS k8s tests: fix a line in the url_map test script The line copied from the Python script was hardcoded and not handled by a variable, so I did not change it on the first pass. --- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index 901938ad2..bf2d4df67 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -40,7 +40,7 @@ build_test_app_docker_images() { pushd "${SRC_DIR}" docker build \ - -f src/python/grpcio_tests/tests_py3_only/interop/Dockerfile.client \ + -f ${BUILD_APP_PATH} \ -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . From 5a039aa90b743919782cd64d349830ce46162f82 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 24 Jun 2022 15:29:52 -0700 Subject: [PATCH 267/694] Use quotation marks for variable substitution Co-authored-by: Sergii Tkachenko --- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index bf2d4df67..8b07b655e 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -40,7 +40,7 @@ build_test_app_docker_images() { pushd "${SRC_DIR}" docker build \ - -f ${BUILD_APP_PATH} \ + -f "${BUILD_APP_PATH}" \ -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . From 4db0cd2ac995efe08185f6b825b2aa641441dc57 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 24 Jun 2022 16:08:59 -0700 Subject: [PATCH 268/694] xDS k8s tests: fix docker auth error in the build scripts Docker auth needed to be done before pushing thats to the GCR. Also add missing versioned image tagging to xds_k8s_url_map.sh. --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 8 +++----- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 8 +++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 58a951376..ca300f0c2 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -31,6 +31,7 @@ readonly LANGUAGE_NAME="Node" # BUILD_APP_PATH # CLIENT_IMAGE_NAME: Test client Docker image name # GIT_COMMIT: SHA-1 of git commit being built +# TESTING_VERSION: version branch under test, f.e. v1.42.x, master # Arguments: # None # Outputs: @@ -45,15 +46,12 @@ build_test_app_docker_images() { -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . + gcloud -q auth configure-docker + docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" if is_version_branch "${TESTING_VERSION}"; then tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" fi - popd - - gcloud -q auth configure-docker - - docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" } ####################################### diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index 901938ad2..a865e4e5b 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -30,6 +30,7 @@ readonly LANGUAGE_NAME="Node" # BUILD_APP_PATH # CLIENT_IMAGE_NAME: Test client Docker image name # GIT_COMMIT: SHA-1 of git commit being built +# TESTING_VERSION: version branch under test, f.e. v1.42.x, master # Arguments: # None # Outputs: @@ -44,11 +45,12 @@ build_test_app_docker_images() { -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . - popd - gcloud -q auth configure-docker - docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" + if is_version_branch "${TESTING_VERSION}"; then + tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" + fi + popd } ####################################### From 1463ffa42ed2d5de7c23c772684b944a60c5c90c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 28 Jun 2022 14:05:29 -0700 Subject: [PATCH 269/694] Backport xDS k8s test scripts to the v1.6.x branch --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 172 ++++++++++++++++++ .../grpc-js-xds/scripts/xds_k8s_url_map.sh | 154 ++++++++++++++++ test/kokoro/xds_k8s_lb.cfg | 26 +++ test/kokoro/xds_k8s_url_map.cfg | 26 +++ 4 files changed, 378 insertions(+) create mode 100755 packages/grpc-js-xds/scripts/xds_k8s_lb.sh create mode 100644 packages/grpc-js-xds/scripts/xds_k8s_url_map.sh create mode 100644 test/kokoro/xds_k8s_lb.cfg create mode 100644 test/kokoro/xds_k8s_url_map.cfg diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh new file mode 100755 index 000000000..ca300f0c2 --- /dev/null +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -0,0 +1,172 @@ +#!/usr/bin/env bash +# Copyright 2022 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. + +set -eo pipefail + +# Constants +readonly GITHUB_REPOSITORY_NAME="grpc-node" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +## xDS test client Docker images +readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:v1.46.x" +readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" +readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" +readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" +readonly LANGUAGE_NAME="Node" + +####################################### +# Builds test app Docker images and pushes them to GCR +# Globals: +# BUILD_APP_PATH +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# TESTING_VERSION: version branch under test, f.e. v1.42.x, master +# Arguments: +# None +# Outputs: +# Writes the output of `gcloud builds submit` to stdout, stderr +####################################### +build_test_app_docker_images() { + echo "Building ${LANGUAGE_NAME} xDS interop test app Docker images" + + pushd "${SRC_DIR}" + docker build \ + -f "${BUILD_APP_PATH}" \ + -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + . + + gcloud -q auth configure-docker + docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" + if is_version_branch "${TESTING_VERSION}"; then + tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" + fi + popd +} + +####################################### +# Builds test app and its docker images unless they already exist +# Globals: +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# FORCE_IMAGE_BUILD +# Arguments: +# None +# Outputs: +# Writes the output to stdout, stderr +####################################### +build_docker_images_if_needed() { + # Check if images already exist + client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")" + printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" + echo "${client_tags:-Client image not found}" + + # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1 + if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${client_tags}" ]]; then + build_test_app_docker_images + else + echo "Skipping ${LANGUAGE_NAME} test app build" + fi +} + +####################################### +# Executes the test case +# Globals: +# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile +# KUBE_CONTEXT: The name of kubectl context with GKE cluster access +# SECONDARY_KUBE_CONTEXT: The name of kubectl context with secondary GKE cluster access, if any +# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# Arguments: +# Test case name +# Outputs: +# Writes the output of test execution to stdout, stderr +# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml +####################################### +run_test() { + # Test driver usage: + # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage + local test_name="${1:?Usage: run_test test_name}" + # testing_version is used by the framework to determine the supported PSM + # features. It's captured from Kokoro job name of the Node repo, which takes + # the form: + # grpc/node// + python3 -m "tests.${test_name}" \ + --flagfile="${TEST_DRIVER_FLAGFILE}" \ + --kube_context="${KUBE_CONTEXT}" \ + --secondary_kube_context="${SECONDARY_KUBE_CONTEXT}" \ + --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + --server_image="${SERVER_IMAGE_NAME}" \ + --testing_version="${TESTING_VERSION}" \ + --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" +} + +####################################### +# Main function: provision software necessary to execute tests, and run them +# Globals: +# KOKORO_ARTIFACTS_DIR +# GITHUB_REPOSITORY_NAME +# SRC_DIR: Populated with absolute path to the source repo +# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing +# the test driver +# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code +# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile +# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report +# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build +# GIT_COMMIT: Populated with the SHA-1 of git commit being built +# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built +# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access +# SECONDARY_KUBE_CONTEXT: Populated with name of kubectl context with secondary GKE cluster access, if any +# Arguments: +# None +# Outputs: +# Writes the output of test execution to stdout, stderr +####################################### +main() { + local script_dir + script_dir="$(dirname "$0")" + + cd "${script_dir}" + + git submodule update --init --recursive + + # Source the test driver from the master branch. + echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" + source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" + + activate_gke_cluster GKE_CLUSTER_PSM_LB + activate_secondary_gke_cluster GKE_CLUSTER_PSM_LB + + set -x + if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then + kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}" + else + local_setup_test_driver "${script_dir}" + fi + build_docker_images_if_needed + + # Run tests + cd "${TEST_DRIVER_FULL_DIR}" + local failed_tests=0 + test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test") + for test in "${test_suites[@]}"; do + run_test $test || (( failed_tests++ )) + done + echo "Failed test suites: ${failed_tests}" + if (( failed_tests > 0 )); then + exit 1 + fi +} + +main "$@" diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh new file mode 100644 index 000000000..4371f235c --- /dev/null +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# Copyright 2022 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. + +set -eo pipefail + +# Constants +readonly GITHUB_REPOSITORY_NAME="grpc-node" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +## xDS test client Docker images +readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" +readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" +readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" +readonly LANGUAGE_NAME="Node" + +####################################### +# Builds test app Docker images and pushes them to GCR +# Globals: +# BUILD_APP_PATH +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# TESTING_VERSION: version branch under test, f.e. v1.42.x, master +# Arguments: +# None +# Outputs: +# Writes the output of `gcloud builds submit` to stdout, stderr +####################################### +build_test_app_docker_images() { + echo "Building ${LANGUAGE_NAME} xDS interop test app Docker images" + + pushd "${SRC_DIR}" + docker build \ + -f "${BUILD_APP_PATH}" \ + -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + . + + gcloud -q auth configure-docker + docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" + if is_version_branch "${TESTING_VERSION}"; then + tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" + fi + popd +} + +####################################### +# Builds test app and its docker images unless they already exist +# Globals: +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# FORCE_IMAGE_BUILD +# Arguments: +# None +# Outputs: +# Writes the output to stdout, stderr +####################################### +build_docker_images_if_needed() { + # Check if images already exist + client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")" + printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" + echo "${client_tags:-Client image not found}" + + # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1 + if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${client_tags}" ]]; then + build_test_app_docker_images + else + echo "Skipping ${LANGUAGE_NAME} test app build" + fi +} + +####################################### +# Executes the test case +# Globals: +# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile +# KUBE_CONTEXT: The name of kubectl context with GKE cluster access +# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# TESTING_VERSION: version branch under test: used by the framework to determine the supported PSM +# features. +# Arguments: +# Test case name +# Outputs: +# Writes the output of test execution to stdout, stderr +# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml +####################################### +run_test() { + # Test driver usage: + # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage + local test_name="${1:?Usage: run_test test_name}" + set -x + python3 -m "tests.${test_name}" \ + --flagfile="${TEST_DRIVER_FLAGFILE}" \ + --kube_context="${KUBE_CONTEXT}" \ + --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ + --testing_version="${TESTING_VERSION}" \ + --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \ + --flagfile="config/url-map.cfg" + set +x +} + +####################################### +# Main function: provision software necessary to execute tests, and run them +# Globals: +# KOKORO_ARTIFACTS_DIR +# GITHUB_REPOSITORY_NAME +# SRC_DIR: Populated with absolute path to the source repo +# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing +# the test driver +# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code +# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile +# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report +# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build +# GIT_COMMIT: Populated with the SHA-1 of git commit being built +# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built +# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access +# Arguments: +# None +# Outputs: +# Writes the output of test execution to stdout, stderr +####################################### +main() { + local script_dir + script_dir="$(dirname "$0")" + + # Source the test driver from the master branch. + echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" + source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" + + activate_gke_cluster GKE_CLUSTER_PSM_BASIC + + set -x + if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then + kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}" + else + local_setup_test_driver "${script_dir}" + fi + build_docker_images_if_needed + # Run tests + cd "${TEST_DRIVER_FULL_DIR}" + run_test url_map +} + +main "$@" diff --git a/test/kokoro/xds_k8s_lb.cfg b/test/kokoro/xds_k8s_lb.cfg new file mode 100644 index 000000000..b9940cfdc --- /dev/null +++ b/test/kokoro/xds_k8s_lb.cfg @@ -0,0 +1,26 @@ +# Copyright 2022 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/xds_k8s_lb.sh" +timeout_mins: 180 +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*sponge_log.log" + strip_prefix: "artifacts" + } +} diff --git a/test/kokoro/xds_k8s_url_map.cfg b/test/kokoro/xds_k8s_url_map.cfg new file mode 100644 index 000000000..dd4cce76d --- /dev/null +++ b/test/kokoro/xds_k8s_url_map.cfg @@ -0,0 +1,26 @@ +# Copyright 2022 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. + +# Config file for Kokoro (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc-node/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh" +timeout_mins: 180 +action { + define_artifacts { + regex: "artifacts/**/*sponge_log.xml" + regex: "artifacts/**/*sponge_log.log" + strip_prefix: "artifacts" + } +} From 15a962aab629bf7c3c001407f4244279fc72c6e3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Jul 2022 10:37:15 -0700 Subject: [PATCH 270/694] Enable xDS tracing in k8s framework tests --- packages/grpc-js-xds/interop/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index bc85d3b21..e8a0a98cb 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -32,4 +32,7 @@ WORKDIR /node/src/grpc-node COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ +ENV GRPC_VERBOSITY="DEBUG" +ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds + ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] From c6d7d2aa033b2ebd88bd010552124c47e9af79e8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Jul 2022 14:34:20 -0700 Subject: [PATCH 271/694] Backport xDS k8s interop docker image to version branch --- packages/grpc-js-xds/interop/Dockerfile | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 packages/grpc-js-xds/interop/Dockerfile diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile new file mode 100644 index 000000000..e8a0a98cb --- /dev/null +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -0,0 +1,38 @@ +# Copyright 2022 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. + +# Dockerfile for building the xDS interop client. To build the image, run the +# following command from grpc-node directory: +# docker build -t -f packages/grpc-js-xds/interop/Dockerfile . + +FROM node:16-alpine as build + +# Make a grpc-node directory and copy the repo into it. +WORKDIR /node/src/grpc-node +COPY . . + +WORKDIR /node/src/grpc-node/packages/grpc-js +RUN npm install +WORKDIR /node/src/grpc-node/packages/grpc-js-xds +RUN npm install + +FROM node:16-alpine +WORKDIR /node/src/grpc-node +COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ +COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ + +ENV GRPC_VERBOSITY="DEBUG" +ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds + +ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] From 914ccdc19d52b22e7ea5fc5e8250984a68165201 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 8 Jul 2022 10:05:07 -0700 Subject: [PATCH 272/694] Clone submodules in xds k8s url map script --- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index 4371f235c..c29d0c0a5 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -133,6 +133,10 @@ main() { local script_dir script_dir="$(dirname "$0")" + cd "${script_dir}" + + git submodule update --init --recursive + # Source the test driver from the master branch. echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" From 6641494e029e648206b42e218a4a8d9536955f8b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 8 Jul 2022 10:05:07 -0700 Subject: [PATCH 273/694] Clone submodules in xds k8s url map script --- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index 4371f235c..c29d0c0a5 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -133,6 +133,10 @@ main() { local script_dir script_dir="$(dirname "$0")" + cd "${script_dir}" + + git submodule update --init --recursive + # Source the test driver from the master branch. echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" From 7b4704cc921cddf7c6084c69704ac940c4450b98 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Jul 2022 11:32:04 -0700 Subject: [PATCH 274/694] proto-loader: Update protobufjs dependency to 7.x --- packages/proto-loader/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 65639c501..07e66c9fe 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.6.13", + "version": "0.7.0", "author": "Google Inc.", "contributors": [ { @@ -48,7 +48,7 @@ "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^6.11.3", + "protobufjs": "^7.0.0", "yargs": "^16.2.0" }, "devDependencies": { From 27b7bb8928118292892dd5727089be32b325fed3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 19 Jul 2022 10:52:02 -0700 Subject: [PATCH 275/694] grpc-js: Update proto-loader dependency to ^0.7.0 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 723b108ff..5fae717a4 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -58,7 +58,7 @@ "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto" }, "dependencies": { - "@grpc/proto-loader": "^0.6.4", + "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" }, "files": [ From 50c58238ff62b5e8d94fd4e795ff42ebc76d08a4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 20 Jul 2022 15:22:53 -0700 Subject: [PATCH 276/694] grpc-js: Update version to 1.6.8 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 5fae717a4..3ceaefebe 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.7", + "version": "1.6.8", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 3ab4ee34678d4e026013c55b2821da0553e737ea Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 21 Jul 2022 17:48:28 +0100 Subject: [PATCH 277/694] include .map files in proto-loader npm package --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 07e66c9fe..550346d04 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -38,7 +38,7 @@ "files": [ "LICENSE", "build/src/*.d.ts", - "build/src/*.js", + "build/src/*.{js,js.map}", "build/bin/*.js" ], "bin": { From 924fb9a3297febe3cceb8000822b80b8f5ffcee1 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Fri, 22 Jul 2022 09:23:22 +0100 Subject: [PATCH 278/694] include proto-loader-gen-types.js.map in release --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 550346d04..c306fe9b6 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -39,7 +39,7 @@ "LICENSE", "build/src/*.d.ts", "build/src/*.{js,js.map}", - "build/bin/*.js" + "build/bin/*.{js,js.map}" ], "bin": { "proto-loader-gen-types": "./build/bin/proto-loader-gen-types.js" From fbf7944646b4286b97a93c15274180ab5ac29b6c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 22 Jul 2022 12:58:22 -0700 Subject: [PATCH 279/694] grpc-js: Outlier detection: Fix standard deviation calculation --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 2c231c406..dde618b73 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -430,11 +430,12 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { // Step 2 const successRateMean = successRates.reduce((a, b) => a + b) / successRates.length; - let successRateVariance = 0; + let successRateDeviationSum = 0; for (const rate of successRates) { const deviation = rate - successRateMean; - successRateVariance += deviation * deviation; + successRateDeviationSum += deviation * deviation; } + const successRateVariance = successRateDeviationSum / successRates.length; const successRateStdev = Math.sqrt(successRateVariance); const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000); From 90e8886d988bd5348c994bfb5d29a88df6a84782 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 2 Aug 2022 11:02:02 -0700 Subject: [PATCH 280/694] grpc-js: Add outlier detection tracing and enable it in interop tests --- packages/grpc-js-xds/interop/Dockerfile | 2 +- .../src/load-balancer-outlier-detection.ts | 31 ++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index e8a0a98cb..b93e309d7 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -33,6 +33,6 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ ENV GRPC_VERBOSITY="DEBUG" -ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds +ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index dde618b73..e9793beab 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -18,7 +18,7 @@ import { ChannelOptions, connectivityState, StatusObject } from "."; import { Call } from "./call-stream"; import { ConnectivityState } from "./connectivity-state"; -import { Status } from "./constants"; +import { LogVerbosity, Status } from "./constants"; import { durationToMs, isDuration, msToDuration } from "./duration"; import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental"; import { BaseFilter, Filter, FilterFactory } from "./filter"; @@ -28,7 +28,13 @@ import { PickArgs, Picker, PickResult, PickResultType, QueuePicker, UnavailableP import { Subchannel } from "./subchannel"; import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address"; import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface"; +import * as logging from './logging'; +const TRACER_NAME = 'outlier_detection'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} const TYPE_NAME = 'outlier_detection'; @@ -412,6 +418,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (!successRateConfig) { return; } + trace('Running success rate check'); // Step 1 const targetRequestVolume = successRateConfig.request_volume; let addresesWithTargetVolume = 0; @@ -424,6 +431,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { successRates.push(successes/(successes + failures)); } } + trace('Found ' + addresesWithTargetVolume + ' success rate candidates; currentEjectionPercent=' + this.getCurrentEjectionPercent() + ' successRates=[' + successRates + ']'); if (addresesWithTargetVolume < successRateConfig.minimum_hosts) { return; } @@ -438,9 +446,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const successRateVariance = successRateDeviationSum / successRates.length; const successRateStdev = Math.sqrt(successRateVariance); const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000); + trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold); // Step 3 - for (const mapEntry of this.addressMap.values()) { + for (const [address, mapEntry] of this.addressMap.entries()) { // Step 3.i if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { break; @@ -453,9 +462,12 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } // Step 3.iii const successRate = successes / (successes + failures); + trace('Checking candidate ' + address + ' successRate=' + successRate); if (successRate < ejectionThreshold) { const randomNumber = Math.random() * 100; + trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + successRateConfig.enforcement_percentage); if (randomNumber < successRateConfig.enforcement_percentage) { + trace('Ejecting candidate ' + address); this.eject(mapEntry, ejectionTimestamp); } } @@ -470,13 +482,14 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (!failurePercentageConfig) { return; } + trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume); // Step 1 if (this.addressMap.size < failurePercentageConfig.minimum_hosts) { return; } // Step 2 - for (const mapEntry of this.addressMap.values()) { + for (const [address, mapEntry] of this.addressMap.entries()) { // Step 2.i if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { break; @@ -484,6 +497,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { // Step 2.ii const successes = mapEntry.counter.getLastSuccesses(); const failures = mapEntry.counter.getLastFailures(); + trace('Candidate successes=' + successes + ' failures=' + failures); if (successes + failures < failurePercentageConfig.request_volume) { continue; } @@ -491,7 +505,9 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const failurePercentage = (failures * 100) / (failures + successes); if (failurePercentage > failurePercentageConfig.threshold) { const randomNumber = Math.random() * 100; + trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + failurePercentageConfig.enforcement_percentage); if (randomNumber < failurePercentageConfig.enforcement_percentage) { + trace('Ejecting candidate ' + address); this.eject(mapEntry, ejectionTimestamp); } } @@ -525,6 +541,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private runChecks() { const ejectionTimestamp = new Date(); + trace('Ejection timer running'); this.switchAllBuckets(); @@ -537,7 +554,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { this.runSuccessRateCheck(ejectionTimestamp); this.runFailurePercentageCheck(ejectionTimestamp); - for (const mapEntry of this.addressMap.values()) { + for (const [address, mapEntry] of this.addressMap.entries()) { if (mapEntry.currentEjectionTimestamp === null) { if (mapEntry.ejectionTimeMultiplier > 0) { mapEntry.ejectionTimeMultiplier -= 1; @@ -548,6 +565,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime()); returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs))); if (returnTime < new Date()) { + trace('Unejecting ' + address); this.uneject(mapEntry); } } @@ -564,6 +582,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } for (const address of subchannelAddresses) { if (!this.addressMap.has(address)) { + trace('Adding map entry for ' + address); this.addressMap.set(address, { counter: new CallCounter(), currentEjectionTimestamp: null, @@ -574,6 +593,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } for (const key of this.addressMap.keys()) { if (!subchannelAddresses.has(key)) { + trace('Removing map entry for ' + key); this.addressMap.delete(key); } } @@ -585,15 +605,18 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) { if (this.timerStartTime) { + trace('Previous timer existed. Replacing timer'); clearTimeout(this.ejectionTimer); const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime()); this.startTimer(remainingDelay); } else { + trace('Starting new timer'); this.timerStartTime = new Date(); this.startTimer(lbConfig.getIntervalMs()); this.switchAllBuckets(); } } else { + trace('Counting disabled. Cancelling timer.'); this.timerStartTime = null; clearTimeout(this.ejectionTimer); } From 1e531501550d989c7260370261ecf9423b3cf599 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 2 Aug 2022 13:48:16 -0700 Subject: [PATCH 281/694] Update outlier detection behavior for gRFC updates --- packages/grpc-js-xds/src/load-balancer-cds.ts | 5 ++--- packages/grpc-js/src/load-balancer-outlier-detection.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index a6dbf42f0..4d47b2546 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -79,9 +79,8 @@ function translateOutlierDetectionConfig(outlierDetection: OutlierDetection__Out return undefined; } if (!outlierDetection) { - /* No-op outlier detection config, with max possible interval and no - * ejection criteria configured. */ - return new OutlierDetectionLoadBalancingConfig(~(1<<31), null, null, null, null, null, []); + /* No-op outlier detection config, with all fields unset. */ + return new OutlierDetectionLoadBalancingConfig(null, null, null, null, null, null, []); } let successRateConfig: Partial | null = null; /* Success rate ejection is enabled by default, so we only disable it if diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index e9793beab..4219e293a 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -357,7 +357,10 @@ class OutlierDetectionPicker implements Picker { extraFilterFactories: extraFilterFactories }; } else { - return wrappedPick; + return { + ...wrappedPick, + subchannel: subchannelWrapper.getWrappedSubchannel() + } } } else { return wrappedPick; @@ -619,6 +622,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { trace('Counting disabled. Cancelling timer.'); this.timerStartTime = null; clearTimeout(this.ejectionTimer); + for (const mapEntry of this.addressMap.values()) { + this.uneject(mapEntry); + mapEntry.ejectionTimeMultiplier = 0; + } } this.latestConfig = lbConfig; From b3f23d805efd1f30d44375c62de82845e78d5fd2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Aug 2022 12:54:15 -0700 Subject: [PATCH 282/694] grpc-js: Implement getConnectivityState in subchannel wrapper --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 4219e293a..afbeb3459 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -215,6 +215,14 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements }); } + getConnectivityState(): connectivityState { + if (this.ejected) { + return ConnectivityState.TRANSIENT_FAILURE; + } else { + return this.childSubchannelState; + } + } + /** * Add a listener function to be called whenever the wrapper's * connectivity state changes. From 78fe8c6d05f82eddc270dcbd3995868e3a49c038 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Aug 2022 15:56:28 -0700 Subject: [PATCH 283/694] grpc-js: Initialize connectivity state from subchannel in outlier detection subchannel wrapper --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index afbeb3459..0ef401c9a 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -199,12 +199,13 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig } class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { - private childSubchannelState: ConnectivityState = ConnectivityState.IDLE; + private childSubchannelState: ConnectivityState; private stateListeners: ConnectivityStateListener[] = []; private ejected: boolean = false; private refCount: number = 0; constructor(childSubchannel: SubchannelInterface, private mapEntry?: MapEntry) { super(childSubchannel); + this.childSubchannelState = childSubchannel.getConnectivityState(); childSubchannel.addConnectivityStateListener((subchannel, previousState, newState) => { this.childSubchannelState = newState; if (!this.ejected) { From 001cce7db07ee06c648e9f364003e5fb7879d052 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Aug 2022 16:02:05 -0700 Subject: [PATCH 284/694] grpc-js: Propagate ejection when recreating outlier detection subchannel wrapper --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 0ef401c9a..ee400d45f 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -391,6 +391,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); const mapEntry = this.addressMap.get(subchannelAddressToString(subchannelAddress)); const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry); + if (mapEntry?.currentEjectionTimestamp !== null) { + // If the address is ejected, propagate that to the new subchannel wrapper + subchannelWrapper.eject(); + } mapEntry?.subchannelWrappers.push(subchannelWrapper); return subchannelWrapper; }, From edbdc570c7492b8be7eda2cb5f5afa0915a68f3b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 2 Aug 2022 11:02:02 -0700 Subject: [PATCH 285/694] grpc-js: Add outlier detection tracing and enable it in interop tests --- packages/grpc-js-xds/interop/Dockerfile | 2 +- .../src/load-balancer-outlier-detection.ts | 31 ++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index e8a0a98cb..b93e309d7 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -33,6 +33,6 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ ENV GRPC_VERBOSITY="DEBUG" -ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds +ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index dde618b73..e9793beab 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -18,7 +18,7 @@ import { ChannelOptions, connectivityState, StatusObject } from "."; import { Call } from "./call-stream"; import { ConnectivityState } from "./connectivity-state"; -import { Status } from "./constants"; +import { LogVerbosity, Status } from "./constants"; import { durationToMs, isDuration, msToDuration } from "./duration"; import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental"; import { BaseFilter, Filter, FilterFactory } from "./filter"; @@ -28,7 +28,13 @@ import { PickArgs, Picker, PickResult, PickResultType, QueuePicker, UnavailableP import { Subchannel } from "./subchannel"; import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address"; import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface"; +import * as logging from './logging'; +const TRACER_NAME = 'outlier_detection'; + +function trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} const TYPE_NAME = 'outlier_detection'; @@ -412,6 +418,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (!successRateConfig) { return; } + trace('Running success rate check'); // Step 1 const targetRequestVolume = successRateConfig.request_volume; let addresesWithTargetVolume = 0; @@ -424,6 +431,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { successRates.push(successes/(successes + failures)); } } + trace('Found ' + addresesWithTargetVolume + ' success rate candidates; currentEjectionPercent=' + this.getCurrentEjectionPercent() + ' successRates=[' + successRates + ']'); if (addresesWithTargetVolume < successRateConfig.minimum_hosts) { return; } @@ -438,9 +446,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const successRateVariance = successRateDeviationSum / successRates.length; const successRateStdev = Math.sqrt(successRateVariance); const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000); + trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold); // Step 3 - for (const mapEntry of this.addressMap.values()) { + for (const [address, mapEntry] of this.addressMap.entries()) { // Step 3.i if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { break; @@ -453,9 +462,12 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } // Step 3.iii const successRate = successes / (successes + failures); + trace('Checking candidate ' + address + ' successRate=' + successRate); if (successRate < ejectionThreshold) { const randomNumber = Math.random() * 100; + trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + successRateConfig.enforcement_percentage); if (randomNumber < successRateConfig.enforcement_percentage) { + trace('Ejecting candidate ' + address); this.eject(mapEntry, ejectionTimestamp); } } @@ -470,13 +482,14 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (!failurePercentageConfig) { return; } + trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume); // Step 1 if (this.addressMap.size < failurePercentageConfig.minimum_hosts) { return; } // Step 2 - for (const mapEntry of this.addressMap.values()) { + for (const [address, mapEntry] of this.addressMap.entries()) { // Step 2.i if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { break; @@ -484,6 +497,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { // Step 2.ii const successes = mapEntry.counter.getLastSuccesses(); const failures = mapEntry.counter.getLastFailures(); + trace('Candidate successes=' + successes + ' failures=' + failures); if (successes + failures < failurePercentageConfig.request_volume) { continue; } @@ -491,7 +505,9 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const failurePercentage = (failures * 100) / (failures + successes); if (failurePercentage > failurePercentageConfig.threshold) { const randomNumber = Math.random() * 100; + trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + failurePercentageConfig.enforcement_percentage); if (randomNumber < failurePercentageConfig.enforcement_percentage) { + trace('Ejecting candidate ' + address); this.eject(mapEntry, ejectionTimestamp); } } @@ -525,6 +541,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private runChecks() { const ejectionTimestamp = new Date(); + trace('Ejection timer running'); this.switchAllBuckets(); @@ -537,7 +554,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { this.runSuccessRateCheck(ejectionTimestamp); this.runFailurePercentageCheck(ejectionTimestamp); - for (const mapEntry of this.addressMap.values()) { + for (const [address, mapEntry] of this.addressMap.entries()) { if (mapEntry.currentEjectionTimestamp === null) { if (mapEntry.ejectionTimeMultiplier > 0) { mapEntry.ejectionTimeMultiplier -= 1; @@ -548,6 +565,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime()); returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs))); if (returnTime < new Date()) { + trace('Unejecting ' + address); this.uneject(mapEntry); } } @@ -564,6 +582,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } for (const address of subchannelAddresses) { if (!this.addressMap.has(address)) { + trace('Adding map entry for ' + address); this.addressMap.set(address, { counter: new CallCounter(), currentEjectionTimestamp: null, @@ -574,6 +593,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } for (const key of this.addressMap.keys()) { if (!subchannelAddresses.has(key)) { + trace('Removing map entry for ' + key); this.addressMap.delete(key); } } @@ -585,15 +605,18 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) { if (this.timerStartTime) { + trace('Previous timer existed. Replacing timer'); clearTimeout(this.ejectionTimer); const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime()); this.startTimer(remainingDelay); } else { + trace('Starting new timer'); this.timerStartTime = new Date(); this.startTimer(lbConfig.getIntervalMs()); this.switchAllBuckets(); } } else { + trace('Counting disabled. Cancelling timer.'); this.timerStartTime = null; clearTimeout(this.ejectionTimer); } From 9be6c6c5dab57d3f9f5408c5ee0f6d9ad2a99445 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 2 Aug 2022 13:48:16 -0700 Subject: [PATCH 286/694] Update outlier detection behavior for gRFC updates --- packages/grpc-js-xds/src/load-balancer-cds.ts | 5 ++--- packages/grpc-js/src/load-balancer-outlier-detection.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index a6dbf42f0..4d47b2546 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -79,9 +79,8 @@ function translateOutlierDetectionConfig(outlierDetection: OutlierDetection__Out return undefined; } if (!outlierDetection) { - /* No-op outlier detection config, with max possible interval and no - * ejection criteria configured. */ - return new OutlierDetectionLoadBalancingConfig(~(1<<31), null, null, null, null, null, []); + /* No-op outlier detection config, with all fields unset. */ + return new OutlierDetectionLoadBalancingConfig(null, null, null, null, null, null, []); } let successRateConfig: Partial | null = null; /* Success rate ejection is enabled by default, so we only disable it if diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index e9793beab..4219e293a 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -357,7 +357,10 @@ class OutlierDetectionPicker implements Picker { extraFilterFactories: extraFilterFactories }; } else { - return wrappedPick; + return { + ...wrappedPick, + subchannel: subchannelWrapper.getWrappedSubchannel() + } } } else { return wrappedPick; @@ -619,6 +622,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { trace('Counting disabled. Cancelling timer.'); this.timerStartTime = null; clearTimeout(this.ejectionTimer); + for (const mapEntry of this.addressMap.values()) { + this.uneject(mapEntry); + mapEntry.ejectionTimeMultiplier = 0; + } } this.latestConfig = lbConfig; From 3328798d28f544a752f4328fc670a46009a769ba Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Aug 2022 12:54:15 -0700 Subject: [PATCH 287/694] grpc-js: Implement getConnectivityState in subchannel wrapper --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 4219e293a..afbeb3459 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -215,6 +215,14 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements }); } + getConnectivityState(): connectivityState { + if (this.ejected) { + return ConnectivityState.TRANSIENT_FAILURE; + } else { + return this.childSubchannelState; + } + } + /** * Add a listener function to be called whenever the wrapper's * connectivity state changes. From 4cfe75b43a72ca98d7745417be86206d611426ac Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Aug 2022 15:56:28 -0700 Subject: [PATCH 288/694] grpc-js: Initialize connectivity state from subchannel in outlier detection subchannel wrapper --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index afbeb3459..0ef401c9a 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -199,12 +199,13 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig } class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { - private childSubchannelState: ConnectivityState = ConnectivityState.IDLE; + private childSubchannelState: ConnectivityState; private stateListeners: ConnectivityStateListener[] = []; private ejected: boolean = false; private refCount: number = 0; constructor(childSubchannel: SubchannelInterface, private mapEntry?: MapEntry) { super(childSubchannel); + this.childSubchannelState = childSubchannel.getConnectivityState(); childSubchannel.addConnectivityStateListener((subchannel, previousState, newState) => { this.childSubchannelState = newState; if (!this.ejected) { From 36f37cb78fb6fd987d9834d71a797ae8fabe2428 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Aug 2022 16:02:05 -0700 Subject: [PATCH 289/694] grpc-js: Propagate ejection when recreating outlier detection subchannel wrapper --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 0ef401c9a..ee400d45f 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -391,6 +391,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); const mapEntry = this.addressMap.get(subchannelAddressToString(subchannelAddress)); const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry); + if (mapEntry?.currentEjectionTimestamp !== null) { + // If the address is ejected, propagate that to the new subchannel wrapper + subchannelWrapper.eject(); + } mapEntry?.subchannelWrappers.push(subchannelWrapper); return subchannelWrapper; }, From d0dc6cd46e94b48bcf9520d587747a230dfcb2dd Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Aug 2022 11:36:10 -0700 Subject: [PATCH 290/694] grpc-js-xds: Enable the outlier detection interop test --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index ca300f0c2..ff915db59 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -159,7 +159,7 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test") + test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection") for test in "${test_suites[@]}"; do run_test $test || (( failed_tests++ )) done From e2960a87d29862b83cae8ec558c41847d7f33cb1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Aug 2022 11:36:10 -0700 Subject: [PATCH 291/694] grpc-js-xds: Enable the outlier detection interop test --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index ca300f0c2..ff915db59 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -159,7 +159,7 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test") + test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection") for test in "${test_suites[@]}"; do run_test $test || (( failed_tests++ )) done From ee1e330157067e479690106b2dffa1fcf904b0e5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Aug 2022 12:50:50 -0700 Subject: [PATCH 292/694] grpc-js: Avoid explicit bind in trailer event handler --- packages/grpc-js/src/call-stream.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index e8f312752..f265512f4 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -573,7 +573,9 @@ export class Http2CallStream implements Call { } } }); - stream.on('trailers', this.handleTrailers.bind(this)); + stream.on('trailers', (headers: http2.IncomingHttpHeaders) => { + this.handleTrailers(headers); + }); stream.on('data', (data: Buffer) => { this.trace('receive HTTP/2 data frame of length ' + data.length); const messages = this.decoder.write(data); From 31d28b5f147e1392bab22e042aa0282c16a18567 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Aug 2022 13:05:26 -0700 Subject: [PATCH 293/694] grpc-js: Handle errors when trying to ping --- packages/grpc-js/src/subchannel.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 5d1bf8973..87c0cd2cd 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -361,12 +361,21 @@ export class Subchannel { this.handleDisconnect(); }, this.keepaliveTimeoutMs); this.keepaliveTimeoutId.unref?.(); - this.session!.ping( - (err: Error | null, duration: number, payload: Buffer) => { - this.keepaliveTrace('Received ping response'); - clearTimeout(this.keepaliveTimeoutId); - } - ); + try { + this.session!.ping( + (err: Error | null, duration: number, payload: Buffer) => { + this.keepaliveTrace('Received ping response'); + clearTimeout(this.keepaliveTimeoutId); + } + ); + } catch (e) { + /* If we fail to send a ping, the connection is no longer functional, so + * we should discard it. */ + this.transitionToState( + [ConnectivityState.READY], + ConnectivityState.TRANSIENT_FAILURE + ); + } } private startKeepalivePings() { From c1ab4c4a1b69a79fb7b69eff68a0bcfc517b28c9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Aug 2022 13:44:02 -0700 Subject: [PATCH 294/694] grpc-js: Update version to 1.6.9 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 3ceaefebe..b10ad4a47 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.8", + "version": "1.6.9", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From e87b8640757ce583ab441a0807617eeafc3df6a2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Aug 2022 17:18:02 -0700 Subject: [PATCH 295/694] grpc-js: Update version to 1.6.9 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 3ceaefebe..b10ad4a47 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.8", + "version": "1.6.9", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 9e3935ec839009a5b64af935bfd503a634753367 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 9 Aug 2022 12:26:33 +0200 Subject: [PATCH 296/694] fix: update `long` to v5 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 07e66c9fe..6aef44a99 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -47,7 +47,7 @@ "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", + "long": "^5.0.0", "protobufjs": "^7.0.0", "yargs": "^16.2.0" }, From d23d7bdd09d9adce9b4704e50e38aacd48f7d334 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 9 Aug 2022 12:32:57 +0200 Subject: [PATCH 297/694] remove types `long` ships with types now --- packages/proto-loader/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 6aef44a99..60af4242f 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -45,7 +45,6 @@ "proto-loader-gen-types": "./build/bin/proto-loader-gen-types.js" }, "dependencies": { - "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.0.0", From 3f4418faf01cd029512cb3cbf48319d3b4f367ca Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 11 Aug 2022 18:01:01 -0700 Subject: [PATCH 298/694] grpc-js: Drain incoming http2 data after outputting status --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/call-stream.ts | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index b10ad4a47..72b4b10ed 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.9", + "version": "1.6.10", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index f265512f4..c405405e8 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -329,6 +329,11 @@ export class Http2CallStream implements Call { process.nextTick(() => { this.listener?.onReceiveStatus(filteredStatus); }); + /* Leave the http2 stream in flowing state to drain incoming messages, to + * ensure that the stream closure completes. The call stream already does + * not push more messages after the status is output, so the messages go + * nowhere either way. */ + this.http2Stream?.resume(); if (this.subchannel) { this.subchannel.callUnref(); this.subchannel.removeDisconnectListener(this.disconnectListener); @@ -577,6 +582,11 @@ export class Http2CallStream implements Call { this.handleTrailers(headers); }); stream.on('data', (data: Buffer) => { + /* If the status has already been output, allow the http2 stream to + * drain without processing the data. */ + if (this.statusOutput) { + return; + } this.trace('receive HTTP/2 data frame of length ' + data.length); const messages = this.decoder.write(data); @@ -688,9 +698,6 @@ export class Http2CallStream implements Call { } this.streamEndWatchers.forEach(watcher => watcher(false)); }); - if (!this.pendingRead) { - stream.pause(); - } if (this.pendingWrite) { if (!this.pendingWriteCallback) { throw new Error('Invalid state in write handling code'); From 9ba4ed36217a4a45c39138301e1bfffedfa9e888 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 15 Aug 2022 09:36:35 -0700 Subject: [PATCH 299/694] grpc-js-xds: Fix outlier detection interop test name --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index ff915db59..0e45cd86f 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -159,7 +159,7 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection") + test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection_test") for test in "${test_suites[@]}"; do run_test $test || (( failed_tests++ )) done From ff16cdf02104295b08f1cbc124b0cba89a6336cc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 15 Aug 2022 09:36:35 -0700 Subject: [PATCH 300/694] grpc-js-xds: Fix outlier detection interop test name --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index ff915db59..0e45cd86f 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -159,7 +159,7 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection") + test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection_test") for test in "${test_suites[@]}"; do run_test $test || (( failed_tests++ )) done From 9d0eb60d1948255fbe0af6ad4a2585734e5db366 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 17 Aug 2022 09:30:53 -0700 Subject: [PATCH 301/694] proto-loader: Update dependencies to fix compilation error --- packages/proto-loader/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 07e66c9fe..9c813337e 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.0", + "version": "0.7.1", "author": "Google Inc.", "contributors": [ { @@ -58,9 +58,9 @@ "@types/node": "^10.17.26", "@types/yargs": "^16.0.4", "clang-format": "^1.2.2", - "gts": "^1.1.0", + "gts": "^3.1.0", "rimraf": "^3.0.2", - "typescript": "~3.8.3" + "typescript": "~4.7.4" }, "engines": { "node": ">=6" From d0e7f356db02656ce1ba06b1f26d0a217348c481 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 17 Aug 2022 12:44:48 -0700 Subject: [PATCH 302/694] proto-loader: Undo upgrade of 'long' dependency --- packages/proto-loader/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 09f815c86..53b269d1b 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.1", + "version": "0.7.2", "author": "Google Inc.", "contributors": [ { @@ -45,8 +45,9 @@ "proto-loader-gen-types": "./build/bin/proto-loader-gen-types.js" }, "dependencies": { + "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", + "long": "^4.0.0", "protobufjs": "^7.0.0", "yargs": "^16.2.0" }, From 7ca0cc006922588190597278288b65007f30ad7d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 17 Aug 2022 16:32:20 -0700 Subject: [PATCH 303/694] grpc-js-xds: Implement ignore_resource_deletion option --- packages/grpc-js-xds/src/xds-client.ts | 4 +++ .../src/xds-stream-state/xds-stream-state.ts | 36 +++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index d2b3f1823..0c8126cbe 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -360,6 +360,10 @@ export class XdsClient { } else { this.apiVersion = XdsApiVersion.V2; } + if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('ignore_resource_deletion') >= 0) { + this.adsState.lds.enableIgnoreResourceDeletion(); + this.adsState.cds.enableIgnoreResourceDeletion(); + } const nodeV2: NodeV2 = { ...bootstrapInfo.node, build_version: `gRPC Node Pure JS ${clientVersion}`, diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 0b806f843..88d36c77d 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -75,6 +75,7 @@ interface SubscriptionEntry { watchers: Watcher[]; cachedResponse: ResponseType | null; resourceTimer: NodeJS.Timer; + deletionIgnored: boolean; } const RESOURCE_TIMEOUT_MS = 15_000; @@ -86,11 +87,12 @@ export abstract class BaseXdsStreamState implements XdsStreamState private subscriptions: Map> = new Map>(); private latestIsV2 = false; private isAdsStreamRunning = false; + private ignoreResourceDeletion = false; constructor(private updateResourceNames: () => void) {} - protected trace(text: string) { - experimental.trace(logVerbosity.DEBUG, TRACER_NAME, this.getProtocolName() + ' | ' + text); + protected trace(text: string, verbosity = logVerbosity.DEBUG) { + experimental.trace(verbosity, TRACER_NAME, this.getProtocolName() + ' | ' + text); } private startResourceTimer(subscriptionEntry: SubscriptionEntry) { @@ -111,7 +113,8 @@ export abstract class BaseXdsStreamState implements XdsStreamState subscriptionEntry = { watchers: [], cachedResponse: null, - resourceTimer: setTimeout(() => {}, 0) + resourceTimer: setTimeout(() => {}, 0), + deletionIgnored: false }; if (this.isAdsStreamRunning) { this.startResourceTimer(subscriptionEntry); @@ -142,6 +145,9 @@ export abstract class BaseXdsStreamState implements XdsStreamState } if (subscriptionEntry.watchers.length === 0) { clearTimeout(subscriptionEntry.resourceTimer); + if (subscriptionEntry.deletionIgnored) { + this.trace('Unsubscribing from resource with previously ignored deletion: ' + resourceName, logVerbosity.INFO); + } this.subscriptions.delete(resourceName); this.updateResourceNames(); } @@ -187,6 +193,10 @@ export abstract class BaseXdsStreamState implements XdsStreamState } clearTimeout(subscriptionEntry.resourceTimer); subscriptionEntry.cachedResponse = resource; + if (subscriptionEntry.deletionIgnored) { + this.trace('Received resource with previously ignored deletion: ' + resourceName, logVerbosity.INFO); + subscriptionEntry.deletionIgnored = false; + } } } result.missing = this.handleMissingNames(allResourceNames); @@ -218,10 +228,18 @@ export abstract class BaseXdsStreamState implements XdsStreamState const missingNames: string[] = []; for (const [resourceName, subscriptionEntry] of this.subscriptions.entries()) { if (!allResponseNames.has(resourceName) && subscriptionEntry.cachedResponse !== null) { - this.trace('Reporting resource does not exist named ' + resourceName); - missingNames.push(resourceName); - for (const watcher of subscriptionEntry.watchers) { - watcher.onResourceDoesNotExist(); + if (this.ignoreResourceDeletion) { + if (!subscriptionEntry.deletionIgnored) { + this.trace('Ignoring nonexistent resource ' + resourceName, logVerbosity.ERROR); + subscriptionEntry.deletionIgnored = true; + } + } else { + this.trace('Reporting resource does not exist named ' + resourceName); + missingNames.push(resourceName); + for (const watcher of subscriptionEntry.watchers) { + watcher.onResourceDoesNotExist(); + } + subscriptionEntry.cachedResponse = null; } } } @@ -231,6 +249,10 @@ export abstract class BaseXdsStreamState implements XdsStreamState } } + enableIgnoreResourceDeletion() { + this.ignoreResourceDeletion = true; + } + /** * Apply the validation rules for this resource type to this resource * instance. From a3b698e837cfa6cc5be0c989b2d0f2db7694af65 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 17 Aug 2022 17:00:02 -0700 Subject: [PATCH 304/694] Don't use tracer for ignored resource deletion logs --- .../src/xds-stream-state/xds-stream-state.ts | 10 +++++----- packages/grpc-js/src/experimental.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 88d36c77d..7b3bc0189 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -91,8 +91,8 @@ export abstract class BaseXdsStreamState implements XdsStreamState constructor(private updateResourceNames: () => void) {} - protected trace(text: string, verbosity = logVerbosity.DEBUG) { - experimental.trace(verbosity, TRACER_NAME, this.getProtocolName() + ' | ' + text); + protected trace(text: string) { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, this.getProtocolName() + ' | ' + text); } private startResourceTimer(subscriptionEntry: SubscriptionEntry) { @@ -146,7 +146,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState if (subscriptionEntry.watchers.length === 0) { clearTimeout(subscriptionEntry.resourceTimer); if (subscriptionEntry.deletionIgnored) { - this.trace('Unsubscribing from resource with previously ignored deletion: ' + resourceName, logVerbosity.INFO); + experimental.log(logVerbosity.INFO, 'Unsubscribing from resource with previously ignored deletion: ' + resourceName); } this.subscriptions.delete(resourceName); this.updateResourceNames(); @@ -194,7 +194,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState clearTimeout(subscriptionEntry.resourceTimer); subscriptionEntry.cachedResponse = resource; if (subscriptionEntry.deletionIgnored) { - this.trace('Received resource with previously ignored deletion: ' + resourceName, logVerbosity.INFO); + experimental.log(logVerbosity.INFO, 'Received resource with previously ignored deletion: ' + resourceName); subscriptionEntry.deletionIgnored = false; } } @@ -230,7 +230,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState if (!allResponseNames.has(resourceName) && subscriptionEntry.cachedResponse !== null) { if (this.ignoreResourceDeletion) { if (!subscriptionEntry.deletionIgnored) { - this.trace('Ignoring nonexistent resource ' + resourceName, logVerbosity.ERROR); + experimental.log(logVerbosity.ERROR, 'Ignoring nonexistent resource ' + resourceName); subscriptionEntry.deletionIgnored = true; } } else { diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index fcafbeb01..d58495f46 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -1,4 +1,4 @@ -export { trace } from './logging'; +export { trace, log } from './logging'; export { Resolver, ResolverListener, From 5a7f89a5f5c768807f1c879069a2901af7a6b478 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 18 Aug 2022 14:25:49 -0700 Subject: [PATCH 305/694] grpc-js: Switch LB policy when new one is not CONNECTING --- packages/grpc-js/src/load-balancer-child-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 0591c92fd..64b341810 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -48,7 +48,7 @@ export class ChildLoadBalancerHandler implements LoadBalancer { } updateState(connectivityState: ConnectivityState, picker: Picker): void { if (this.calledByPendingChild()) { - if (connectivityState !== ConnectivityState.READY) { + if (connectivityState === ConnectivityState.CONNECTING) { return; } this.parent.currentChild?.destroy(); From 3e6730cd24bebd6780daa8b69161568881ab622b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 18 Aug 2022 14:55:58 -0700 Subject: [PATCH 306/694] grpc-js-xds: delay picker updates while updating children in weighted target and xds_cluster_manager --- .../grpc-js-xds/src/load-balancer-weighted-target.ts | 11 ++++++++++- .../src/load-balancer-xds-cluster-manager.ts | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts index a7a28c663..5d7cfa04e 100644 --- a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -181,7 +181,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { trace('Target ' + this.name + ' ' + ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[connectivityState]); this.connectivityState = connectivityState; this.picker = picker; - this.parent.updateState(); + this.parent.maybeUpdateState(); } updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void { @@ -238,9 +238,16 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { * List of current target names. */ private targetList: string[] = []; + private updatesPaused = false; constructor(private channelControlHelper: ChannelControlHelper) {} + private maybeUpdateState() { + if (!this.updatesPaused) { + this.updateState() + } + } + private updateState() { const pickerList: WeightedPicker[] = []; let end = 0; @@ -343,6 +350,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { childAddressList.push(childAddress); } + this.updatesPaused = true; this.targetList = Array.from(lbConfig.getTargets().keys()); for (const [targetName, targetConfig] of lbConfig.getTargets()) { let target = this.targets.get(targetName); @@ -364,6 +372,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { target.deactivate(); } } + this.updatesPaused = false; this.updateState(); } diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index 6210ea84a..bfc55c809 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -144,7 +144,7 @@ class XdsClusterManager implements LoadBalancer { trace('Child ' + this.name + ' ' + ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[connectivityState]); this.connectivityState = connectivityState; this.picker = picker; - this.parent.updateState(); + this.parent.maybeUpdateState(); } updateAddressList(addressList: SubchannelAddress[], lbConfig: ClusterManagerChild, attributes: { [key: string]: unknown; }): void { const childConfig = getFirstUsableConfig(lbConfig.child_policy); @@ -173,8 +173,15 @@ class XdsClusterManager implements LoadBalancer { private children: Map = new Map(); // Shutdown is a placeholder value that will never appear in normal operation. private currentState: ConnectivityState = ConnectivityState.SHUTDOWN; + private updatesPaused = false; constructor(private channelControlHelper: ChannelControlHelper) {} + private maybeUpdateState() { + if (!this.updatesPaused) { + this.updateState(); + } + } + private updateState() { const pickerMap: Map = new Map(); let anyReady = false; @@ -250,6 +257,7 @@ class XdsClusterManager implements LoadBalancer { namesToRemove.push(name); } } + this.updatesPaused = true; for (const name of namesToRemove) { this.children.get(name)!.destroy(); this.children.delete(name); @@ -262,6 +270,7 @@ class XdsClusterManager implements LoadBalancer { this.children.set(name, newChild); } } + this.updatesPaused = false; this.updateState(); } exitIdle(): void { From e1b2cad25ea15f1ae848069a5b80c5ae2b2b5ef3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 22 Aug 2022 14:28:34 -0700 Subject: [PATCH 307/694] grpc-js-xds: Make various fixes to the priority LB policy --- .../grpc-js-xds/src/load-balancer-priority.ts | 120 ++++++++---------- 1 file changed, 56 insertions(+), 64 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index b05e459a7..9f4f68e1f 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -115,7 +115,6 @@ interface PriorityChildBalancer { resetBackoff(): void; deactivate(): void; maybeReactivate(): void; - cancelFailoverTimer(): void; isFailoverTimerPending(): boolean; getConnectivityState(): ConnectivityState; getPicker(): Picker; @@ -138,6 +137,7 @@ export class PriorityLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; private failoverTimer: NodeJS.Timer | null = null; private deactivationTimer: NodeJS.Timer | null = null; + private seenReadyOrIdleSinceTransientFailure = false; constructor(private parent: PriorityLoadBalancer, private name: string) { this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { updateState: (connectivityState: ConnectivityState, picker: Picker) => { @@ -145,12 +145,24 @@ export class PriorityLoadBalancer implements LoadBalancer { }, })); this.picker = new QueuePicker(this.childBalancer); + this.startFailoverTimer(); } private updateState(connectivityState: ConnectivityState, picker: Picker) { trace('Child ' + this.name + ' ' + ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[connectivityState]); this.connectivityState = connectivityState; this.picker = picker; + if (connectivityState === ConnectivityState.CONNECTING) { + if (this.seenReadyOrIdleSinceTransientFailure && this.failoverTimer === null) { + this.startFailoverTimer(); + } + } else if (connectivityState === ConnectivityState.READY || connectivityState === ConnectivityState.IDLE) { + this.seenReadyOrIdleSinceTransientFailure = true; + this.cancelFailoverTimer(); + } else if (connectivityState === ConnectivityState.TRANSIENT_FAILURE) { + this.seenReadyOrIdleSinceTransientFailure = false; + this.cancelFailoverTimer(); + } this.parent.onChildStateChange(this); } @@ -174,13 +186,9 @@ export class PriorityLoadBalancer implements LoadBalancer { attributes: { [key: string]: unknown } ): void { this.childBalancer.updateAddressList(addressList, lbConfig, attributes); - this.startFailoverTimer(); } exitIdle() { - if (this.connectivityState === ConnectivityState.IDLE) { - this.startFailoverTimer(); - } this.childBalancer.exitIdle(); } @@ -204,7 +212,7 @@ export class PriorityLoadBalancer implements LoadBalancer { } } - cancelFailoverTimer() { + private cancelFailoverTimer() { if (this.failoverTimer !== null) { clearTimeout(this.failoverTimer); this.failoverTimer = null; @@ -267,6 +275,8 @@ export class PriorityLoadBalancer implements LoadBalancer { */ private currentChildFromBeforeUpdate: PriorityChildBalancer | null = null; + private updatesPaused = false; + constructor(private channelControlHelper: ChannelControlHelper) {} private updateState(state: ConnectivityState, picker: Picker) { @@ -286,6 +296,9 @@ export class PriorityLoadBalancer implements LoadBalancer { private onChildStateChange(child: PriorityChildBalancer) { const childState = child.getConnectivityState(); trace('Child ' + child.getName() + ' transitioning to ' + ConnectivityState[childState]); + if (this.updatesPaused) { + return; + } if (child === this.currentChildFromBeforeUpdate) { if ( childState === ConnectivityState.READY || @@ -294,40 +307,11 @@ export class PriorityLoadBalancer implements LoadBalancer { this.updateState(childState, child.getPicker()); } else { this.currentChildFromBeforeUpdate = null; - this.tryNextPriority(true); + this.choosePriority(); } return; } - const childPriority = this.priorities.indexOf(child.getName()); - if (childPriority < 0) { - // child is not in the priority list, ignore updates - return; - } - if (this.currentPriority !== null && childPriority > this.currentPriority) { - // child is lower priority than the currently selected child, ignore updates - return; - } - if (childState === ConnectivityState.TRANSIENT_FAILURE) { - /* Report connecting if and only if the currently selected child is the - * one entering TRANSIENT_FAILURE */ - this.tryNextPriority(childPriority === this.currentPriority); - return; - } - if (this.currentPriority === null || childPriority < this.currentPriority) { - /* In this case, either there is no currently selected child or this - * child is higher priority than the currently selected child, so we want - * to switch to it if it is READY or IDLE. */ - if ( - childState === ConnectivityState.READY || - childState === ConnectivityState.IDLE - ) { - this.selectPriority(childPriority); - } - return; - } - /* The currently selected child has updated state to something other than - * TRANSIENT_FAILURE, so we pass that update along */ - this.updateState(childState, child.getPicker()); + this.choosePriority(); } private deleteChild(child: PriorityChildBalancer) { @@ -335,7 +319,7 @@ export class PriorityLoadBalancer implements LoadBalancer { this.currentChildFromBeforeUpdate = null; /* If we get to this point, the currentChildFromBeforeUpdate was still in * use, so we are still trying to connect to the specified priorities */ - this.tryNextPriority(true); + this.choosePriority(); } } @@ -348,7 +332,6 @@ export class PriorityLoadBalancer implements LoadBalancer { private selectPriority(priority: number) { this.currentPriority = priority; const chosenChild = this.children.get(this.priorities[priority])!; - chosenChild.cancelFailoverTimer(); this.updateState( chosenChild.getConnectivityState(), chosenChild.getPicker() @@ -360,20 +343,22 @@ export class PriorityLoadBalancer implements LoadBalancer { } } - /** - * Check each child in priority order until we find one to use - * @param reportConnecting Whether we should report a CONNECTING state if we - * stop before picking a specific child. This should be true when we have - * not already selected a child. - */ - private tryNextPriority(reportConnecting: boolean) { - for (const [index, childName] of this.priorities.entries()) { + private choosePriority() { + if (this.priorities.length === 0) { + this.updateState(ConnectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: Status.UNAVAILABLE, details: 'priority policy has empty priority list', metadata: new Metadata()})); + return; + } + + this.currentPriority = null; + for (const [priority, childName] of this.priorities.entries()) { + trace('Trying priority ' + priority + ' child ' + childName); let child = this.children.get(childName); /* If the child doesn't already exist, create it and update it. */ if (child === undefined) { - if (reportConnecting) { + if (this.currentChildFromBeforeUpdate === null) { this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); } + this.currentPriority = priority; child = new this.PriorityChildImpl(this, childName); this.children.set(childName, child); const childUpdate = this.latestUpdates.get(childName); @@ -383,6 +368,7 @@ export class PriorityLoadBalancer implements LoadBalancer { childUpdate.lbConfig, this.latestAttributes ); + return; } } /* We're going to try to use this child, so reactivate it if it has been @@ -392,28 +378,33 @@ export class PriorityLoadBalancer implements LoadBalancer { child.getConnectivityState() === ConnectivityState.READY || child.getConnectivityState() === ConnectivityState.IDLE ) { - this.selectPriority(index); + this.selectPriority(priority); return; } if (child.isFailoverTimerPending()) { - /* This child is still trying to connect. Wait until its failover timer - * has ended to continue to the next one */ - if (reportConnecting) { + this.currentPriority = priority; + if (this.currentChildFromBeforeUpdate === null) { + /* This child is still trying to connect. Wait until its failover timer + * has ended to continue to the next one */ this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); + return; } + } + } + + /* If we didn't find any priority to try, pick the first one in the state + * CONNECTING */ + for (const [priority, childName] of this.priorities.entries()) { + let child = this.children.get(childName)!; + if (child.getConnectivityState() === ConnectivityState.CONNECTING) { + this.updateState(child.getConnectivityState(), child.getPicker()); return; } } - this.currentPriority = null; - this.currentChildFromBeforeUpdate = null; - this.updateState( - ConnectivityState.TRANSIENT_FAILURE, - new UnavailablePicker({ - code: Status.UNAVAILABLE, - details: 'No ready priority', - metadata: new Metadata(), - }) - ); + + // Did not find any child in CONNECTING, delegate to last child + const child = this.children.get(this.priorities[this.priorities.length - 1])!; + this.updateState(child.getConnectivityState(), child.getPicker()); } updateAddressList( @@ -464,6 +455,7 @@ export class PriorityLoadBalancer implements LoadBalancer { this.latestAttributes = attributes; this.latestUpdates.clear(); this.priorities = lbConfig.getPriorities(); + this.updatesPaused = true; /* Pair up the new child configs with the corresponding address lists, and * update all existing children with their new configs */ for (const [childName, childConfig] of lbConfig.getChildren()) { @@ -492,8 +484,8 @@ export class PriorityLoadBalancer implements LoadBalancer { child.deactivate(); } } - // Only report connecting if there are no existing children - this.tryNextPriority(this.children.size === 0); + this.updatesPaused = false; + this.choosePriority(); } exitIdle(): void { if (this.currentPriority !== null) { From 460fa93b9c216687ed2e6ab640f9ce49241e0f0f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 23 Aug 2022 13:38:56 -0700 Subject: [PATCH 308/694] grpc-js-xds: priority: remove currentChildFromBeforeUpdate --- .../grpc-js-xds/src/load-balancer-priority.ts | 76 +++++-------------- 1 file changed, 17 insertions(+), 59 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index 9f4f68e1f..12037a777 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -266,14 +266,6 @@ export class PriorityLoadBalancer implements LoadBalancer { * Current chosen priority that requests are sent to */ private currentPriority: number | null = null; - /** - * After an update, this preserves the currently selected child from before - * the update. We continue to use that child until it disconnects, or - * another higher-priority child connects, or it is deleted because it is not - * in the new priority list at all and its retention interval has expired, or - * we try and fail to connect to every child in the new priority list. - */ - private currentChildFromBeforeUpdate: PriorityChildBalancer | null = null; private updatesPaused = false; @@ -299,28 +291,11 @@ export class PriorityLoadBalancer implements LoadBalancer { if (this.updatesPaused) { return; } - if (child === this.currentChildFromBeforeUpdate) { - if ( - childState === ConnectivityState.READY || - childState === ConnectivityState.IDLE - ) { - this.updateState(childState, child.getPicker()); - } else { - this.currentChildFromBeforeUpdate = null; - this.choosePriority(); - } - return; - } this.choosePriority(); } private deleteChild(child: PriorityChildBalancer) { - if (child === this.currentChildFromBeforeUpdate) { - this.currentChildFromBeforeUpdate = null; - /* If we get to this point, the currentChildFromBeforeUpdate was still in - * use, so we are still trying to connect to the specified priorities */ - this.choosePriority(); - } + this.children.delete(child.getName()); } /** @@ -329,17 +304,17 @@ export class PriorityLoadBalancer implements LoadBalancer { * child connects. * @param priority */ - private selectPriority(priority: number) { + private selectPriority(priority: number, deactivateLowerPriorities: boolean) { this.currentPriority = priority; const chosenChild = this.children.get(this.priorities[priority])!; this.updateState( chosenChild.getConnectivityState(), chosenChild.getPicker() ); - this.currentChildFromBeforeUpdate = null; - // Deactivate each child of lower priority than the chosen child - for (let i = priority + 1; i < this.priorities.length; i++) { - this.children.get(this.priorities[i])?.deactivate(); + if (deactivateLowerPriorities) { + for (let i = priority + 1; i < this.priorities.length; i++) { + this.children.get(this.priorities[i])?.deactivate(); + } } } @@ -349,16 +324,11 @@ export class PriorityLoadBalancer implements LoadBalancer { return; } - this.currentPriority = null; for (const [priority, childName] of this.priorities.entries()) { trace('Trying priority ' + priority + ' child ' + childName); let child = this.children.get(childName); /* If the child doesn't already exist, create it and update it. */ if (child === undefined) { - if (this.currentChildFromBeforeUpdate === null) { - this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); - } - this.currentPriority = priority; child = new this.PriorityChildImpl(this, childName); this.children.set(childName, child); const childUpdate = this.latestUpdates.get(childName); @@ -368,27 +338,24 @@ export class PriorityLoadBalancer implements LoadBalancer { childUpdate.lbConfig, this.latestAttributes ); - return; } + } else { + /* We're going to try to use this child, so reactivate it if it has been + * deactivated */ + child.maybeReactivate(); } - /* We're going to try to use this child, so reactivate it if it has been - * deactivated */ - child.maybeReactivate(); if ( child.getConnectivityState() === ConnectivityState.READY || child.getConnectivityState() === ConnectivityState.IDLE ) { - this.selectPriority(priority); + this.selectPriority(priority, true); return; } if (child.isFailoverTimerPending()) { - this.currentPriority = priority; - if (this.currentChildFromBeforeUpdate === null) { - /* This child is still trying to connect. Wait until its failover timer - * has ended to continue to the next one */ - this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); - return; - } + this.selectPriority(priority, false); + /* This child is still trying to connect. Wait until its failover timer + * has ended to continue to the next one */ + return; } } @@ -397,14 +364,13 @@ export class PriorityLoadBalancer implements LoadBalancer { for (const [priority, childName] of this.priorities.entries()) { let child = this.children.get(childName)!; if (child.getConnectivityState() === ConnectivityState.CONNECTING) { - this.updateState(child.getConnectivityState(), child.getPicker()); + this.selectPriority(priority, false); return; } } // Did not find any child in CONNECTING, delegate to last child - const child = this.children.get(this.priorities[this.priorities.length - 1])!; - this.updateState(child.getConnectivityState(), child.getPicker()); + this.selectPriority(this.priorities.length - 1, false); } updateAddressList( @@ -446,12 +412,6 @@ export class PriorityLoadBalancer implements LoadBalancer { } childAddressList.push(childAddress); } - if (this.currentPriority !== null) { - this.currentChildFromBeforeUpdate = this.children.get( - this.priorities[this.currentPriority] - )!; - this.currentPriority = null; - } this.latestAttributes = attributes; this.latestUpdates.clear(); this.priorities = lbConfig.getPriorities(); @@ -502,8 +462,6 @@ export class PriorityLoadBalancer implements LoadBalancer { child.destroy(); } this.children.clear(); - this.currentChildFromBeforeUpdate?.destroy(); - this.currentChildFromBeforeUpdate = null; } getTypeName(): string { return TYPE_NAME; From f15efb63deaf8a84cee358b69434319628e8175a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 24 Aug 2022 10:27:53 -0700 Subject: [PATCH 309/694] grpc-js: Outlier Detection: fix failure percentage min hosts check --- .../grpc-js/src/load-balancer-outlier-detection.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index ee400d45f..dccca7c04 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -500,7 +500,15 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume); // Step 1 - if (this.addressMap.size < failurePercentageConfig.minimum_hosts) { + let addresesWithTargetVolume = 0; + for (const mapEntry of this.addressMap.values()) { + const successes = mapEntry.counter.getLastSuccesses(); + const failures = mapEntry.counter.getLastFailures(); + if (successes + failures >= failurePercentageConfig.request_volume) { + addresesWithTargetVolume += 1; + } + } + if (addresesWithTargetVolume < failurePercentageConfig.minimum_hosts) { return; } From 8664c837db2f03e17b5bfd70a9d352a9a318448a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 24 Aug 2022 10:59:15 -0700 Subject: [PATCH 310/694] Fix spelling --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index dccca7c04..a799e5b09 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -500,15 +500,15 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume); // Step 1 - let addresesWithTargetVolume = 0; + let addressesWithTargetVolume = 0; for (const mapEntry of this.addressMap.values()) { const successes = mapEntry.counter.getLastSuccesses(); const failures = mapEntry.counter.getLastFailures(); if (successes + failures >= failurePercentageConfig.request_volume) { - addresesWithTargetVolume += 1; + addressesWithTargetVolume += 1; } } - if (addresesWithTargetVolume < failurePercentageConfig.minimum_hosts) { + if (addressesWithTargetVolume < failurePercentageConfig.minimum_hosts) { return; } From 7a138a696509494dcf9912569064f48b4421ee7b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 17 Aug 2022 09:30:53 -0700 Subject: [PATCH 311/694] proto-loader: Update dependencies to fix compilation error --- packages/proto-loader/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 9b9431dc1..989e34eac 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -58,9 +58,9 @@ "@types/node": "^10.17.26", "@types/yargs": "^16.0.4", "clang-format": "^1.2.2", - "gts": "^1.1.0", + "gts": "^3.1.0", "rimraf": "^3.0.2", - "typescript": "~3.8.3" + "typescript": "~4.7.4" }, "engines": { "node": ">=6" From 1d5801aa90784746809d3f7e10c9693d919bdc3f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 24 Aug 2022 14:54:09 -0700 Subject: [PATCH 312/694] grpc-js: Stop ejecting when current percent is equal to max --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index a799e5b09..bf7a72f87 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -467,7 +467,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { // Step 3 for (const [address, mapEntry] of this.addressMap.entries()) { // Step 3.i - if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { + if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) { break; } // Step 3.ii @@ -515,7 +515,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { // Step 2 for (const [address, mapEntry] of this.addressMap.entries()) { // Step 2.i - if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) { + if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) { break; } // Step 2.ii From 2c6fd779d8151580492e8e482add5a53bcfce42a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 25 Aug 2022 14:22:00 -0700 Subject: [PATCH 313/694] grpc-js-xds: Use authority override to select VirtualHost when provided --- packages/grpc-js-xds/src/resolver-xds.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index f6e2ad402..8c447931d 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -319,9 +319,12 @@ class XdsResolver implements Resolver { private handleRouteConfig(routeConfig: RouteConfiguration__Output, isV2: boolean) { this.latestRouteConfig = routeConfig; this.latestRouteConfigIsV2 = isV2; - const virtualHost = findVirtualHostForDomain(routeConfig.virtual_hosts, this.target.path); + /* Select the virtual host using the default authority override if it + * exists, and the channel target otherwise. */ + const hostDomain = this.channelOptions['grpc.default_authority'] ?? this.target.path; + const virtualHost = findVirtualHostForDomain(routeConfig.virtual_hosts, hostDomain); if (virtualHost === null) { - this.reportResolutionError('No matching route found'); + this.reportResolutionError('No matching route found for ' + hostDomain); return; } const virtualHostHttpFilterOverrides = new Map(); From 594933aa2b9c0b0d59635383c6e8f7b7829eb7de Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 26 Aug 2022 17:49:48 -0700 Subject: [PATCH 314/694] xDS interop: enable pod log collection in the buildscripts - Enables pod log collection in all PSM interop jobs implemented in https://github.com/grpc/grpc/pull/30594. - Associate test suite runs with their own log file, so it's displayed on the "Target Log" tab - Adds missing `--force_cleanup` to the lb test (reduces leaked resources) - Fix run_test not returning correct exit status, causing false positives in some cases. See https://github.com/grpc/grpc/pull/30768 --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 8 +++++++- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 10 +++++++--- test/kokoro/xds_k8s_lb.cfg | 2 +- test/kokoro/xds_k8s_url_map.cfg | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 0e45cd86f..ca747f7ea 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -98,6 +98,8 @@ run_test() { # Test driver usage: # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage local test_name="${1:?Usage: run_test test_name}" + local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}" + mkdir -pv "${out_dir}" # testing_version is used by the framework to determine the supported PSM # features. It's captured from Kokoro job name of the Node repo, which takes # the form: @@ -109,7 +111,11 @@ run_test() { --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ --server_image="${SERVER_IMAGE_NAME}" \ --testing_version="${TESTING_VERSION}" \ - --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" + --force_cleanup \ + --collect_app_logs \ + --log_dir="${out_dir}" \ + --xml_output_file="${out_dir}/sponge_log.xml" \ + |& tee "${out_dir}/sponge_log.log" } ####################################### diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index c29d0c0a5..69126c72b 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -98,15 +98,19 @@ run_test() { # Test driver usage: # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage local test_name="${1:?Usage: run_test test_name}" + local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}" + mkdir -pv "${out_dir}" set -x python3 -m "tests.${test_name}" \ --flagfile="${TEST_DRIVER_FLAGFILE}" \ + --flagfile="config/url-map.cfg" \ --kube_context="${KUBE_CONTEXT}" \ --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ --testing_version="${TESTING_VERSION}" \ - --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \ - --flagfile="config/url-map.cfg" - set +x + --collect_app_logs \ + --log_dir="${out_dir}" \ + --xml_output_file="${out_dir}/sponge_log.xml" \ + |& tee "${out_dir}/sponge_log.log" } ####################################### diff --git a/test/kokoro/xds_k8s_lb.cfg b/test/kokoro/xds_k8s_lb.cfg index b9940cfdc..09aa3d17d 100644 --- a/test/kokoro/xds_k8s_lb.cfg +++ b/test/kokoro/xds_k8s_lb.cfg @@ -20,7 +20,7 @@ timeout_mins: 180 action { define_artifacts { regex: "artifacts/**/*sponge_log.xml" - regex: "artifacts/**/*sponge_log.log" + regex: "artifacts/**/*.log" strip_prefix: "artifacts" } } diff --git a/test/kokoro/xds_k8s_url_map.cfg b/test/kokoro/xds_k8s_url_map.cfg index dd4cce76d..50d523b66 100644 --- a/test/kokoro/xds_k8s_url_map.cfg +++ b/test/kokoro/xds_k8s_url_map.cfg @@ -20,7 +20,7 @@ timeout_mins: 180 action { define_artifacts { regex: "artifacts/**/*sponge_log.xml" - regex: "artifacts/**/*sponge_log.log" + regex: "artifacts/**/*.log" strip_prefix: "artifacts" } } From a82e40ff9a7946df11557a77ee6b00e5c8bd946b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 29 Aug 2022 09:52:13 -0700 Subject: [PATCH 315/694] grpc-js: Handle errors when decoding status details --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/call-stream.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 72b4b10ed..dab771696 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.10", + "version": "1.6.11", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index c405405e8..0bb4d01cb 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -488,7 +488,11 @@ export class Http2CallStream implements Call { } let details = ''; if (typeof metadataMap['grpc-message'] === 'string') { - details = decodeURI(metadataMap['grpc-message']); + try { + details = decodeURI(metadataMap['grpc-message']); + } catch (e) { + details = metadataMap['grpc-messages'] as string; + } metadata.remove('grpc-message'); this.trace( 'received status details string "' + details + '" from server' From c323369929e321a3de3465caeb97c74a527c7156 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 29 Aug 2022 15:41:51 -0700 Subject: [PATCH 316/694] grpc-js: Enable outlier detection by default --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index bf7a72f87..685cfc60d 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -38,7 +38,7 @@ function trace(text: string): void { const TYPE_NAME = 'outlier_detection'; -const OUTLIER_DETECTION_ENABLED = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION === 'true'; +const OUTLIER_DETECTION_ENABLED = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION !== 'false'; export interface SuccessRateEjectionConfig { readonly stdev_factor: number; From ccd855fb5add8e92e22418a84ed5eccedf239a6f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 29 Aug 2022 18:18:53 -0700 Subject: [PATCH 317/694] grpc-js: Fix typo in previous status message handling fix --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/call-stream.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index dab771696..8ca6eb1c6 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.11", + "version": "1.6.12", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index 0bb4d01cb..d7151eb6f 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -491,7 +491,7 @@ export class Http2CallStream implements Call { try { details = decodeURI(metadataMap['grpc-message']); } catch (e) { - details = metadataMap['grpc-messages'] as string; + details = metadataMap['grpc-message']; } metadata.remove('grpc-message'); this.trace( From bc66ebf62f0a8418c9ee90a1a038a7492078bb4d Mon Sep 17 00:00:00 2001 From: install <1994052+install@users.noreply.github.com> Date: Fri, 5 Aug 2022 12:38:36 -0400 Subject: [PATCH 318/694] proto-loader-gen-types Typename templates - Allow for customizing the naming pattern for both restricted and permissive types --- packages/proto-loader/README.md | 4 + .../bin/proto-loader-gen-types.ts | 84 ++++++++++++------- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/packages/proto-loader/README.md b/packages/proto-loader/README.md index 123fad111..818f0efda 100644 --- a/packages/proto-loader/README.md +++ b/packages/proto-loader/README.md @@ -88,6 +88,10 @@ Options: -O, --outDir Directory in which to output files [string] [required] --grpcLib The gRPC implementation library that these types will be used with [string] [required] + --inputTemplate Template for mapping input or "permissive" type names + [string] [default: "%s"] + --outputTemplate Template for mapping output or "restricted" type names + [string] [default: "%s__Output"] ``` ### Example Usage diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index a819d12d4..bb5b35f81 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -26,12 +26,25 @@ import * as yargs from 'yargs'; import camelCase = require('lodash.camelcase'); import { loadProtosWithOptions, addCommonProtos } from '../src/util'; +const templateStr = "%s"; +const useNameFmter = ({outputTemplate, inputTemplate}: GeneratorOptions) => { + if (outputTemplate === inputTemplate) { + throw new Error('inputTemplate and outputTemplate must differ') + } + return { + outputName: (n: string) => outputTemplate.replace(templateStr, n), + inputName: (n: string) => inputTemplate.replace(templateStr, n) + }; +} + type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeDirs?: string[]; grpcLib: string; outDir: string; verbose?: boolean; includeComments?: boolean; + inputTemplate: string; + outputTemplate: string; } class TextFormatter { @@ -114,15 +127,16 @@ function getTypeInterfaceName(type: Protobuf.Type | Protobuf.Enum | Protobuf.Ser return type.fullName.replace(/\./g, '_'); } -function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Service, from?: Protobuf.Type | Protobuf.Service) { +function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Service, from: Protobuf.Type | Protobuf.Service | undefined, options: GeneratorOptions) { const filePath = from === undefined ? './' + getImportPath(dependency) : getRelativeImportPath(from, dependency); + const {outputName, inputName} = useNameFmter(options); const typeInterfaceName = getTypeInterfaceName(dependency); let importedTypes: string; /* If the dependency is defined within a message, it will be generated in that * message's file and exported using its typeInterfaceName. */ if (dependency.parent instanceof Protobuf.Type) { if (dependency instanceof Protobuf.Type) { - importedTypes = `${typeInterfaceName}, ${typeInterfaceName}__Output`; + importedTypes = `${inputName(typeInterfaceName)}, ${outputName(typeInterfaceName)}`; } else if (dependency instanceof Protobuf.Enum) { importedTypes = `${typeInterfaceName}`; } else if (dependency instanceof Protobuf.Service) { @@ -132,7 +146,7 @@ function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Serv } } else { if (dependency instanceof Protobuf.Type) { - importedTypes = `${dependency.name} as ${typeInterfaceName}, ${dependency.name}__Output as ${typeInterfaceName}__Output`; + importedTypes = `${inputName(dependency.name)} as ${inputName(typeInterfaceName)}, ${outputName(dependency.name)} as ${outputName(typeInterfaceName)}`; } else if (dependency instanceof Protobuf.Enum) { importedTypes = `${dependency.name} as ${typeInterfaceName}`; } else if (dependency instanceof Protobuf.Service) { @@ -170,7 +184,8 @@ function formatComment(formatter: TextFormatter, comment?: string | null) { // GENERATOR FUNCTIONS -function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean): string { +function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean, options: GeneratorOptions): string { + const {inputName} = useNameFmter(options); switch (fieldType) { case 'double': case 'float': @@ -200,9 +215,9 @@ function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | const typeInterfaceName = getTypeInterfaceName(resolvedType); if (resolvedType instanceof Protobuf.Type) { if (repeated || map) { - return typeInterfaceName; + return inputName(typeInterfaceName); } else { - return `${typeInterfaceName} | null`; + return `${inputName(typeInterfaceName)} | null`; } } else { return `${typeInterfaceName} | keyof typeof ${typeInterfaceName}`; @@ -210,8 +225,8 @@ function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | } } -function getFieldTypePermissive(field: Protobuf.FieldBase): string { - const valueType = getTypeNamePermissive(field.type, field.resolvedType, field.repeated, field.map); +function getFieldTypePermissive(field: Protobuf.FieldBase, options: GeneratorOptions): string { + const valueType = getTypeNamePermissive(field.type, field.resolvedType, field.repeated, field.map, options); if (field instanceof Protobuf.MapField) { const keyType = field.keyType === 'string' ? 'string' : 'number'; return `{[key: ${keyType}]: ${valueType}}`; @@ -221,23 +236,24 @@ function getFieldTypePermissive(field: Protobuf.FieldBase): string { } function generatePermissiveMessageInterface(formatter: TextFormatter, messageType: Protobuf.Type, options: GeneratorOptions, nameOverride?: string) { + const {inputName} = useNameFmter(options); if (options.includeComments) { formatComment(formatter, messageType.comment); } if (messageType.fullName === '.google.protobuf.Any') { /* This describes the behavior of the Protobuf.js Any wrapper fromObject * replacement function */ - formatter.writeLine('export type Any = AnyExtension | {'); + formatter.writeLine(`export type ${inputName('Any')} = AnyExtension | {`); formatter.writeLine(' type_url: string;'); formatter.writeLine(' value: Buffer | Uint8Array | string;'); formatter.writeLine('}'); return; } - formatter.writeLine(`export interface ${nameOverride ?? messageType.name} {`); + formatter.writeLine(`export interface ${inputName(nameOverride ?? messageType.name)} {`); formatter.indent(); for (const field of messageType.fieldsArray) { const repeatedString = field.repeated ? '[]' : ''; - const type: string = getFieldTypePermissive(field); + const type: string = getFieldTypePermissive(field, options); if (options.includeComments) { formatComment(formatter, field.comment); } @@ -255,6 +271,7 @@ function generatePermissiveMessageInterface(formatter: TextFormatter, messageTyp } function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean, options: GeneratorOptions): string { + const {outputName} = useNameFmter(options); switch (fieldType) { case 'double': case 'float': @@ -302,9 +319,9 @@ function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | /* null is only used to represent absent message values if the defaults * option is set, and only for non-repeated, non-map fields. */ if (options.defaults && !repeated && !map) { - return `${typeInterfaceName}__Output | null`; + return `${outputName(typeInterfaceName)} | null`; } else { - return `${typeInterfaceName}__Output`; + return `${outputName(typeInterfaceName)}`; } } else { if (options.enums == String) { @@ -327,6 +344,7 @@ function getFieldTypeRestricted(field: Protobuf.FieldBase, options: GeneratorOpt } function generateRestrictedMessageInterface(formatter: TextFormatter, messageType: Protobuf.Type, options: GeneratorOptions, nameOverride?: string) { + const {outputName} = useNameFmter(options); if (options.includeComments) { formatComment(formatter, messageType.comment); } @@ -334,13 +352,13 @@ function generateRestrictedMessageInterface(formatter: TextFormatter, messageTyp /* This describes the behavior of the Protobuf.js Any wrapper toObject * replacement function */ let optionalString = options.defaults ? '' : '?'; - formatter.writeLine('export type Any__Output = AnyExtension | {'); + formatter.writeLine(`export type ${outputName('Any')} = AnyExtension | {`); formatter.writeLine(` type_url${optionalString}: string;`); formatter.writeLine(` value${optionalString}: ${getTypeNameRestricted('bytes', null, false, false, options)};`); formatter.writeLine('}'); return; } - formatter.writeLine(`export interface ${nameOverride ?? messageType.name}__Output {`); + formatter.writeLine(`export interface ${outputName(nameOverride ?? messageType.name)} {`); formatter.indent(); for (const field of messageType.fieldsArray) { let fieldGuaranteed: boolean; @@ -389,7 +407,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob continue; } seenDeps.add(dependency.fullName); - formatter.writeLine(getImportLine(dependency, messageType)); + formatter.writeLine(getImportLine(dependency, messageType, options)); } if (field.type.indexOf('64') >= 0) { usesLong = true; @@ -404,7 +422,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob continue; } seenDeps.add(dependency.fullName); - formatter.writeLine(getImportLine(dependency, messageType)); + formatter.writeLine(getImportLine(dependency, messageType, options)); } if (field.type.indexOf('64') >= 0) { usesLong = true; @@ -487,6 +505,7 @@ const CLIENT_RESERVED_METHOD_NAMES = new Set([ ]); function generateServiceClientInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + const {outputName, inputName} = useNameFmter(options); if (options.includeComments) { formatComment(formatter, serviceType.comment); } @@ -501,8 +520,8 @@ function generateServiceClientInterface(formatter: TextFormatter, serviceType: P if (options.includeComments) { formatComment(formatter, method.comment); } - const requestType = getTypeInterfaceName(method.resolvedRequestType!); - const responseType = getTypeInterfaceName(method.resolvedResponseType!) + '__Output'; + const requestType = inputName(getTypeInterfaceName(method.resolvedRequestType!)); + const responseType = outputName(getTypeInterfaceName(method.resolvedResponseType!)); const callbackType = `grpc.requestCallback<${responseType}>`; if (method.requestStream) { if (method.responseStream) { @@ -541,6 +560,7 @@ function generateServiceClientInterface(formatter: TextFormatter, serviceType: P } function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + const {inputName, outputName} = useNameFmter(options); if (options.includeComments) { formatComment(formatter, serviceType.comment); } @@ -551,8 +571,8 @@ function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: if (options.includeComments) { formatComment(formatter, method.comment); } - const requestType = getTypeInterfaceName(method.resolvedRequestType!) + '__Output'; - const responseType = getTypeInterfaceName(method.resolvedResponseType!); + const requestType = outputName(getTypeInterfaceName(method.resolvedRequestType!)); + const responseType = inputName(getTypeInterfaceName(method.resolvedResponseType!)); if (method.requestStream) { if (method.responseStream) { // Bidi streaming @@ -576,14 +596,15 @@ function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: formatter.writeLine('}'); } -function generateServiceDefinitionInterface(formatter: TextFormatter, serviceType: Protobuf.Service) { +function generateServiceDefinitionInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { + const {inputName, outputName} = useNameFmter(options); formatter.writeLine(`export interface ${serviceType.name}Definition extends grpc.ServiceDefinition {`); formatter.indent(); for (const methodName of Object.keys(serviceType.methods).sort()) { const method = serviceType.methods[methodName]; const requestType = getTypeInterfaceName(method.resolvedRequestType!); const responseType = getTypeInterfaceName(method.resolvedResponseType!); - formatter.writeLine(`${methodName}: MethodDefinition<${requestType}, ${responseType}, ${requestType}__Output, ${responseType}__Output>`); + formatter.writeLine(`${methodName}: MethodDefinition<${inputName(requestType)}, ${inputName(responseType)}, ${outputName(requestType)}, ${outputName(responseType)}>`); } formatter.unindent(); formatter.writeLine('}') @@ -601,7 +622,7 @@ function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protob dependencies.add(method.resolvedResponseType!); } for (const dep of Array.from(dependencies.values()).sort(compareName)) { - formatter.writeLine(getImportLine(dep, serviceType)); + formatter.writeLine(getImportLine(dep, serviceType, options)); } formatter.writeLine(''); @@ -611,7 +632,7 @@ function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protob generateServiceHandlerInterface(formatter, serviceType, options); formatter.writeLine(''); - generateServiceDefinitionInterface(formatter, serviceType); + generateServiceDefinitionInterface(formatter, serviceType, options); } function containsDefinition(definitionType: typeof Protobuf.Type | typeof Protobuf.Enum, namespace: Protobuf.NamespaceBase): boolean { @@ -645,7 +666,7 @@ function generateDefinitionImports(formatter: TextFormatter, namespace: Protobuf function generateServiceImports(formatter: TextFormatter, namespace: Protobuf.NamespaceBase, options: GeneratorOptions) { for (const nested of namespace.nestedArray.sort(compareName)) { if (nested instanceof Protobuf.Service) { - formatter.writeLine(getImportLine(nested)); + formatter.writeLine(getImportLine(nested, undefined, options)); } else if (isNamespaceBase(nested) && !(nested instanceof Protobuf.Type) && !(nested instanceof Protobuf.Enum)) { generateServiceImports(formatter, nested, options); } @@ -776,7 +797,7 @@ async function runScript() { .normalize(['includeDirs', 'outDir']) .array('includeDirs') .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments']) - .string(['longs', 'enums', 'bytes']) + .string(['longs', 'enums', 'bytes', 'inputTemplate', 'outputTemplate']) .default('keepCase', false) .default('defaults', false) .default('arrays', false) @@ -787,6 +808,8 @@ async function runScript() { .default('longs', 'Long') .default('enums', 'number') .default('bytes', 'Buffer') + .default('inputTemplate', `${templateStr}`) + .default('outputTemplate', `${templateStr}__Output`) .coerce('longs', value => { switch (value) { case 'String': return String; @@ -805,7 +828,8 @@ async function runScript() { case 'String': return String; default: return undefined; } - }).alias({ + }) + .alias({ includeDirs: 'I', outDir: 'O', verbose: 'v' @@ -822,7 +846,9 @@ async function runScript() { includeComments: 'Generate doc comments from comments in the original files', includeDirs: 'Directories to search for included files', outDir: 'Directory in which to output files', - grpcLib: 'The gRPC implementation library that these types will be used with' + grpcLib: 'The gRPC implementation library that these types will be used with', + inputTemplate: 'Template for mapping input or "permissive" type names', + outputTemplate: 'Template for mapping output or "restricted" type names', }).demandOption(['outDir', 'grpcLib']) .demand(1) .usage('$0 [options] filenames...') From d81ec8e5326a8f48cc824878b8ce7ecb09b95ffb Mon Sep 17 00:00:00 2001 From: install <1994052+install@users.noreply.github.com> Date: Wed, 7 Sep 2022 09:59:22 -0400 Subject: [PATCH 319/694] Update golden tests - use input/output templates --- .../google/api/CustomHttpPattern.ts | 4 +- .../golden-generated/google/api/Http.ts | 10 +- .../golden-generated/google/api/HttpRule.ts | 16 +- .../longrunning/CancelOperationRequest.ts | 4 +- .../longrunning/DeleteOperationRequest.ts | 4 +- .../google/longrunning/GetOperationRequest.ts | 4 +- .../longrunning/ListOperationsRequest.ts | 4 +- .../longrunning/ListOperationsResponse.ts | 10 +- .../google/longrunning/Operation.ts | 20 +-- .../google/longrunning/OperationInfo.ts | 4 +- .../google/longrunning/Operations.ts | 116 +++++++------- .../longrunning/WaitOperationRequest.ts | 10 +- .../golden-generated/google/protobuf/Any.ts | 4 +- .../google/protobuf/DescriptorProto.ts | 54 +++---- .../google/protobuf/Duration.ts | 4 +- .../golden-generated/google/protobuf/Empty.ts | 4 +- .../google/protobuf/EnumDescriptorProto.ts | 16 +- .../google/protobuf/EnumOptions.ts | 10 +- .../protobuf/EnumValueDescriptorProto.ts | 10 +- .../google/protobuf/EnumValueOptions.ts | 10 +- .../google/protobuf/FieldDescriptorProto.ts | 10 +- .../google/protobuf/FieldOptions.ts | 10 +- .../google/protobuf/FileDescriptorProto.ts | 40 ++--- .../google/protobuf/FileDescriptorSet.ts | 10 +- .../google/protobuf/FileOptions.ts | 10 +- .../google/protobuf/GeneratedCodeInfo.ts | 12 +- .../google/protobuf/MessageOptions.ts | 10 +- .../google/protobuf/MethodDescriptorProto.ts | 10 +- .../google/protobuf/MethodOptions.ts | 22 +-- .../google/protobuf/OneofDescriptorProto.ts | 10 +- .../google/protobuf/OneofOptions.ts | 10 +- .../google/protobuf/ServiceDescriptorProto.ts | 16 +- .../google/protobuf/ServiceOptions.ts | 10 +- .../google/protobuf/SourceCodeInfo.ts | 12 +- .../google/protobuf/Timestamp.ts | 4 +- .../google/protobuf/UninterpretedOption.ts | 12 +- .../golden-generated/google/rpc/Status.ts | 10 +- .../google/showcase/v1beta1/BlockRequest.ts | 22 +-- .../google/showcase/v1beta1/BlockResponse.ts | 4 +- .../google/showcase/v1beta1/Echo.ts | 142 +++++++++--------- .../google/showcase/v1beta1/EchoRequest.ts | 10 +- .../google/showcase/v1beta1/EchoResponse.ts | 4 +- .../google/showcase/v1beta1/ExpandRequest.ts | 10 +- .../showcase/v1beta1/PagedExpandRequest.ts | 4 +- .../showcase/v1beta1/PagedExpandResponse.ts | 10 +- .../google/showcase/v1beta1/WaitMetadata.ts | 10 +- .../google/showcase/v1beta1/WaitRequest.ts | 28 ++-- .../google/showcase/v1beta1/WaitResponse.ts | 4 +- packages/proto-loader/package.json | 2 +- 49 files changed, 393 insertions(+), 393 deletions(-) diff --git a/packages/proto-loader/golden-generated/google/api/CustomHttpPattern.ts b/packages/proto-loader/golden-generated/google/api/CustomHttpPattern.ts index 2b6490be6..2f6e20297 100644 --- a/packages/proto-loader/golden-generated/google/api/CustomHttpPattern.ts +++ b/packages/proto-loader/golden-generated/google/api/CustomHttpPattern.ts @@ -4,7 +4,7 @@ /** * A custom pattern is used for defining custom HTTP verb. */ -export interface CustomHttpPattern { +export interface ICustomHttpPattern { /** * The name of this custom HTTP verb. */ @@ -18,7 +18,7 @@ export interface CustomHttpPattern { /** * A custom pattern is used for defining custom HTTP verb. */ -export interface CustomHttpPattern__Output { +export interface OCustomHttpPattern { /** * The name of this custom HTTP verb. */ diff --git a/packages/proto-loader/golden-generated/google/api/Http.ts b/packages/proto-loader/golden-generated/google/api/Http.ts index e9b3cb309..6b6ae8a63 100644 --- a/packages/proto-loader/golden-generated/google/api/Http.ts +++ b/packages/proto-loader/golden-generated/google/api/Http.ts @@ -1,19 +1,19 @@ // Original file: deps/googleapis/google/api/http.proto -import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; +import type { IHttpRule as I_google_api_HttpRule, OHttpRule as O_google_api_HttpRule } from '../../google/api/HttpRule'; /** * Defines the HTTP configuration for an API service. It contains a list of * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method * to one or more HTTP REST API methods. */ -export interface Http { +export interface IHttp { /** * A list of HTTP configuration rules that apply to individual API methods. * * **NOTE:** All service configuration rules follow "last one wins" order. */ - 'rules'?: (_google_api_HttpRule)[]; + 'rules'?: (I_google_api_HttpRule)[]; /** * When set to true, URL path parameters will be fully URI-decoded except in * cases of single segment matches in reserved expansion, where "%2F" will be @@ -30,13 +30,13 @@ export interface Http { * [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method * to one or more HTTP REST API methods. */ -export interface Http__Output { +export interface OHttp { /** * A list of HTTP configuration rules that apply to individual API methods. * * **NOTE:** All service configuration rules follow "last one wins" order. */ - 'rules': (_google_api_HttpRule__Output)[]; + 'rules': (O_google_api_HttpRule)[]; /** * When set to true, URL path parameters will be fully URI-decoded except in * cases of single segment matches in reserved expansion, where "%2F" will be diff --git a/packages/proto-loader/golden-generated/google/api/HttpRule.ts b/packages/proto-loader/golden-generated/google/api/HttpRule.ts index 243a99f80..90efdc00d 100644 --- a/packages/proto-loader/golden-generated/google/api/HttpRule.ts +++ b/packages/proto-loader/golden-generated/google/api/HttpRule.ts @@ -1,7 +1,7 @@ // Original file: deps/googleapis/google/api/http.proto -import type { CustomHttpPattern as _google_api_CustomHttpPattern, CustomHttpPattern__Output as _google_api_CustomHttpPattern__Output } from '../../google/api/CustomHttpPattern'; -import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; +import type { ICustomHttpPattern as I_google_api_CustomHttpPattern, OCustomHttpPattern as O_google_api_CustomHttpPattern } from '../../google/api/CustomHttpPattern'; +import type { IHttpRule as I_google_api_HttpRule, OHttpRule as O_google_api_HttpRule } from '../../google/api/HttpRule'; /** * # gRPC Transcoding @@ -274,7 +274,7 @@ import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_ * the request or response body to a repeated field. However, some gRPC * Transcoding implementations may not support this feature. */ -export interface HttpRule { +export interface IHttpRule { /** * Selects a method to which this rule applies. * @@ -317,13 +317,13 @@ export interface HttpRule { * HTTP method unspecified for this rule. The wild-card rule is useful * for services that provide content to Web (HTML) clients. */ - 'custom'?: (_google_api_CustomHttpPattern | null); + 'custom'?: (I_google_api_CustomHttpPattern | null); /** * Additional HTTP bindings for the selector. Nested bindings must * not contain an `additional_bindings` field themselves (that is, * the nesting may only be one level deep). */ - 'additional_bindings'?: (_google_api_HttpRule)[]; + 'additional_bindings'?: (I_google_api_HttpRule)[]; /** * Optional. The name of the response field whose value is mapped to the HTTP * response body. When omitted, the entire response message will be used @@ -612,7 +612,7 @@ export interface HttpRule { * the request or response body to a repeated field. However, some gRPC * Transcoding implementations may not support this feature. */ -export interface HttpRule__Output { +export interface OHttpRule { /** * Selects a method to which this rule applies. * @@ -655,13 +655,13 @@ export interface HttpRule__Output { * HTTP method unspecified for this rule. The wild-card rule is useful * for services that provide content to Web (HTML) clients. */ - 'custom'?: (_google_api_CustomHttpPattern__Output | null); + 'custom'?: (O_google_api_CustomHttpPattern | null); /** * Additional HTTP bindings for the selector. Nested bindings must * not contain an `additional_bindings` field themselves (that is, * the nesting may only be one level deep). */ - 'additional_bindings': (_google_api_HttpRule__Output)[]; + 'additional_bindings': (O_google_api_HttpRule)[]; /** * Optional. The name of the response field whose value is mapped to the HTTP * response body. When omitted, the entire response message will be used diff --git a/packages/proto-loader/golden-generated/google/longrunning/CancelOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/CancelOperationRequest.ts index 05fbc842e..7e0f15ed8 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/CancelOperationRequest.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/CancelOperationRequest.ts @@ -4,7 +4,7 @@ /** * The request message for [Operations.CancelOperation][google.longrunning.Operations.CancelOperation]. */ -export interface CancelOperationRequest { +export interface ICancelOperationRequest { /** * The name of the operation resource to be cancelled. */ @@ -14,7 +14,7 @@ export interface CancelOperationRequest { /** * The request message for [Operations.CancelOperation][google.longrunning.Operations.CancelOperation]. */ -export interface CancelOperationRequest__Output { +export interface OCancelOperationRequest { /** * The name of the operation resource to be cancelled. */ diff --git a/packages/proto-loader/golden-generated/google/longrunning/DeleteOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/DeleteOperationRequest.ts index 0ad87cde9..39d669d0a 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/DeleteOperationRequest.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/DeleteOperationRequest.ts @@ -4,7 +4,7 @@ /** * The request message for [Operations.DeleteOperation][google.longrunning.Operations.DeleteOperation]. */ -export interface DeleteOperationRequest { +export interface IDeleteOperationRequest { /** * The name of the operation resource to be deleted. */ @@ -14,7 +14,7 @@ export interface DeleteOperationRequest { /** * The request message for [Operations.DeleteOperation][google.longrunning.Operations.DeleteOperation]. */ -export interface DeleteOperationRequest__Output { +export interface ODeleteOperationRequest { /** * The name of the operation resource to be deleted. */ diff --git a/packages/proto-loader/golden-generated/google/longrunning/GetOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/GetOperationRequest.ts index 039f01674..9667e2e87 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/GetOperationRequest.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/GetOperationRequest.ts @@ -4,7 +4,7 @@ /** * The request message for [Operations.GetOperation][google.longrunning.Operations.GetOperation]. */ -export interface GetOperationRequest { +export interface IGetOperationRequest { /** * The name of the operation resource. */ @@ -14,7 +14,7 @@ export interface GetOperationRequest { /** * The request message for [Operations.GetOperation][google.longrunning.Operations.GetOperation]. */ -export interface GetOperationRequest__Output { +export interface OGetOperationRequest { /** * The name of the operation resource. */ diff --git a/packages/proto-loader/golden-generated/google/longrunning/ListOperationsRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsRequest.ts index 294ec6773..49dcd39f0 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/ListOperationsRequest.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsRequest.ts @@ -4,7 +4,7 @@ /** * The request message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. */ -export interface ListOperationsRequest { +export interface IListOperationsRequest { /** * The standard list filter. */ @@ -26,7 +26,7 @@ export interface ListOperationsRequest { /** * The request message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. */ -export interface ListOperationsRequest__Output { +export interface OListOperationsRequest { /** * The standard list filter. */ diff --git a/packages/proto-loader/golden-generated/google/longrunning/ListOperationsResponse.ts b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsResponse.ts index c295aa801..1e8b9ed5a 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/ListOperationsResponse.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/ListOperationsResponse.ts @@ -1,15 +1,15 @@ // Original file: deps/googleapis/google/longrunning/operations.proto -import type { Operation as _google_longrunning_Operation, Operation__Output as _google_longrunning_Operation__Output } from '../../google/longrunning/Operation'; +import type { IOperation as I_google_longrunning_Operation, OOperation as O_google_longrunning_Operation } from '../../google/longrunning/Operation'; /** * The response message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. */ -export interface ListOperationsResponse { +export interface IListOperationsResponse { /** * A list of operations that matches the specified filter in the request. */ - 'operations'?: (_google_longrunning_Operation)[]; + 'operations'?: (I_google_longrunning_Operation)[]; /** * The standard List next-page token. */ @@ -19,11 +19,11 @@ export interface ListOperationsResponse { /** * The response message for [Operations.ListOperations][google.longrunning.Operations.ListOperations]. */ -export interface ListOperationsResponse__Output { +export interface OListOperationsResponse { /** * A list of operations that matches the specified filter in the request. */ - 'operations': (_google_longrunning_Operation__Output)[]; + 'operations': (O_google_longrunning_Operation)[]; /** * The standard List next-page token. */ diff --git a/packages/proto-loader/golden-generated/google/longrunning/Operation.ts b/packages/proto-loader/golden-generated/google/longrunning/Operation.ts index 2a4bbe1ee..bbd1d8078 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/Operation.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/Operation.ts @@ -1,13 +1,13 @@ // Original file: deps/googleapis/google/longrunning/operations.proto -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../google/protobuf/Any'; -import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../google/rpc/Status'; +import type { IAny as I_google_protobuf_Any, OAny as O_google_protobuf_Any } from '../../google/protobuf/Any'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../google/rpc/Status'; /** * This resource represents a long-running operation that is the result of a * network API call. */ -export interface Operation { +export interface IOperation { /** * The server-assigned name, which is only unique within the same service that * originally returns it. If you use the default HTTP mapping, the @@ -20,7 +20,7 @@ export interface Operation { * Some services might not provide such metadata. Any method that returns a * long-running operation should document the metadata type, if any. */ - 'metadata'?: (_google_protobuf_Any | null); + 'metadata'?: (I_google_protobuf_Any | null); /** * If the value is `false`, it means the operation is still in progress. * If `true`, the operation is completed, and either `error` or `response` is @@ -30,7 +30,7 @@ export interface Operation { /** * The error result of the operation in case of failure or cancellation. */ - 'error'?: (_google_rpc_Status | null); + 'error'?: (I_google_rpc_Status | null); /** * The normal response of the operation in case of success. If the original * method returns no data on success, such as `Delete`, the response is @@ -41,7 +41,7 @@ export interface Operation { * is `TakeSnapshot()`, the inferred response type is * `TakeSnapshotResponse`. */ - 'response'?: (_google_protobuf_Any | null); + 'response'?: (I_google_protobuf_Any | null); /** * The operation result, which can be either an `error` or a valid `response`. * If `done` == `false`, neither `error` nor `response` is set. @@ -54,7 +54,7 @@ export interface Operation { * This resource represents a long-running operation that is the result of a * network API call. */ -export interface Operation__Output { +export interface OOperation { /** * The server-assigned name, which is only unique within the same service that * originally returns it. If you use the default HTTP mapping, the @@ -67,7 +67,7 @@ export interface Operation__Output { * Some services might not provide such metadata. Any method that returns a * long-running operation should document the metadata type, if any. */ - 'metadata': (_google_protobuf_Any__Output | null); + 'metadata': (O_google_protobuf_Any | null); /** * If the value is `false`, it means the operation is still in progress. * If `true`, the operation is completed, and either `error` or `response` is @@ -77,7 +77,7 @@ export interface Operation__Output { /** * The error result of the operation in case of failure or cancellation. */ - 'error'?: (_google_rpc_Status__Output | null); + 'error'?: (O_google_rpc_Status | null); /** * The normal response of the operation in case of success. If the original * method returns no data on success, such as `Delete`, the response is @@ -88,7 +88,7 @@ export interface Operation__Output { * is `TakeSnapshot()`, the inferred response type is * `TakeSnapshotResponse`. */ - 'response'?: (_google_protobuf_Any__Output | null); + 'response'?: (O_google_protobuf_Any | null); /** * The operation result, which can be either an `error` or a valid `response`. * If `done` == `false`, neither `error` nor `response` is set. diff --git a/packages/proto-loader/golden-generated/google/longrunning/OperationInfo.ts b/packages/proto-loader/golden-generated/google/longrunning/OperationInfo.ts index 343e2f8c9..907574412 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/OperationInfo.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/OperationInfo.ts @@ -14,7 +14,7 @@ * }; * } */ -export interface OperationInfo { +export interface IOperationInfo { /** * Required. The message name of the primary return type for this * long-running operation. @@ -51,7 +51,7 @@ export interface OperationInfo { * }; * } */ -export interface OperationInfo__Output { +export interface OOperationInfo { /** * Required. The message name of the primary return type for this * long-running operation. diff --git a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts index 6358cbbfd..00d6a95d2 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/Operations.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/Operations.ts @@ -2,14 +2,14 @@ import type * as grpc from '@grpc/grpc-js' import type { MethodDefinition } from '@grpc/proto-loader' -import type { CancelOperationRequest as _google_longrunning_CancelOperationRequest, CancelOperationRequest__Output as _google_longrunning_CancelOperationRequest__Output } from '../../google/longrunning/CancelOperationRequest'; -import type { DeleteOperationRequest as _google_longrunning_DeleteOperationRequest, DeleteOperationRequest__Output as _google_longrunning_DeleteOperationRequest__Output } from '../../google/longrunning/DeleteOperationRequest'; -import type { Empty as _google_protobuf_Empty, Empty__Output as _google_protobuf_Empty__Output } from '../../google/protobuf/Empty'; -import type { GetOperationRequest as _google_longrunning_GetOperationRequest, GetOperationRequest__Output as _google_longrunning_GetOperationRequest__Output } from '../../google/longrunning/GetOperationRequest'; -import type { ListOperationsRequest as _google_longrunning_ListOperationsRequest, ListOperationsRequest__Output as _google_longrunning_ListOperationsRequest__Output } from '../../google/longrunning/ListOperationsRequest'; -import type { ListOperationsResponse as _google_longrunning_ListOperationsResponse, ListOperationsResponse__Output as _google_longrunning_ListOperationsResponse__Output } from '../../google/longrunning/ListOperationsResponse'; -import type { Operation as _google_longrunning_Operation, Operation__Output as _google_longrunning_Operation__Output } from '../../google/longrunning/Operation'; -import type { WaitOperationRequest as _google_longrunning_WaitOperationRequest, WaitOperationRequest__Output as _google_longrunning_WaitOperationRequest__Output } from '../../google/longrunning/WaitOperationRequest'; +import type { ICancelOperationRequest as I_google_longrunning_CancelOperationRequest, OCancelOperationRequest as O_google_longrunning_CancelOperationRequest } from '../../google/longrunning/CancelOperationRequest'; +import type { IDeleteOperationRequest as I_google_longrunning_DeleteOperationRequest, ODeleteOperationRequest as O_google_longrunning_DeleteOperationRequest } from '../../google/longrunning/DeleteOperationRequest'; +import type { IEmpty as I_google_protobuf_Empty, OEmpty as O_google_protobuf_Empty } from '../../google/protobuf/Empty'; +import type { IGetOperationRequest as I_google_longrunning_GetOperationRequest, OGetOperationRequest as O_google_longrunning_GetOperationRequest } from '../../google/longrunning/GetOperationRequest'; +import type { IListOperationsRequest as I_google_longrunning_ListOperationsRequest, OListOperationsRequest as O_google_longrunning_ListOperationsRequest } from '../../google/longrunning/ListOperationsRequest'; +import type { IListOperationsResponse as I_google_longrunning_ListOperationsResponse, OListOperationsResponse as O_google_longrunning_ListOperationsResponse } from '../../google/longrunning/ListOperationsResponse'; +import type { IOperation as I_google_longrunning_Operation, OOperation as O_google_longrunning_Operation } from '../../google/longrunning/Operation'; +import type { IWaitOperationRequest as I_google_longrunning_WaitOperationRequest, OWaitOperationRequest as O_google_longrunning_WaitOperationRequest } from '../../google/longrunning/WaitOperationRequest'; /** * Manages long-running operations with an API service. @@ -35,10 +35,10 @@ export interface OperationsClient extends grpc.Client { * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, * corresponding to `Code.CANCELLED`. */ - CancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - CancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - CancelOperation(argument: _google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - CancelOperation(argument: _google_longrunning_CancelOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + CancelOperation(argument: I_google_longrunning_CancelOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Starts asynchronous cancellation on a long-running operation. The server * makes a best effort to cancel the operation, but success is not @@ -51,10 +51,10 @@ export interface OperationsClient extends grpc.Client { * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, * corresponding to `Code.CANCELLED`. */ - cancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - cancelOperation(argument: _google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - cancelOperation(argument: _google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - cancelOperation(argument: _google_longrunning_CancelOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + cancelOperation(argument: I_google_longrunning_CancelOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Deletes a long-running operation. This method indicates that the client is @@ -62,39 +62,39 @@ export interface OperationsClient extends grpc.Client { * operation. If the server doesn't support this method, it returns * `google.rpc.Code.UNIMPLEMENTED`. */ - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - DeleteOperation(argument: _google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + DeleteOperation(argument: I_google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Deletes a long-running operation. This method indicates that the client is * no longer interested in the operation result. It does not cancel the * operation. If the server doesn't support this method, it returns * `google.rpc.Code.UNIMPLEMENTED`. */ - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; - deleteOperation(argument: _google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback<_google_protobuf_Empty__Output>): grpc.ClientUnaryCall; + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + deleteOperation(argument: I_google_longrunning_DeleteOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Gets the latest state of a long-running operation. Clients can use this * method to poll the operation result at intervals as recommended by the API * service. */ - GetOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - GetOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - GetOperation(argument: _google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - GetOperation(argument: _google_longrunning_GetOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + GetOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + GetOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + GetOperation(argument: I_google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + GetOperation(argument: I_google_longrunning_GetOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Gets the latest state of a long-running operation. Clients can use this * method to poll the operation result at intervals as recommended by the API * service. */ - getOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - getOperation(argument: _google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - getOperation(argument: _google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - getOperation(argument: _google_longrunning_GetOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + getOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + getOperation(argument: I_google_longrunning_GetOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + getOperation(argument: I_google_longrunning_GetOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + getOperation(argument: I_google_longrunning_GetOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Lists operations that match the specified filter in the request. If the @@ -108,10 +108,10 @@ export interface OperationsClient extends grpc.Client { * collection id, however overriding users must ensure the name binding * is the parent resource, without the operations collection id. */ - ListOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; - ListOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; - ListOperations(argument: _google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; - ListOperations(argument: _google_longrunning_ListOperationsRequest, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + ListOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + ListOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + ListOperations(argument: I_google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + ListOperations(argument: I_google_longrunning_ListOperationsRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Lists operations that match the specified filter in the request. If the * server doesn't support this method, it returns `UNIMPLEMENTED`. @@ -124,10 +124,10 @@ export interface OperationsClient extends grpc.Client { * collection id, however overriding users must ensure the name binding * is the parent resource, without the operations collection id. */ - listOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; - listOperations(argument: _google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; - listOperations(argument: _google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; - listOperations(argument: _google_longrunning_ListOperationsRequest, callback: grpc.requestCallback<_google_longrunning_ListOperationsResponse__Output>): grpc.ClientUnaryCall; + listOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + listOperations(argument: I_google_longrunning_ListOperationsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + listOperations(argument: I_google_longrunning_ListOperationsRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + listOperations(argument: I_google_longrunning_ListOperationsRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Waits for the specified long-running operation until it is done or reaches @@ -140,10 +140,10 @@ export interface OperationsClient extends grpc.Client { * state before the specified timeout (including immediately), meaning even an * immediate response is no guarantee that the operation is done. */ - WaitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - WaitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - WaitOperation(argument: _google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - WaitOperation(argument: _google_longrunning_WaitOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + WaitOperation(argument: I_google_longrunning_WaitOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * Waits for the specified long-running operation until it is done or reaches * at most a specified timeout, returning the latest state. If the operation @@ -155,10 +155,10 @@ export interface OperationsClient extends grpc.Client { * state before the specified timeout (including immediately), meaning even an * immediate response is no guarantee that the operation is done. */ - waitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - waitOperation(argument: _google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - waitOperation(argument: _google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - waitOperation(argument: _google_longrunning_WaitOperationRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + waitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + waitOperation(argument: I_google_longrunning_WaitOperationRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + waitOperation(argument: I_google_longrunning_WaitOperationRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + waitOperation(argument: I_google_longrunning_WaitOperationRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; } @@ -186,7 +186,7 @@ export interface OperationsHandlers extends grpc.UntypedServiceImplementation { * an [Operation.error][google.longrunning.Operation.error] value with a [google.rpc.Status.code][google.rpc.Status.code] of 1, * corresponding to `Code.CANCELLED`. */ - CancelOperation: grpc.handleUnaryCall<_google_longrunning_CancelOperationRequest__Output, _google_protobuf_Empty>; + CancelOperation: grpc.handleUnaryCall; /** * Deletes a long-running operation. This method indicates that the client is @@ -194,14 +194,14 @@ export interface OperationsHandlers extends grpc.UntypedServiceImplementation { * operation. If the server doesn't support this method, it returns * `google.rpc.Code.UNIMPLEMENTED`. */ - DeleteOperation: grpc.handleUnaryCall<_google_longrunning_DeleteOperationRequest__Output, _google_protobuf_Empty>; + DeleteOperation: grpc.handleUnaryCall; /** * Gets the latest state of a long-running operation. Clients can use this * method to poll the operation result at intervals as recommended by the API * service. */ - GetOperation: grpc.handleUnaryCall<_google_longrunning_GetOperationRequest__Output, _google_longrunning_Operation>; + GetOperation: grpc.handleUnaryCall; /** * Lists operations that match the specified filter in the request. If the @@ -215,7 +215,7 @@ export interface OperationsHandlers extends grpc.UntypedServiceImplementation { * collection id, however overriding users must ensure the name binding * is the parent resource, without the operations collection id. */ - ListOperations: grpc.handleUnaryCall<_google_longrunning_ListOperationsRequest__Output, _google_longrunning_ListOperationsResponse>; + ListOperations: grpc.handleUnaryCall; /** * Waits for the specified long-running operation until it is done or reaches @@ -228,14 +228,14 @@ export interface OperationsHandlers extends grpc.UntypedServiceImplementation { * state before the specified timeout (including immediately), meaning even an * immediate response is no guarantee that the operation is done. */ - WaitOperation: grpc.handleUnaryCall<_google_longrunning_WaitOperationRequest__Output, _google_longrunning_Operation>; + WaitOperation: grpc.handleUnaryCall; } export interface OperationsDefinition extends grpc.ServiceDefinition { - CancelOperation: MethodDefinition<_google_longrunning_CancelOperationRequest, _google_protobuf_Empty, _google_longrunning_CancelOperationRequest__Output, _google_protobuf_Empty__Output> - DeleteOperation: MethodDefinition<_google_longrunning_DeleteOperationRequest, _google_protobuf_Empty, _google_longrunning_DeleteOperationRequest__Output, _google_protobuf_Empty__Output> - GetOperation: MethodDefinition<_google_longrunning_GetOperationRequest, _google_longrunning_Operation, _google_longrunning_GetOperationRequest__Output, _google_longrunning_Operation__Output> - ListOperations: MethodDefinition<_google_longrunning_ListOperationsRequest, _google_longrunning_ListOperationsResponse, _google_longrunning_ListOperationsRequest__Output, _google_longrunning_ListOperationsResponse__Output> - WaitOperation: MethodDefinition<_google_longrunning_WaitOperationRequest, _google_longrunning_Operation, _google_longrunning_WaitOperationRequest__Output, _google_longrunning_Operation__Output> + CancelOperation: MethodDefinition + DeleteOperation: MethodDefinition + GetOperation: MethodDefinition + ListOperations: MethodDefinition + WaitOperation: MethodDefinition } diff --git a/packages/proto-loader/golden-generated/google/longrunning/WaitOperationRequest.ts b/packages/proto-loader/golden-generated/google/longrunning/WaitOperationRequest.ts index f97e39dc4..2f11f7580 100644 --- a/packages/proto-loader/golden-generated/google/longrunning/WaitOperationRequest.ts +++ b/packages/proto-loader/golden-generated/google/longrunning/WaitOperationRequest.ts @@ -1,11 +1,11 @@ // Original file: deps/googleapis/google/longrunning/operations.proto -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../google/protobuf/Duration'; +import type { IDuration as I_google_protobuf_Duration, ODuration as O_google_protobuf_Duration } from '../../google/protobuf/Duration'; /** * The request message for [Operations.WaitOperation][google.longrunning.Operations.WaitOperation]. */ -export interface WaitOperationRequest { +export interface IWaitOperationRequest { /** * The name of the operation resource to wait on. */ @@ -15,13 +15,13 @@ export interface WaitOperationRequest { * will be at most the time permitted by the underlying HTTP/RPC protocol. * If RPC context deadline is also specified, the shorter one will be used. */ - 'timeout'?: (_google_protobuf_Duration | null); + 'timeout'?: (I_google_protobuf_Duration | null); } /** * The request message for [Operations.WaitOperation][google.longrunning.Operations.WaitOperation]. */ -export interface WaitOperationRequest__Output { +export interface OWaitOperationRequest { /** * The name of the operation resource to wait on. */ @@ -31,5 +31,5 @@ export interface WaitOperationRequest__Output { * will be at most the time permitted by the underlying HTTP/RPC protocol. * If RPC context deadline is also specified, the shorter one will be used. */ - 'timeout': (_google_protobuf_Duration__Output | null); + 'timeout': (O_google_protobuf_Duration | null); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/Any.ts b/packages/proto-loader/golden-generated/google/protobuf/Any.ts index fe0d05f12..d9ee4e200 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/Any.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/Any.ts @@ -2,12 +2,12 @@ import type { AnyExtension } from '@grpc/proto-loader'; -export type Any = AnyExtension | { +export type IAny = AnyExtension | { type_url: string; value: Buffer | Uint8Array | string; } -export type Any__Output = AnyExtension | { +export type OAny = AnyExtension | { type_url: string; value: Buffer; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/DescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/DescriptorProto.ts index f729437f4..5f568ca2c 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/DescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/DescriptorProto.ts @@ -1,53 +1,53 @@ // Original file: null -import type { FieldDescriptorProto as _google_protobuf_FieldDescriptorProto, FieldDescriptorProto__Output as _google_protobuf_FieldDescriptorProto__Output } from '../../google/protobuf/FieldDescriptorProto'; -import type { DescriptorProto as _google_protobuf_DescriptorProto, DescriptorProto__Output as _google_protobuf_DescriptorProto__Output } from '../../google/protobuf/DescriptorProto'; -import type { EnumDescriptorProto as _google_protobuf_EnumDescriptorProto, EnumDescriptorProto__Output as _google_protobuf_EnumDescriptorProto__Output } from '../../google/protobuf/EnumDescriptorProto'; -import type { MessageOptions as _google_protobuf_MessageOptions, MessageOptions__Output as _google_protobuf_MessageOptions__Output } from '../../google/protobuf/MessageOptions'; -import type { OneofDescriptorProto as _google_protobuf_OneofDescriptorProto, OneofDescriptorProto__Output as _google_protobuf_OneofDescriptorProto__Output } from '../../google/protobuf/OneofDescriptorProto'; +import type { IFieldDescriptorProto as I_google_protobuf_FieldDescriptorProto, OFieldDescriptorProto as O_google_protobuf_FieldDescriptorProto } from '../../google/protobuf/FieldDescriptorProto'; +import type { IDescriptorProto as I_google_protobuf_DescriptorProto, ODescriptorProto as O_google_protobuf_DescriptorProto } from '../../google/protobuf/DescriptorProto'; +import type { IEnumDescriptorProto as I_google_protobuf_EnumDescriptorProto, OEnumDescriptorProto as O_google_protobuf_EnumDescriptorProto } from '../../google/protobuf/EnumDescriptorProto'; +import type { IMessageOptions as I_google_protobuf_MessageOptions, OMessageOptions as O_google_protobuf_MessageOptions } from '../../google/protobuf/MessageOptions'; +import type { IOneofDescriptorProto as I_google_protobuf_OneofDescriptorProto, OOneofDescriptorProto as O_google_protobuf_OneofDescriptorProto } from '../../google/protobuf/OneofDescriptorProto'; -export interface _google_protobuf_DescriptorProto_ExtensionRange { +export interface I_google_protobuf_DescriptorProto_ExtensionRange { 'start'?: (number); 'end'?: (number); } -export interface _google_protobuf_DescriptorProto_ExtensionRange__Output { +export interface O_google_protobuf_DescriptorProto_ExtensionRange { 'start': (number); 'end': (number); } -export interface _google_protobuf_DescriptorProto_ReservedRange { +export interface I_google_protobuf_DescriptorProto_ReservedRange { 'start'?: (number); 'end'?: (number); } -export interface _google_protobuf_DescriptorProto_ReservedRange__Output { +export interface O_google_protobuf_DescriptorProto_ReservedRange { 'start': (number); 'end': (number); } -export interface DescriptorProto { +export interface IDescriptorProto { 'name'?: (string); - 'field'?: (_google_protobuf_FieldDescriptorProto)[]; - 'nestedType'?: (_google_protobuf_DescriptorProto)[]; - 'enumType'?: (_google_protobuf_EnumDescriptorProto)[]; - 'extensionRange'?: (_google_protobuf_DescriptorProto_ExtensionRange)[]; - 'extension'?: (_google_protobuf_FieldDescriptorProto)[]; - 'options'?: (_google_protobuf_MessageOptions | null); - 'oneofDecl'?: (_google_protobuf_OneofDescriptorProto)[]; - 'reservedRange'?: (_google_protobuf_DescriptorProto_ReservedRange)[]; + 'field'?: (I_google_protobuf_FieldDescriptorProto)[]; + 'nestedType'?: (I_google_protobuf_DescriptorProto)[]; + 'enumType'?: (I_google_protobuf_EnumDescriptorProto)[]; + 'extensionRange'?: (I_google_protobuf_DescriptorProto_ExtensionRange)[]; + 'extension'?: (I_google_protobuf_FieldDescriptorProto)[]; + 'options'?: (I_google_protobuf_MessageOptions | null); + 'oneofDecl'?: (I_google_protobuf_OneofDescriptorProto)[]; + 'reservedRange'?: (I_google_protobuf_DescriptorProto_ReservedRange)[]; 'reservedName'?: (string)[]; } -export interface DescriptorProto__Output { +export interface ODescriptorProto { 'name': (string); - 'field': (_google_protobuf_FieldDescriptorProto__Output)[]; - 'nestedType': (_google_protobuf_DescriptorProto__Output)[]; - 'enumType': (_google_protobuf_EnumDescriptorProto__Output)[]; - 'extensionRange': (_google_protobuf_DescriptorProto_ExtensionRange__Output)[]; - 'extension': (_google_protobuf_FieldDescriptorProto__Output)[]; - 'options': (_google_protobuf_MessageOptions__Output | null); - 'oneofDecl': (_google_protobuf_OneofDescriptorProto__Output)[]; - 'reservedRange': (_google_protobuf_DescriptorProto_ReservedRange__Output)[]; + 'field': (O_google_protobuf_FieldDescriptorProto)[]; + 'nestedType': (O_google_protobuf_DescriptorProto)[]; + 'enumType': (O_google_protobuf_EnumDescriptorProto)[]; + 'extensionRange': (O_google_protobuf_DescriptorProto_ExtensionRange)[]; + 'extension': (O_google_protobuf_FieldDescriptorProto)[]; + 'options': (O_google_protobuf_MessageOptions | null); + 'oneofDecl': (O_google_protobuf_OneofDescriptorProto)[]; + 'reservedRange': (O_google_protobuf_DescriptorProto_ReservedRange)[]; 'reservedName': (string)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/Duration.ts b/packages/proto-loader/golden-generated/google/protobuf/Duration.ts index 8595377a0..d5e3be89a 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/Duration.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/Duration.ts @@ -2,12 +2,12 @@ import type { Long } from '@grpc/proto-loader'; -export interface Duration { +export interface IDuration { 'seconds'?: (number | string | Long); 'nanos'?: (number); } -export interface Duration__Output { +export interface ODuration { 'seconds': (string); 'nanos': (number); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/Empty.ts b/packages/proto-loader/golden-generated/google/protobuf/Empty.ts index f32c2a284..6594cc86c 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/Empty.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/Empty.ts @@ -1,8 +1,8 @@ // Original file: null -export interface Empty { +export interface IEmpty { } -export interface Empty__Output { +export interface OEmpty { } diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumDescriptorProto.ts index dc4c9673e..30f52c610 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/EnumDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumDescriptorProto.ts @@ -1,16 +1,16 @@ // Original file: null -import type { EnumValueDescriptorProto as _google_protobuf_EnumValueDescriptorProto, EnumValueDescriptorProto__Output as _google_protobuf_EnumValueDescriptorProto__Output } from '../../google/protobuf/EnumValueDescriptorProto'; -import type { EnumOptions as _google_protobuf_EnumOptions, EnumOptions__Output as _google_protobuf_EnumOptions__Output } from '../../google/protobuf/EnumOptions'; +import type { IEnumValueDescriptorProto as I_google_protobuf_EnumValueDescriptorProto, OEnumValueDescriptorProto as O_google_protobuf_EnumValueDescriptorProto } from '../../google/protobuf/EnumValueDescriptorProto'; +import type { IEnumOptions as I_google_protobuf_EnumOptions, OEnumOptions as O_google_protobuf_EnumOptions } from '../../google/protobuf/EnumOptions'; -export interface EnumDescriptorProto { +export interface IEnumDescriptorProto { 'name'?: (string); - 'value'?: (_google_protobuf_EnumValueDescriptorProto)[]; - 'options'?: (_google_protobuf_EnumOptions | null); + 'value'?: (I_google_protobuf_EnumValueDescriptorProto)[]; + 'options'?: (I_google_protobuf_EnumOptions | null); } -export interface EnumDescriptorProto__Output { +export interface OEnumDescriptorProto { 'name': (string); - 'value': (_google_protobuf_EnumValueDescriptorProto__Output)[]; - 'options': (_google_protobuf_EnumOptions__Output | null); + 'value': (O_google_protobuf_EnumValueDescriptorProto)[]; + 'options': (O_google_protobuf_EnumOptions | null); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumOptions.ts index b92ade4f9..6d2a0c2c6 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/EnumOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumOptions.ts @@ -1,15 +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 { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; -export interface EnumOptions { +export interface IEnumOptions { 'allowAlias'?: (boolean); 'deprecated'?: (boolean); - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; } -export interface EnumOptions__Output { +export interface OEnumOptions { 'allowAlias': (boolean); 'deprecated': (boolean); - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumValueDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumValueDescriptorProto.ts index 7f8e57ea5..44cfcde4a 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/EnumValueDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumValueDescriptorProto.ts @@ -1,15 +1,15 @@ // Original file: null -import type { EnumValueOptions as _google_protobuf_EnumValueOptions, EnumValueOptions__Output as _google_protobuf_EnumValueOptions__Output } from '../../google/protobuf/EnumValueOptions'; +import type { IEnumValueOptions as I_google_protobuf_EnumValueOptions, OEnumValueOptions as O_google_protobuf_EnumValueOptions } from '../../google/protobuf/EnumValueOptions'; -export interface EnumValueDescriptorProto { +export interface IEnumValueDescriptorProto { 'name'?: (string); 'number'?: (number); - 'options'?: (_google_protobuf_EnumValueOptions | null); + 'options'?: (I_google_protobuf_EnumValueOptions | null); } -export interface EnumValueDescriptorProto__Output { +export interface OEnumValueDescriptorProto { 'name': (string); 'number': (number); - 'options': (_google_protobuf_EnumValueOptions__Output | null); + 'options': (O_google_protobuf_EnumValueOptions | null); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/EnumValueOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/EnumValueOptions.ts index e60ee6f4c..143381113 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/EnumValueOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/EnumValueOptions.ts @@ -1,13 +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 { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; -export interface EnumValueOptions { +export interface IEnumValueOptions { 'deprecated'?: (boolean); - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; } -export interface EnumValueOptions__Output { +export interface OEnumValueOptions { 'deprecated': (boolean); - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts index c511e2eff..0a713e9dc 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts @@ -1,6 +1,6 @@ // Original file: null -import type { FieldOptions as _google_protobuf_FieldOptions, FieldOptions__Output as _google_protobuf_FieldOptions__Output } from '../../google/protobuf/FieldOptions'; +import type { IFieldOptions as I_google_protobuf_FieldOptions, OFieldOptions as O_google_protobuf_FieldOptions } from '../../google/protobuf/FieldOptions'; // Original file: null @@ -33,7 +33,7 @@ export enum _google_protobuf_FieldDescriptorProto_Type { TYPE_SINT64 = 18, } -export interface FieldDescriptorProto { +export interface IFieldDescriptorProto { 'name'?: (string); 'extendee'?: (string); 'number'?: (number); @@ -41,12 +41,12 @@ export interface FieldDescriptorProto { 'type'?: (_google_protobuf_FieldDescriptorProto_Type | keyof typeof _google_protobuf_FieldDescriptorProto_Type); 'typeName'?: (string); 'defaultValue'?: (string); - 'options'?: (_google_protobuf_FieldOptions | null); + 'options'?: (I_google_protobuf_FieldOptions | null); 'oneofIndex'?: (number); 'jsonName'?: (string); } -export interface FieldDescriptorProto__Output { +export interface OFieldDescriptorProto { 'name': (string); 'extendee': (string); 'number': (number); @@ -54,7 +54,7 @@ export interface FieldDescriptorProto__Output { 'type': (keyof typeof _google_protobuf_FieldDescriptorProto_Type); 'typeName': (string); 'defaultValue': (string); - 'options': (_google_protobuf_FieldOptions__Output | null); + 'options': (O_google_protobuf_FieldOptions | null); 'oneofIndex': (number); 'jsonName': (string); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts index 8304053f1..076b35983 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts @@ -1,6 +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 { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; import type { FieldBehavior as _google_api_FieldBehavior } from '../../google/api/FieldBehavior'; // Original file: null @@ -19,24 +19,24 @@ export enum _google_protobuf_FieldOptions_JSType { JS_NUMBER = 2, } -export interface FieldOptions { +export interface IFieldOptions { 'ctype'?: (_google_protobuf_FieldOptions_CType | keyof typeof _google_protobuf_FieldOptions_CType); 'packed'?: (boolean); 'deprecated'?: (boolean); 'lazy'?: (boolean); 'jstype'?: (_google_protobuf_FieldOptions_JSType | keyof typeof _google_protobuf_FieldOptions_JSType); 'weak'?: (boolean); - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; '.google.api.field_behavior'?: (_google_api_FieldBehavior | keyof typeof _google_api_FieldBehavior)[]; } -export interface FieldOptions__Output { +export interface OFieldOptions { 'ctype': (keyof typeof _google_protobuf_FieldOptions_CType); 'packed': (boolean); 'deprecated': (boolean); 'lazy': (boolean); 'jstype': (keyof typeof _google_protobuf_FieldOptions_JSType); 'weak': (boolean); - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; '.google.api.field_behavior': (keyof typeof _google_api_FieldBehavior)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorProto.ts index b723da7c0..c98732f9d 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorProto.ts @@ -1,37 +1,37 @@ // Original file: null -import type { DescriptorProto as _google_protobuf_DescriptorProto, DescriptorProto__Output as _google_protobuf_DescriptorProto__Output } from '../../google/protobuf/DescriptorProto'; -import type { EnumDescriptorProto as _google_protobuf_EnumDescriptorProto, EnumDescriptorProto__Output as _google_protobuf_EnumDescriptorProto__Output } from '../../google/protobuf/EnumDescriptorProto'; -import type { ServiceDescriptorProto as _google_protobuf_ServiceDescriptorProto, ServiceDescriptorProto__Output as _google_protobuf_ServiceDescriptorProto__Output } from '../../google/protobuf/ServiceDescriptorProto'; -import type { FieldDescriptorProto as _google_protobuf_FieldDescriptorProto, FieldDescriptorProto__Output as _google_protobuf_FieldDescriptorProto__Output } from '../../google/protobuf/FieldDescriptorProto'; -import type { FileOptions as _google_protobuf_FileOptions, FileOptions__Output as _google_protobuf_FileOptions__Output } from '../../google/protobuf/FileOptions'; -import type { SourceCodeInfo as _google_protobuf_SourceCodeInfo, SourceCodeInfo__Output as _google_protobuf_SourceCodeInfo__Output } from '../../google/protobuf/SourceCodeInfo'; +import type { IDescriptorProto as I_google_protobuf_DescriptorProto, ODescriptorProto as O_google_protobuf_DescriptorProto } from '../../google/protobuf/DescriptorProto'; +import type { IEnumDescriptorProto as I_google_protobuf_EnumDescriptorProto, OEnumDescriptorProto as O_google_protobuf_EnumDescriptorProto } from '../../google/protobuf/EnumDescriptorProto'; +import type { IServiceDescriptorProto as I_google_protobuf_ServiceDescriptorProto, OServiceDescriptorProto as O_google_protobuf_ServiceDescriptorProto } from '../../google/protobuf/ServiceDescriptorProto'; +import type { IFieldDescriptorProto as I_google_protobuf_FieldDescriptorProto, OFieldDescriptorProto as O_google_protobuf_FieldDescriptorProto } from '../../google/protobuf/FieldDescriptorProto'; +import type { IFileOptions as I_google_protobuf_FileOptions, OFileOptions as O_google_protobuf_FileOptions } from '../../google/protobuf/FileOptions'; +import type { ISourceCodeInfo as I_google_protobuf_SourceCodeInfo, OSourceCodeInfo as O_google_protobuf_SourceCodeInfo } from '../../google/protobuf/SourceCodeInfo'; -export interface FileDescriptorProto { +export interface IFileDescriptorProto { 'name'?: (string); 'package'?: (string); 'dependency'?: (string)[]; - 'messageType'?: (_google_protobuf_DescriptorProto)[]; - 'enumType'?: (_google_protobuf_EnumDescriptorProto)[]; - 'service'?: (_google_protobuf_ServiceDescriptorProto)[]; - 'extension'?: (_google_protobuf_FieldDescriptorProto)[]; - 'options'?: (_google_protobuf_FileOptions | null); - 'sourceCodeInfo'?: (_google_protobuf_SourceCodeInfo | null); + 'messageType'?: (I_google_protobuf_DescriptorProto)[]; + 'enumType'?: (I_google_protobuf_EnumDescriptorProto)[]; + 'service'?: (I_google_protobuf_ServiceDescriptorProto)[]; + 'extension'?: (I_google_protobuf_FieldDescriptorProto)[]; + 'options'?: (I_google_protobuf_FileOptions | null); + 'sourceCodeInfo'?: (I_google_protobuf_SourceCodeInfo | null); 'publicDependency'?: (number)[]; 'weakDependency'?: (number)[]; 'syntax'?: (string); } -export interface FileDescriptorProto__Output { +export interface OFileDescriptorProto { 'name': (string); 'package': (string); 'dependency': (string)[]; - 'messageType': (_google_protobuf_DescriptorProto__Output)[]; - 'enumType': (_google_protobuf_EnumDescriptorProto__Output)[]; - 'service': (_google_protobuf_ServiceDescriptorProto__Output)[]; - 'extension': (_google_protobuf_FieldDescriptorProto__Output)[]; - 'options': (_google_protobuf_FileOptions__Output | null); - 'sourceCodeInfo': (_google_protobuf_SourceCodeInfo__Output | null); + 'messageType': (O_google_protobuf_DescriptorProto)[]; + 'enumType': (O_google_protobuf_EnumDescriptorProto)[]; + 'service': (O_google_protobuf_ServiceDescriptorProto)[]; + 'extension': (O_google_protobuf_FieldDescriptorProto)[]; + 'options': (O_google_protobuf_FileOptions | null); + 'sourceCodeInfo': (O_google_protobuf_SourceCodeInfo | null); 'publicDependency': (number)[]; 'weakDependency': (number)[]; 'syntax': (string); diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorSet.ts b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorSet.ts index 74ded2471..9c940ed5e 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorSet.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FileDescriptorSet.ts @@ -1,11 +1,11 @@ // Original file: null -import type { FileDescriptorProto as _google_protobuf_FileDescriptorProto, FileDescriptorProto__Output as _google_protobuf_FileDescriptorProto__Output } from '../../google/protobuf/FileDescriptorProto'; +import type { IFileDescriptorProto as I_google_protobuf_FileDescriptorProto, OFileDescriptorProto as O_google_protobuf_FileDescriptorProto } from '../../google/protobuf/FileDescriptorProto'; -export interface FileDescriptorSet { - 'file'?: (_google_protobuf_FileDescriptorProto)[]; +export interface IFileDescriptorSet { + 'file'?: (I_google_protobuf_FileDescriptorProto)[]; } -export interface FileDescriptorSet__Output { - 'file': (_google_protobuf_FileDescriptorProto__Output)[]; +export interface OFileDescriptorSet { + 'file': (O_google_protobuf_FileDescriptorProto)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts index 573e847c0..2b832e0f8 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts @@ -1,6 +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 { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; // Original file: null @@ -10,7 +10,7 @@ export enum _google_protobuf_FileOptions_OptimizeMode { LITE_RUNTIME = 3, } -export interface FileOptions { +export interface IFileOptions { 'javaPackage'?: (string); 'javaOuterClassname'?: (string); 'optimizeFor'?: (_google_protobuf_FileOptions_OptimizeMode | keyof typeof _google_protobuf_FileOptions_OptimizeMode); @@ -25,10 +25,10 @@ export interface FileOptions { 'ccEnableArenas'?: (boolean); 'objcClassPrefix'?: (string); 'csharpNamespace'?: (string); - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; } -export interface FileOptions__Output { +export interface OFileOptions { 'javaPackage': (string); 'javaOuterClassname': (string); 'optimizeFor': (keyof typeof _google_protobuf_FileOptions_OptimizeMode); @@ -43,5 +43,5 @@ export interface FileOptions__Output { 'ccEnableArenas': (boolean); 'objcClassPrefix': (string); 'csharpNamespace': (string); - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/GeneratedCodeInfo.ts b/packages/proto-loader/golden-generated/google/protobuf/GeneratedCodeInfo.ts index 019fb0e15..62f9dc715 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/GeneratedCodeInfo.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/GeneratedCodeInfo.ts @@ -1,24 +1,24 @@ // Original file: null -export interface _google_protobuf_GeneratedCodeInfo_Annotation { +export interface I_google_protobuf_GeneratedCodeInfo_Annotation { 'path'?: (number)[]; 'sourceFile'?: (string); 'begin'?: (number); 'end'?: (number); } -export interface _google_protobuf_GeneratedCodeInfo_Annotation__Output { +export interface O_google_protobuf_GeneratedCodeInfo_Annotation { 'path': (number)[]; 'sourceFile': (string); 'begin': (number); 'end': (number); } -export interface GeneratedCodeInfo { - 'annotation'?: (_google_protobuf_GeneratedCodeInfo_Annotation)[]; +export interface IGeneratedCodeInfo { + 'annotation'?: (I_google_protobuf_GeneratedCodeInfo_Annotation)[]; } -export interface GeneratedCodeInfo__Output { - 'annotation': (_google_protobuf_GeneratedCodeInfo_Annotation__Output)[]; +export interface OGeneratedCodeInfo { + 'annotation': (O_google_protobuf_GeneratedCodeInfo_Annotation)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/MessageOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/MessageOptions.ts index 31f669eb0..8c8885e63 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/MessageOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/MessageOptions.ts @@ -1,19 +1,19 @@ // Original file: null -import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; -export interface MessageOptions { +export interface IMessageOptions { 'messageSetWireFormat'?: (boolean); 'noStandardDescriptorAccessor'?: (boolean); 'deprecated'?: (boolean); 'mapEntry'?: (boolean); - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; } -export interface MessageOptions__Output { +export interface OMessageOptions { 'messageSetWireFormat': (boolean); 'noStandardDescriptorAccessor': (boolean); 'deprecated': (boolean); 'mapEntry': (boolean); - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/MethodDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/MethodDescriptorProto.ts index c76c0ea23..0826370df 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/MethodDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/MethodDescriptorProto.ts @@ -1,21 +1,21 @@ // Original file: null -import type { MethodOptions as _google_protobuf_MethodOptions, MethodOptions__Output as _google_protobuf_MethodOptions__Output } from '../../google/protobuf/MethodOptions'; +import type { IMethodOptions as I_google_protobuf_MethodOptions, OMethodOptions as O_google_protobuf_MethodOptions } from '../../google/protobuf/MethodOptions'; -export interface MethodDescriptorProto { +export interface IMethodDescriptorProto { 'name'?: (string); 'inputType'?: (string); 'outputType'?: (string); - 'options'?: (_google_protobuf_MethodOptions | null); + 'options'?: (I_google_protobuf_MethodOptions | null); 'clientStreaming'?: (boolean); 'serverStreaming'?: (boolean); } -export interface MethodDescriptorProto__Output { +export interface OMethodDescriptorProto { 'name': (string); 'inputType': (string); 'outputType': (string); - 'options': (_google_protobuf_MethodOptions__Output | null); + 'options': (O_google_protobuf_MethodOptions | null); 'clientStreaming': (boolean); 'serverStreaming': (boolean); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/MethodOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/MethodOptions.ts index 7581b9643..5f0b69008 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/MethodOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/MethodOptions.ts @@ -1,21 +1,21 @@ // Original file: null -import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; -import type { OperationInfo as _google_longrunning_OperationInfo, OperationInfo__Output as _google_longrunning_OperationInfo__Output } from '../../google/longrunning/OperationInfo'; -import type { HttpRule as _google_api_HttpRule, HttpRule__Output as _google_api_HttpRule__Output } from '../../google/api/HttpRule'; +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; +import type { IOperationInfo as I_google_longrunning_OperationInfo, OOperationInfo as O_google_longrunning_OperationInfo } from '../../google/longrunning/OperationInfo'; +import type { IHttpRule as I_google_api_HttpRule, OHttpRule as O_google_api_HttpRule } from '../../google/api/HttpRule'; -export interface MethodOptions { +export interface IMethodOptions { 'deprecated'?: (boolean); - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; - '.google.longrunning.operation_info'?: (_google_longrunning_OperationInfo | null); + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; + '.google.longrunning.operation_info'?: (I_google_longrunning_OperationInfo | null); '.google.api.method_signature'?: (string)[]; - '.google.api.http'?: (_google_api_HttpRule | null); + '.google.api.http'?: (I_google_api_HttpRule | null); } -export interface MethodOptions__Output { +export interface OMethodOptions { 'deprecated': (boolean); - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; - '.google.longrunning.operation_info': (_google_longrunning_OperationInfo__Output | null); + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; + '.google.longrunning.operation_info': (O_google_longrunning_OperationInfo | null); '.google.api.method_signature': (string)[]; - '.google.api.http': (_google_api_HttpRule__Output | null); + '.google.api.http': (O_google_api_HttpRule | null); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/OneofDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/OneofDescriptorProto.ts index 636f13ed4..6394270ea 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/OneofDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/OneofDescriptorProto.ts @@ -1,13 +1,13 @@ // Original file: null -import type { OneofOptions as _google_protobuf_OneofOptions, OneofOptions__Output as _google_protobuf_OneofOptions__Output } from '../../google/protobuf/OneofOptions'; +import type { IOneofOptions as I_google_protobuf_OneofOptions, OOneofOptions as O_google_protobuf_OneofOptions } from '../../google/protobuf/OneofOptions'; -export interface OneofDescriptorProto { +export interface IOneofDescriptorProto { 'name'?: (string); - 'options'?: (_google_protobuf_OneofOptions | null); + 'options'?: (I_google_protobuf_OneofOptions | null); } -export interface OneofDescriptorProto__Output { +export interface OOneofDescriptorProto { 'name': (string); - 'options': (_google_protobuf_OneofOptions__Output | null); + 'options': (O_google_protobuf_OneofOptions | null); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/OneofOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/OneofOptions.ts index d81d34797..73280ad73 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/OneofOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/OneofOptions.ts @@ -1,11 +1,11 @@ // Original file: null -import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; -export interface OneofOptions { - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; +export interface IOneofOptions { + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; } -export interface OneofOptions__Output { - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; +export interface OOneofOptions { + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/ServiceDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/ServiceDescriptorProto.ts index 40c9263ea..a0427fda5 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/ServiceDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/ServiceDescriptorProto.ts @@ -1,16 +1,16 @@ // Original file: null -import type { MethodDescriptorProto as _google_protobuf_MethodDescriptorProto, MethodDescriptorProto__Output as _google_protobuf_MethodDescriptorProto__Output } from '../../google/protobuf/MethodDescriptorProto'; -import type { ServiceOptions as _google_protobuf_ServiceOptions, ServiceOptions__Output as _google_protobuf_ServiceOptions__Output } from '../../google/protobuf/ServiceOptions'; +import type { IMethodDescriptorProto as I_google_protobuf_MethodDescriptorProto, OMethodDescriptorProto as O_google_protobuf_MethodDescriptorProto } from '../../google/protobuf/MethodDescriptorProto'; +import type { IServiceOptions as I_google_protobuf_ServiceOptions, OServiceOptions as O_google_protobuf_ServiceOptions } from '../../google/protobuf/ServiceOptions'; -export interface ServiceDescriptorProto { +export interface IServiceDescriptorProto { 'name'?: (string); - 'method'?: (_google_protobuf_MethodDescriptorProto)[]; - 'options'?: (_google_protobuf_ServiceOptions | null); + 'method'?: (I_google_protobuf_MethodDescriptorProto)[]; + 'options'?: (I_google_protobuf_ServiceOptions | null); } -export interface ServiceDescriptorProto__Output { +export interface OServiceDescriptorProto { 'name': (string); - 'method': (_google_protobuf_MethodDescriptorProto__Output)[]; - 'options': (_google_protobuf_ServiceOptions__Output | null); + 'method': (O_google_protobuf_MethodDescriptorProto)[]; + 'options': (O_google_protobuf_ServiceOptions | null); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/ServiceOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/ServiceOptions.ts index c0522eca3..0ddc8e187 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/ServiceOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/ServiceOptions.ts @@ -1,17 +1,17 @@ // Original file: null -import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption'; +import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; -export interface ServiceOptions { +export interface IServiceOptions { 'deprecated'?: (boolean); - 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; + 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; '.google.api.default_host'?: (string); '.google.api.oauth_scopes'?: (string); } -export interface ServiceOptions__Output { +export interface OServiceOptions { 'deprecated': (boolean); - 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; + 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; '.google.api.default_host': (string); '.google.api.oauth_scopes': (string); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/SourceCodeInfo.ts b/packages/proto-loader/golden-generated/google/protobuf/SourceCodeInfo.ts index d30e59b4f..4d0856604 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/SourceCodeInfo.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/SourceCodeInfo.ts @@ -1,7 +1,7 @@ // Original file: null -export interface _google_protobuf_SourceCodeInfo_Location { +export interface I_google_protobuf_SourceCodeInfo_Location { 'path'?: (number)[]; 'span'?: (number)[]; 'leadingComments'?: (string); @@ -9,7 +9,7 @@ export interface _google_protobuf_SourceCodeInfo_Location { 'leadingDetachedComments'?: (string)[]; } -export interface _google_protobuf_SourceCodeInfo_Location__Output { +export interface O_google_protobuf_SourceCodeInfo_Location { 'path': (number)[]; 'span': (number)[]; 'leadingComments': (string); @@ -17,10 +17,10 @@ export interface _google_protobuf_SourceCodeInfo_Location__Output { 'leadingDetachedComments': (string)[]; } -export interface SourceCodeInfo { - 'location'?: (_google_protobuf_SourceCodeInfo_Location)[]; +export interface ISourceCodeInfo { + 'location'?: (I_google_protobuf_SourceCodeInfo_Location)[]; } -export interface SourceCodeInfo__Output { - 'location': (_google_protobuf_SourceCodeInfo_Location__Output)[]; +export interface OSourceCodeInfo { + 'location': (O_google_protobuf_SourceCodeInfo_Location)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/Timestamp.ts b/packages/proto-loader/golden-generated/google/protobuf/Timestamp.ts index ceaa32b5f..06d756134 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/Timestamp.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/Timestamp.ts @@ -2,12 +2,12 @@ import type { Long } from '@grpc/proto-loader'; -export interface Timestamp { +export interface ITimestamp { 'seconds'?: (number | string | Long); 'nanos'?: (number); } -export interface Timestamp__Output { +export interface OTimestamp { 'seconds': (string); 'nanos': (number); } diff --git a/packages/proto-loader/golden-generated/google/protobuf/UninterpretedOption.ts b/packages/proto-loader/golden-generated/google/protobuf/UninterpretedOption.ts index 433820f55..fa0feaf52 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/UninterpretedOption.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/UninterpretedOption.ts @@ -2,18 +2,18 @@ import type { Long } from '@grpc/proto-loader'; -export interface _google_protobuf_UninterpretedOption_NamePart { +export interface I_google_protobuf_UninterpretedOption_NamePart { 'namePart'?: (string); 'isExtension'?: (boolean); } -export interface _google_protobuf_UninterpretedOption_NamePart__Output { +export interface O_google_protobuf_UninterpretedOption_NamePart { 'namePart': (string); 'isExtension': (boolean); } -export interface UninterpretedOption { - 'name'?: (_google_protobuf_UninterpretedOption_NamePart)[]; +export interface IUninterpretedOption { + 'name'?: (I_google_protobuf_UninterpretedOption_NamePart)[]; 'identifierValue'?: (string); 'positiveIntValue'?: (number | string | Long); 'negativeIntValue'?: (number | string | Long); @@ -22,8 +22,8 @@ export interface UninterpretedOption { 'aggregateValue'?: (string); } -export interface UninterpretedOption__Output { - 'name': (_google_protobuf_UninterpretedOption_NamePart__Output)[]; +export interface OUninterpretedOption { + 'name': (O_google_protobuf_UninterpretedOption_NamePart)[]; 'identifierValue': (string); 'positiveIntValue': (string); 'negativeIntValue': (string); diff --git a/packages/proto-loader/golden-generated/google/rpc/Status.ts b/packages/proto-loader/golden-generated/google/rpc/Status.ts index 4ce45b6a9..05cf71c5f 100644 --- a/packages/proto-loader/golden-generated/google/rpc/Status.ts +++ b/packages/proto-loader/golden-generated/google/rpc/Status.ts @@ -1,6 +1,6 @@ // Original file: deps/googleapis/google/rpc/status.proto -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../google/protobuf/Any'; +import type { IAny as I_google_protobuf_Any, OAny as O_google_protobuf_Any } from '../../google/protobuf/Any'; /** * The `Status` type defines a logical error model that is suitable for @@ -11,7 +11,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ * You can find out more about this error model and how to work with it in the * [API Design Guide](https://cloud.google.com/apis/design/errors). */ -export interface Status { +export interface IStatus { /** * The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. */ @@ -26,7 +26,7 @@ export interface Status { * A list of messages that carry the error details. There is a common set of * message types for APIs to use. */ - 'details'?: (_google_protobuf_Any)[]; + 'details'?: (I_google_protobuf_Any)[]; } /** @@ -38,7 +38,7 @@ export interface Status { * You can find out more about this error model and how to work with it in the * [API Design Guide](https://cloud.google.com/apis/design/errors). */ -export interface Status__Output { +export interface OStatus { /** * The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. */ @@ -53,5 +53,5 @@ export interface Status__Output { * A list of messages that carry the error details. There is a common set of * message types for APIs to use. */ - 'details': (_google_protobuf_Any__Output)[]; + 'details': (O_google_protobuf_Any)[]; } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockRequest.ts index 383c409c5..29d10f6dd 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockRequest.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockRequest.ts @@ -1,45 +1,45 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; -import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../google/rpc/Status'; -import type { BlockResponse as _google_showcase_v1beta1_BlockResponse, BlockResponse__Output as _google_showcase_v1beta1_BlockResponse__Output } from '../../../google/showcase/v1beta1/BlockResponse'; +import type { IDuration as I_google_protobuf_Duration, ODuration as O_google_protobuf_Duration } from '../../../google/protobuf/Duration'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; +import type { IBlockResponse as I_google_showcase_v1beta1_BlockResponse, OBlockResponse as O_google_showcase_v1beta1_BlockResponse } from '../../../google/showcase/v1beta1/BlockResponse'; /** * The request for Block method. */ -export interface BlockRequest { +export interface IBlockRequest { /** * The amount of time to block before returning a response. */ - 'response_delay'?: (_google_protobuf_Duration | null); + 'response_delay'?: (I_google_protobuf_Duration | null); /** * The error that will be returned by the server. If this code is specified * to be the OK rpc code, an empty response will be returned. */ - 'error'?: (_google_rpc_Status | null); + 'error'?: (I_google_rpc_Status | null); /** * The response to be returned that will signify successful method call. */ - 'success'?: (_google_showcase_v1beta1_BlockResponse | null); + 'success'?: (I_google_showcase_v1beta1_BlockResponse | null); 'response'?: "error"|"success"; } /** * The request for Block method. */ -export interface BlockRequest__Output { +export interface OBlockRequest { /** * The amount of time to block before returning a response. */ - 'response_delay': (_google_protobuf_Duration__Output | null); + 'response_delay': (O_google_protobuf_Duration | null); /** * The error that will be returned by the server. If this code is specified * to be the OK rpc code, an empty response will be returned. */ - 'error'?: (_google_rpc_Status__Output | null); + 'error'?: (O_google_rpc_Status | null); /** * The response to be returned that will signify successful method call. */ - 'success'?: (_google_showcase_v1beta1_BlockResponse__Output | null); + 'success'?: (O_google_showcase_v1beta1_BlockResponse | null); 'response': "error"|"success"; } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockResponse.ts index 5634b19d4..3bb9bddf2 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockResponse.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/BlockResponse.ts @@ -4,7 +4,7 @@ /** * The response for Block method. */ -export interface BlockResponse { +export interface IBlockResponse { /** * This content can contain anything, the server will not depend on a value * here. @@ -15,7 +15,7 @@ export interface BlockResponse { /** * The response for Block method. */ -export interface BlockResponse__Output { +export interface OBlockResponse { /** * This content can contain anything, the server will not depend on a value * here. diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts index 30ecc8e23..a0330fe68 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Echo.ts @@ -2,15 +2,15 @@ import type * as grpc from '@grpc/grpc-js' import type { MethodDefinition } from '@grpc/proto-loader' -import type { BlockRequest as _google_showcase_v1beta1_BlockRequest, BlockRequest__Output as _google_showcase_v1beta1_BlockRequest__Output } from '../../../google/showcase/v1beta1/BlockRequest'; -import type { BlockResponse as _google_showcase_v1beta1_BlockResponse, BlockResponse__Output as _google_showcase_v1beta1_BlockResponse__Output } from '../../../google/showcase/v1beta1/BlockResponse'; -import type { EchoRequest as _google_showcase_v1beta1_EchoRequest, EchoRequest__Output as _google_showcase_v1beta1_EchoRequest__Output } from '../../../google/showcase/v1beta1/EchoRequest'; -import type { EchoResponse as _google_showcase_v1beta1_EchoResponse, EchoResponse__Output as _google_showcase_v1beta1_EchoResponse__Output } from '../../../google/showcase/v1beta1/EchoResponse'; -import type { ExpandRequest as _google_showcase_v1beta1_ExpandRequest, ExpandRequest__Output as _google_showcase_v1beta1_ExpandRequest__Output } from '../../../google/showcase/v1beta1/ExpandRequest'; -import type { Operation as _google_longrunning_Operation, Operation__Output as _google_longrunning_Operation__Output } from '../../../google/longrunning/Operation'; -import type { PagedExpandRequest as _google_showcase_v1beta1_PagedExpandRequest, PagedExpandRequest__Output as _google_showcase_v1beta1_PagedExpandRequest__Output } from '../../../google/showcase/v1beta1/PagedExpandRequest'; -import type { PagedExpandResponse as _google_showcase_v1beta1_PagedExpandResponse, PagedExpandResponse__Output as _google_showcase_v1beta1_PagedExpandResponse__Output } from '../../../google/showcase/v1beta1/PagedExpandResponse'; -import type { WaitRequest as _google_showcase_v1beta1_WaitRequest, WaitRequest__Output as _google_showcase_v1beta1_WaitRequest__Output } from '../../../google/showcase/v1beta1/WaitRequest'; +import type { IBlockRequest as I_google_showcase_v1beta1_BlockRequest, OBlockRequest as O_google_showcase_v1beta1_BlockRequest } from '../../../google/showcase/v1beta1/BlockRequest'; +import type { IBlockResponse as I_google_showcase_v1beta1_BlockResponse, OBlockResponse as O_google_showcase_v1beta1_BlockResponse } from '../../../google/showcase/v1beta1/BlockResponse'; +import type { IEchoRequest as I_google_showcase_v1beta1_EchoRequest, OEchoRequest as O_google_showcase_v1beta1_EchoRequest } from '../../../google/showcase/v1beta1/EchoRequest'; +import type { IEchoResponse as I_google_showcase_v1beta1_EchoResponse, OEchoResponse as O_google_showcase_v1beta1_EchoResponse } from '../../../google/showcase/v1beta1/EchoResponse'; +import type { IExpandRequest as I_google_showcase_v1beta1_ExpandRequest, OExpandRequest as O_google_showcase_v1beta1_ExpandRequest } from '../../../google/showcase/v1beta1/ExpandRequest'; +import type { IOperation as I_google_longrunning_Operation, OOperation as O_google_longrunning_Operation } from '../../../google/longrunning/Operation'; +import type { IPagedExpandRequest as I_google_showcase_v1beta1_PagedExpandRequest, OPagedExpandRequest as O_google_showcase_v1beta1_PagedExpandRequest } from '../../../google/showcase/v1beta1/PagedExpandRequest'; +import type { IPagedExpandResponse as I_google_showcase_v1beta1_PagedExpandResponse, OPagedExpandResponse as O_google_showcase_v1beta1_PagedExpandResponse } from '../../../google/showcase/v1beta1/PagedExpandResponse'; +import type { IWaitRequest as I_google_showcase_v1beta1_WaitRequest, OWaitRequest as O_google_showcase_v1beta1_WaitRequest } from '../../../google/showcase/v1beta1/WaitRequest'; /** * This service is used showcase the four main types of rpcs - unary, server @@ -25,115 +25,115 @@ export interface EchoClient extends grpc.Client { * and then return the response or error. * This method showcases how a client handles delays or retries. */ - Block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; - Block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; - Block(argument: _google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; - Block(argument: _google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + Block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Block(argument: I_google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Block(argument: I_google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * This method will block (wait) for the requested amount of time * and then return the response or error. * This method showcases how a client handles delays or retries. */ - block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; - block(argument: _google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; - block(argument: _google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; - block(argument: _google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_BlockResponse__Output>): grpc.ClientUnaryCall; + block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + block(argument: I_google_showcase_v1beta1_BlockRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + block(argument: I_google_showcase_v1beta1_BlockRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + block(argument: I_google_showcase_v1beta1_BlockRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * This method, upon receiving a request on the stream, the same content will * be passed back on the stream. This method showcases bidirectional * streaming rpcs. */ - Chat(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse__Output>; - Chat(options?: grpc.CallOptions): grpc.ClientDuplexStream<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse__Output>; + Chat(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream; + Chat(options?: grpc.CallOptions): grpc.ClientDuplexStream; /** * This method, upon receiving a request on the stream, the same content will * be passed back on the stream. This method showcases bidirectional * streaming rpcs. */ - chat(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse__Output>; - chat(options?: grpc.CallOptions): grpc.ClientDuplexStream<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse__Output>; + chat(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream; + chat(options?: grpc.CallOptions): grpc.ClientDuplexStream; /** * This method will collect the words given to it. When the stream is closed * by the client, this method will return the a concatenation of the strings * passed to it. This method showcases client-side streaming rpcs. */ - Collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - Collect(metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - Collect(options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - Collect(callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + Collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + Collect(metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientWritableStream; + Collect(options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + Collect(callback: grpc.requestCallback): grpc.ClientWritableStream; /** * This method will collect the words given to it. When the stream is closed * by the client, this method will return the a concatenation of the strings * passed to it. This method showcases client-side streaming rpcs. */ - collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - collect(metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - collect(options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; - collect(callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientWritableStream<_google_showcase_v1beta1_EchoRequest>; + collect(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + collect(metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientWritableStream; + collect(options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientWritableStream; + collect(callback: grpc.requestCallback): grpc.ClientWritableStream; /** * This method simply echos the request. This method is showcases unary rpcs. */ - Echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; - Echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; - Echo(argument: _google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; - Echo(argument: _google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Echo(argument: I_google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Echo(argument: I_google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * This method simply echos the request. This method is showcases unary rpcs. */ - echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; - echo(argument: _google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; - echo(argument: _google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; - echo(argument: _google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + echo(argument: I_google_showcase_v1beta1_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + echo(argument: I_google_showcase_v1beta1_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + echo(argument: I_google_showcase_v1beta1_EchoRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * This method split the given content into words and will pass each word back * through the stream. This method showcases server-side streaming rpcs. */ - Expand(argument: _google_showcase_v1beta1_ExpandRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_google_showcase_v1beta1_EchoResponse__Output>; - Expand(argument: _google_showcase_v1beta1_ExpandRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_google_showcase_v1beta1_EchoResponse__Output>; + Expand(argument: I_google_showcase_v1beta1_ExpandRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream; + Expand(argument: I_google_showcase_v1beta1_ExpandRequest, options?: grpc.CallOptions): grpc.ClientReadableStream; /** * This method split the given content into words and will pass each word back * through the stream. This method showcases server-side streaming rpcs. */ - expand(argument: _google_showcase_v1beta1_ExpandRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_google_showcase_v1beta1_EchoResponse__Output>; - expand(argument: _google_showcase_v1beta1_ExpandRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_google_showcase_v1beta1_EchoResponse__Output>; + expand(argument: I_google_showcase_v1beta1_ExpandRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream; + expand(argument: I_google_showcase_v1beta1_ExpandRequest, options?: grpc.CallOptions): grpc.ClientReadableStream; /** * This is similar to the Expand method but instead of returning a stream of * expanded words, this method returns a paged list of expanded words. */ - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; - PagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + PagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * This is similar to the Expand method but instead of returning a stream of * expanded words, this method returns a paged list of expanded words. */ - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; - pagedExpand(argument: _google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback<_google_showcase_v1beta1_PagedExpandResponse__Output>): grpc.ClientUnaryCall; + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + pagedExpand(argument: I_google_showcase_v1beta1_PagedExpandRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * This method will wait the requested amount of and then return. * This method showcases how a client handles a request timing out. */ - Wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - Wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - Wait(argument: _google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - Wait(argument: _google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + Wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Wait(argument: I_google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + Wait(argument: I_google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; /** * This method will wait the requested amount of and then return. * This method showcases how a client handles a request timing out. */ - wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - wait(argument: _google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - wait(argument: _google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; - wait(argument: _google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback<_google_longrunning_Operation__Output>): grpc.ClientUnaryCall; + wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + wait(argument: I_google_showcase_v1beta1_WaitRequest, metadata: grpc.Metadata, callback: grpc.requestCallback): grpc.ClientUnaryCall; + wait(argument: I_google_showcase_v1beta1_WaitRequest, options: grpc.CallOptions, callback: grpc.requestCallback): grpc.ClientUnaryCall; + wait(argument: I_google_showcase_v1beta1_WaitRequest, callback: grpc.requestCallback): grpc.ClientUnaryCall; } @@ -150,53 +150,53 @@ export interface EchoHandlers extends grpc.UntypedServiceImplementation { * and then return the response or error. * This method showcases how a client handles delays or retries. */ - Block: grpc.handleUnaryCall<_google_showcase_v1beta1_BlockRequest__Output, _google_showcase_v1beta1_BlockResponse>; + Block: grpc.handleUnaryCall; /** * This method, upon receiving a request on the stream, the same content will * be passed back on the stream. This method showcases bidirectional * streaming rpcs. */ - Chat: grpc.handleBidiStreamingCall<_google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse>; + Chat: grpc.handleBidiStreamingCall; /** * This method will collect the words given to it. When the stream is closed * by the client, this method will return the a concatenation of the strings * passed to it. This method showcases client-side streaming rpcs. */ - Collect: grpc.handleClientStreamingCall<_google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse>; + Collect: grpc.handleClientStreamingCall; /** * This method simply echos the request. This method is showcases unary rpcs. */ - Echo: grpc.handleUnaryCall<_google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse>; + Echo: grpc.handleUnaryCall; /** * This method split the given content into words and will pass each word back * through the stream. This method showcases server-side streaming rpcs. */ - Expand: grpc.handleServerStreamingCall<_google_showcase_v1beta1_ExpandRequest__Output, _google_showcase_v1beta1_EchoResponse>; + Expand: grpc.handleServerStreamingCall; /** * This is similar to the Expand method but instead of returning a stream of * expanded words, this method returns a paged list of expanded words. */ - PagedExpand: grpc.handleUnaryCall<_google_showcase_v1beta1_PagedExpandRequest__Output, _google_showcase_v1beta1_PagedExpandResponse>; + PagedExpand: grpc.handleUnaryCall; /** * This method will wait the requested amount of and then return. * This method showcases how a client handles a request timing out. */ - Wait: grpc.handleUnaryCall<_google_showcase_v1beta1_WaitRequest__Output, _google_longrunning_Operation>; + Wait: grpc.handleUnaryCall; } export interface EchoDefinition extends grpc.ServiceDefinition { - Block: MethodDefinition<_google_showcase_v1beta1_BlockRequest, _google_showcase_v1beta1_BlockResponse, _google_showcase_v1beta1_BlockRequest__Output, _google_showcase_v1beta1_BlockResponse__Output> - Chat: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> - Collect: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> - Echo: MethodDefinition<_google_showcase_v1beta1_EchoRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_EchoRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> - Expand: MethodDefinition<_google_showcase_v1beta1_ExpandRequest, _google_showcase_v1beta1_EchoResponse, _google_showcase_v1beta1_ExpandRequest__Output, _google_showcase_v1beta1_EchoResponse__Output> - PagedExpand: MethodDefinition<_google_showcase_v1beta1_PagedExpandRequest, _google_showcase_v1beta1_PagedExpandResponse, _google_showcase_v1beta1_PagedExpandRequest__Output, _google_showcase_v1beta1_PagedExpandResponse__Output> - Wait: MethodDefinition<_google_showcase_v1beta1_WaitRequest, _google_longrunning_Operation, _google_showcase_v1beta1_WaitRequest__Output, _google_longrunning_Operation__Output> + Block: MethodDefinition + Chat: MethodDefinition + Collect: MethodDefinition + Echo: MethodDefinition + Expand: MethodDefinition + PagedExpand: MethodDefinition + Wait: MethodDefinition } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts index fb2bb67d3..649a5d505 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts @@ -1,6 +1,6 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto -import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../google/rpc/Status'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; import type { Severity as _google_showcase_v1beta1_Severity } from '../../../google/showcase/v1beta1/Severity'; /** @@ -9,7 +9,7 @@ import type { Severity as _google_showcase_v1beta1_Severity } from '../../../goo * If status is set in this message * then the status will be returned as an error. */ -export interface EchoRequest { +export interface IEchoRequest { /** * The content to be echoed by the server. */ @@ -17,7 +17,7 @@ export interface EchoRequest { /** * The error to be thrown by the server. */ - 'error'?: (_google_rpc_Status | null); + 'error'?: (I_google_rpc_Status | null); /** * The severity to be echoed by the server. */ @@ -31,7 +31,7 @@ export interface EchoRequest { * If status is set in this message * then the status will be returned as an error. */ -export interface EchoRequest__Output { +export interface OEchoRequest { /** * The content to be echoed by the server. */ @@ -39,7 +39,7 @@ export interface EchoRequest__Output { /** * The error to be thrown by the server. */ - 'error'?: (_google_rpc_Status__Output | null); + 'error'?: (O_google_rpc_Status | null); /** * The severity to be echoed by the server. */ diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts index 3fda238a1..96b7ba25d 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts @@ -5,7 +5,7 @@ import type { Severity as _google_showcase_v1beta1_Severity } from '../../../goo /** * The response message for the Echo methods. */ -export interface EchoResponse { +export interface IEchoResponse { /** * The content specified in the request. */ @@ -19,7 +19,7 @@ export interface EchoResponse { /** * The response message for the Echo methods. */ -export interface EchoResponse__Output { +export interface OEchoResponse { /** * The content specified in the request. */ diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/ExpandRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/ExpandRequest.ts index 33ce73c1f..4347a617a 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/ExpandRequest.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/ExpandRequest.ts @@ -1,11 +1,11 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto -import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../google/rpc/Status'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; /** * The request message for the Expand method. */ -export interface ExpandRequest { +export interface IExpandRequest { /** * The content that will be split into words and returned on the stream. */ @@ -13,13 +13,13 @@ export interface ExpandRequest { /** * The error that is thrown after all words are sent on the stream. */ - 'error'?: (_google_rpc_Status | null); + 'error'?: (I_google_rpc_Status | null); } /** * The request message for the Expand method. */ -export interface ExpandRequest__Output { +export interface OExpandRequest { /** * The content that will be split into words and returned on the stream. */ @@ -27,5 +27,5 @@ export interface ExpandRequest__Output { /** * The error that is thrown after all words are sent on the stream. */ - 'error': (_google_rpc_Status__Output | null); + 'error': (O_google_rpc_Status | null); } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandRequest.ts index 13c945134..8c68ba990 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandRequest.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandRequest.ts @@ -4,7 +4,7 @@ /** * The request for the PagedExpand method. */ -export interface PagedExpandRequest { +export interface IPagedExpandRequest { /** * The string to expand. */ @@ -22,7 +22,7 @@ export interface PagedExpandRequest { /** * The request for the PagedExpand method. */ -export interface PagedExpandRequest__Output { +export interface OPagedExpandRequest { /** * The string to expand. */ diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandResponse.ts index 823de43ed..3b3ef90c2 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandResponse.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/PagedExpandResponse.ts @@ -1,15 +1,15 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto -import type { EchoResponse as _google_showcase_v1beta1_EchoResponse, EchoResponse__Output as _google_showcase_v1beta1_EchoResponse__Output } from '../../../google/showcase/v1beta1/EchoResponse'; +import type { IEchoResponse as I_google_showcase_v1beta1_EchoResponse, OEchoResponse as O_google_showcase_v1beta1_EchoResponse } from '../../../google/showcase/v1beta1/EchoResponse'; /** * The response for the PagedExpand method. */ -export interface PagedExpandResponse { +export interface IPagedExpandResponse { /** * The words that were expanded. */ - 'responses'?: (_google_showcase_v1beta1_EchoResponse)[]; + 'responses'?: (I_google_showcase_v1beta1_EchoResponse)[]; /** * The next page token. */ @@ -19,11 +19,11 @@ export interface PagedExpandResponse { /** * The response for the PagedExpand method. */ -export interface PagedExpandResponse__Output { +export interface OPagedExpandResponse { /** * The words that were expanded. */ - 'responses': (_google_showcase_v1beta1_EchoResponse__Output)[]; + 'responses': (O_google_showcase_v1beta1_EchoResponse)[]; /** * The next page token. */ diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitMetadata.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitMetadata.ts index 5f17b4457..ddbe77c22 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitMetadata.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitMetadata.ts @@ -1,23 +1,23 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto -import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { ITimestamp as I_google_protobuf_Timestamp, OTimestamp as O_google_protobuf_Timestamp } from '../../../google/protobuf/Timestamp'; /** * The metadata for Wait operation. */ -export interface WaitMetadata { +export interface IWaitMetadata { /** * The time that this operation will complete. */ - 'end_time'?: (_google_protobuf_Timestamp | null); + 'end_time'?: (I_google_protobuf_Timestamp | null); } /** * The metadata for Wait operation. */ -export interface WaitMetadata__Output { +export interface OWaitMetadata { /** * The time that this operation will complete. */ - 'end_time': (_google_protobuf_Timestamp__Output | null); + 'end_time': (O_google_protobuf_Timestamp | null); } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitRequest.ts index 46c095b65..331a66947 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitRequest.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitRequest.ts @@ -1,31 +1,31 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto -import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; -import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../google/rpc/Status'; -import type { WaitResponse as _google_showcase_v1beta1_WaitResponse, WaitResponse__Output as _google_showcase_v1beta1_WaitResponse__Output } from '../../../google/showcase/v1beta1/WaitResponse'; -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../google/protobuf/Duration'; +import type { ITimestamp as I_google_protobuf_Timestamp, OTimestamp as O_google_protobuf_Timestamp } from '../../../google/protobuf/Timestamp'; +import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; +import type { IWaitResponse as I_google_showcase_v1beta1_WaitResponse, OWaitResponse as O_google_showcase_v1beta1_WaitResponse } from '../../../google/showcase/v1beta1/WaitResponse'; +import type { IDuration as I_google_protobuf_Duration, ODuration as O_google_protobuf_Duration } from '../../../google/protobuf/Duration'; /** * The request for Wait method. */ -export interface WaitRequest { +export interface IWaitRequest { /** * The time that this operation will complete. */ - 'end_time'?: (_google_protobuf_Timestamp | null); + 'end_time'?: (I_google_protobuf_Timestamp | null); /** * The error that will be returned by the server. If this code is specified * to be the OK rpc code, an empty response will be returned. */ - 'error'?: (_google_rpc_Status | null); + 'error'?: (I_google_rpc_Status | null); /** * The response to be returned on operation completion. */ - 'success'?: (_google_showcase_v1beta1_WaitResponse | null); + 'success'?: (I_google_showcase_v1beta1_WaitResponse | null); /** * The duration of this operation. */ - 'ttl'?: (_google_protobuf_Duration | null); + 'ttl'?: (I_google_protobuf_Duration | null); 'end'?: "end_time"|"ttl"; 'response'?: "error"|"success"; } @@ -33,24 +33,24 @@ export interface WaitRequest { /** * The request for Wait method. */ -export interface WaitRequest__Output { +export interface OWaitRequest { /** * The time that this operation will complete. */ - 'end_time'?: (_google_protobuf_Timestamp__Output | null); + 'end_time'?: (O_google_protobuf_Timestamp | null); /** * The error that will be returned by the server. If this code is specified * to be the OK rpc code, an empty response will be returned. */ - 'error'?: (_google_rpc_Status__Output | null); + 'error'?: (O_google_rpc_Status | null); /** * The response to be returned on operation completion. */ - 'success'?: (_google_showcase_v1beta1_WaitResponse__Output | null); + 'success'?: (O_google_showcase_v1beta1_WaitResponse | null); /** * The duration of this operation. */ - 'ttl'?: (_google_protobuf_Duration__Output | null); + 'ttl'?: (O_google_protobuf_Duration | null); 'end': "end_time"|"ttl"; 'response': "error"|"success"; } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitResponse.ts index 84b804f6b..667b450e2 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitResponse.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/WaitResponse.ts @@ -4,7 +4,7 @@ /** * The result of the Wait operation. */ -export interface WaitResponse { +export interface IWaitResponse { /** * This content of the result. */ @@ -14,7 +14,7 @@ export interface WaitResponse { /** * The result of the Wait operation. */ -export interface WaitResponse__Output { +export interface OWaitResponse { /** * This content of the result. */ diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 97e006912..c9cf35b1e 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -24,7 +24,7 @@ "fix": "gts fix", "pretest": "npm run compile", "posttest": "npm run check", - "generate-golden": "node ./build/bin/proto-loader-gen-types.js --keepCase --longs=String --enums=String --defaults --oneofs --json --includeComments -I deps/gapic-showcase/schema/ deps/googleapis/ -O ./golden-generated --grpcLib @grpc/grpc-js google/showcase/v1beta1/echo.proto", + "generate-golden": "node ./build/bin/proto-loader-gen-types.js --keepCase --longs=String --enums=String --defaults --oneofs --json --includeComments --inputTemplate=I%s --outputTemplate=O%s -I deps/gapic-showcase/schema/ deps/googleapis/ -O ./golden-generated --grpcLib @grpc/grpc-js google/showcase/v1beta1/echo.proto", "validate-golden": "rm -rf ./golden-generated-old && mv ./golden-generated/ ./golden-generated-old && npm run generate-golden && diff -rb ./golden-generated ./golden-generated-old" }, "repository": { From d8022a557d978307ebe5a413703f3660172e2661 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 7 Sep 2022 11:16:12 -0700 Subject: [PATCH 320/694] grpc-js-xds: Enable outlier detection by default --- packages/grpc-js-xds/src/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index b8b518da4..250f791ac 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -16,4 +16,4 @@ */ export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; -export const EXPERIMENTAL_OUTLIER_DETECTION = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION === 'true'; \ No newline at end of file +export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; \ No newline at end of file From 3c27ed4c0012f110e55240c7113f4333ea0f6fc4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 7 Sep 2022 12:39:39 -0700 Subject: [PATCH 321/694] grpc-js: Update grpc-js outlier detection check to match xds check --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 685cfc60d..52a9bfc95 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -38,7 +38,7 @@ function trace(text: string): void { const TYPE_NAME = 'outlier_detection'; -const OUTLIER_DETECTION_ENABLED = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION !== 'false'; +const OUTLIER_DETECTION_ENABLED = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; export interface SuccessRateEjectionConfig { readonly stdev_factor: number; From 51de24ac0ced9a55a714b2c894b0b5f103619761 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 7 Sep 2022 13:11:14 -0700 Subject: [PATCH 322/694] grpc-js: Bump to 1.7.0 --- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 9062403cd..fcb9279f0 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.6.1", + "version": "1.7.0", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -47,7 +47,7 @@ "re2-wasm": "^1.0.1" }, "peerDependencies": { - "@grpc/grpc-js": "~1.6.0" + "@grpc/grpc-js": "~1.7.0" }, "engines": { "node": ">=10.10.0" diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 8ca6eb1c6..d5e5e6927 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.6.12", + "version": "1.7.0", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 4a861a0d4b84931bd9ff5c16072bb0a496d482dc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 7 Sep 2022 13:13:50 -0700 Subject: [PATCH 323/694] grpc-js-xds: Update outlier detection entry in README --- packages/grpc-js-xds/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index 20bd924a8..bbdd98863 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -28,4 +28,4 @@ const client = new MyServiceClient('xds:///example.com:123'); - [xDS Circuit Breaking](https://github.com/grpc/proposal/blob/master/A32-xds-circuit-breaking.md) - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) - - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) (experimental, disabled by default, enabled by setting the environment variable `GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION=true`) \ No newline at end of file + - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) \ No newline at end of file From f438191182c5346bc998be1dcaf2263be237f274 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 7 Sep 2022 15:59:17 -0700 Subject: [PATCH 324/694] grpc-js: Add tests for outlier detection validation rules --- .../grpc-js/test/test-outlier-detection.ts | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts index 977a3058f..74e536767 100644 --- a/packages/grpc-js/test/test-outlier-detection.ts +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -19,6 +19,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as grpc from '../src'; import { loadProtoFile } from './common'; +import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection' function multiDone(done: Mocha.Done, target: number) { let count = 0; @@ -67,6 +68,251 @@ const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); const EchoService = loadProtoFile(protoFile) .EchoService as grpc.ServiceClientConstructor; +describe('Outlier detection config validation', () => { + describe('interval', () => { + it('Should reject a negative interval', () => { + const loadBalancingConfig = { + interval: { + seconds: -1, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large interval', () => { + const loadBalancingConfig = { + interval: { + seconds: 1e12, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a negative interval.nanos', () => { + const loadBalancingConfig = { + interval: { + seconds: 0, + nanos: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large interval.nanos', () => { + const loadBalancingConfig = { + interval: { + seconds: 0, + nanos: 1e12 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /interval parse error: values out of range for non-negative Duaration/); + }); + }); + describe('base_ejection_time', () => { + it('Should reject a negative base_ejection_time', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: -1, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large base_ejection_time', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: 1e12, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a negative base_ejection_time.nanos', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: 0, + nanos: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large base_ejection_time.nanos', () => { + const loadBalancingConfig = { + base_ejection_time: { + seconds: 0, + nanos: 1e12 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /base_ejection_time parse error: values out of range for non-negative Duaration/); + }); + }); + describe('max_ejection_time', () => { + it('Should reject a negative max_ejection_time', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: -1, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large max_ejection_time', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: 1e12, + nanos: 0 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a negative max_ejection_time.nanos', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: 0, + nanos: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + it('Should reject a large max_ejection_time.nanos', () => { + const loadBalancingConfig = { + max_ejection_time: { + seconds: 0, + nanos: 1e12 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_time parse error: values out of range for non-negative Duaration/); + }); + }); + describe('max_ejection_percent', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + max_ejection_percent: 101, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_percent parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + max_ejection_percent: -1, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /max_ejection_percent parse error: value out of range for percentage/); + }); + }); + describe('success_rate_ejection.enforcement_percentage', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + success_rate_ejection: { + enforcement_percentage: 101 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /success_rate_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + success_rate_ejection: { + enforcement_percentage: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /success_rate_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + }); + describe('failure_percentage_ejection.threshold', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + threshold: 101 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.threshold parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + threshold: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.threshold parse error: value out of range for percentage/); + }); + }); + describe('failure_percentage_ejection.enforcement_percentage', () => { + it('Should reject a value above 100', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + enforcement_percentage: 101 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + it('Should reject a negative value', () => { + const loadBalancingConfig = { + failure_percentage_ejection: { + enforcement_percentage: -1 + }, + child_policy: [{round_robin: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /failure_percentage_ejection\.enforcement_percentage parse error: value out of range for percentage/); + }); + }); +}); + describe('Outlier detection', () => { const GOOD_PORTS = 4; let goodServer: grpc.Server; From b0e28f7f939f4989cbb4ac400accd1cf7db8319f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 12 Sep 2022 11:20:19 -0700 Subject: [PATCH 325/694] grpc-js: Add test for sending metadata from call creds on channel creds --- .../grpc-js/test/test-channel-credentials.ts | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/test/test-channel-credentials.ts b/packages/grpc-js/test/test-channel-credentials.ts index d6028f469..2b537ac97 100644 --- a/packages/grpc-js/test/test-channel-credentials.ts +++ b/packages/grpc-js/test/test-channel-credentials.ts @@ -17,12 +17,22 @@ import * as assert from 'assert'; import * as fs from 'fs'; +import * as path from 'path'; import { promisify } from 'util'; +import * as protoLoader from '@grpc/proto-loader'; import { CallCredentials } from '../src/call-credentials'; import { ChannelCredentials } from '../src/channel-credentials'; +import * as grpc from '../src'; +import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; +import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; +import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; -import { assert2, mockFunction } from './common'; +import { assert2, loadProtoFile, mockFunction } from './common'; +import { sendUnaryData, ServerUnaryCall, ServiceError } from '../src'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = loadProtoFile(protoFile).EchoService as ServiceClientConstructor; class CallCredentialsMock implements CallCredentials { child: CallCredentialsMock | null = null; @@ -138,3 +148,65 @@ describe('ChannelCredentials Implementation', () => { }); }); }); + +describe('ChannelCredentials usage', () => { + let client: ServiceClient; + let server: grpc.Server; + before(async () => { + const {ca, key, cert} = await pFixtures; + const serverCreds = grpc.ServerCredentials.createSsl(null, [{private_key: key, cert_chain: cert}]); + const channelCreds = ChannelCredentials.createSsl(ca); + const callCreds = CallCredentials.createFromMetadataGenerator((options, cb) => { + const metadata = new grpc.Metadata(); + metadata.set('test-key', 'test-value'); + cb(null, metadata); + }); + const combinedCreds = channelCreds.compose(callCreds); + return new Promise((resolve, reject) => { + + server = new grpc.Server(); + server.addService(echoService.service, { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + call.sendMetadata(call.metadata); + callback(null, call.request); + }, + }); + + server.bindAsync( + 'localhost:0', + serverCreds, + (err, port) => { + if (err) { + reject(err); + return; + } + client = new echoService( + `localhost:${port}`, + combinedCreds, + {'grpc.ssl_target_name_override': 'foo.test.google.fr', 'grpc.default_authority': 'foo.test.google.fr'} + ); + server.start(); + resolve(); + } + ); + }); + }); + after(() => { + server.forceShutdown(); + }); + + it('Should send the metadata from call credentials attached to channel credentials', (done) => { + const call = client.echo( + { value: 'test value', value2: 3 }, + assert2.mustCall((error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + }) + ); + call.on('metadata', assert2.mustCall((metadata: grpc.Metadata) => { + assert.deepStrictEqual(metadata.get('test-key'), ['test-value']); + + })); + assert2.afterMustCallsSatisfied(done); + }); +}); \ No newline at end of file From 9269f3a76f7c72d643ef5ad0db1261da08045bf1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 12 Sep 2022 11:46:06 -0700 Subject: [PATCH 326/694] grpc-js: Restrict control-plane status codes --- packages/grpc-js/src/channel.ts | 47 ++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 88bf3a7e0..53b80c1cf 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -68,6 +68,28 @@ function getNewCallNumber(): number { return callNumber; } +const INAPPPROPRIATE_CONTROL_PLANE_CODES: Status[] = [ + Status.OK, + Status.INVALID_ARGUMENT, + Status.NOT_FOUND, + Status.ALREADY_EXISTS, + Status.FAILED_PRECONDITION, + Status.ABORTED, + Status.OUT_OF_RANGE, + Status.DATA_LOSS +] + +function restrictControlPlaneStatusCode(code: Status, details: string): {code: Status, details: string} { + if (INAPPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) { + return { + code: Status.INTERNAL, + details: `Invalid status from control plane: ${code} ${Status[code]} ${details}` + } + } else { + return {code, details}; + } +} + /** * An interface that represents a communication channel to a server specified * by a given address. @@ -320,7 +342,7 @@ export class ChannelImplementation implements Channel { this.trace('Name resolution failed with calls queued for config selection'); } if (this.configSelector === null) { - this.currentResolutionError = status; + this.currentResolutionError = {...restrictControlPlaneStatusCode(status.code, status.details), metadata: status.metadata}; } const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; @@ -534,10 +556,11 @@ export class ChannelImplementation implements Channel { }, (error: Error & { code: number }) => { // We assume the error code isn't 0 (Status.OK) - callStream.cancelWithStatus( + const {code, details} = restrictControlPlaneStatusCode( typeof error.code === 'number' ? error.code : Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}` - ); + ) + callStream.cancelWithStatus(code, details); } ); } @@ -549,17 +572,13 @@ export class ChannelImplementation implements Channel { if (callMetadata.getOptions().waitForReady) { this.pushPick(callStream, callMetadata, callConfig, dynamicFilters); } else { - callStream.cancelWithStatus( - pickResult.status!.code, - pickResult.status!.details - ); + const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); + callStream.cancelWithStatus(code, details); } break; case PickResultType.DROP: - callStream.cancelWithStatus( - pickResult.status!.code, - pickResult.status!.details - ); + const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); + callStream.cancelWithStatus(code, details); break; default: throw new Error( @@ -668,10 +687,8 @@ export class ChannelImplementation implements Channel { this.tryPick(stream, metadata, callConfig, []); } } else { - stream.cancelWithStatus( - callConfig.status, - 'Failed to route call to method ' + stream.getMethod() - ); + const {code, details} = restrictControlPlaneStatusCode(callConfig.status, 'Failed to route call to method ' + stream.getMethod()); + stream.cancelWithStatus(code, details); } } } From caf37e4f15b7837c03bf182696676ddeb0fbbfbf Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 12 Sep 2022 12:42:44 -0700 Subject: [PATCH 327/694] Fix constant name spelling --- packages/grpc-js/src/channel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 53b80c1cf..93b2204c6 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -68,7 +68,7 @@ function getNewCallNumber(): number { return callNumber; } -const INAPPPROPRIATE_CONTROL_PLANE_CODES: Status[] = [ +const INAPPROPRIATE_CONTROL_PLANE_CODES: Status[] = [ Status.OK, Status.INVALID_ARGUMENT, Status.NOT_FOUND, @@ -80,7 +80,7 @@ const INAPPPROPRIATE_CONTROL_PLANE_CODES: Status[] = [ ] function restrictControlPlaneStatusCode(code: Status, details: string): {code: Status, details: string} { - if (INAPPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) { + if (INAPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) { return { code: Status.INTERNAL, details: `Invalid status from control plane: ${code} ${Status[code]} ${details}` From 02a43a302d563115c20ec95ec4818e6e21fba542 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 12 Sep 2022 13:47:57 -0700 Subject: [PATCH 328/694] grpc-js-xds: NACK WeightedCluster if total_weight is 0 --- packages/grpc-js-xds/src/xds-stream-state/rds-state.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 77a84469b..a5d3c47cf 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -89,6 +89,9 @@ export class RdsState extends BaseXdsStreamState imp } } if (route.route!.cluster_specifier === 'weighted_clusters') { + if (route.route.weighted_clusters!.total_weight?.value === 0) { + return false; + } let weightSum = 0; for (const clusterWeight of route.route.weighted_clusters!.clusters) { weightSum += clusterWeight.weight?.value ?? 0; From 640a1963c741b7721b00f462801958433a27a805 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 20 Sep 2022 16:20:22 -0700 Subject: [PATCH 329/694] grpc-js: Defer evaluating caller stack until an error --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/client.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index d5e5e6927..c0db93ae0 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.7.0", + "version": "1.7.1", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index 747c5c877..112fb7c7c 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -321,7 +321,7 @@ export class Client { } let responseMessage: ResponseType | null = null; let receivedStatus = false; - const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); + const callerStackError = new Error(); call.start(callProperties.metadata, { onReceiveMetadata: (metadata) => { emitter.emit('metadata', metadata); @@ -340,6 +340,7 @@ export class Client { receivedStatus = true; if (status.code === Status.OK) { if (responseMessage === null) { + const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); callProperties.callback!(callErrorFromStatus({ code: Status.INTERNAL, details: 'No message received', @@ -349,6 +350,7 @@ export class Client { callProperties.callback!(null, responseMessage); } } else { + const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); callProperties.callback!(callErrorFromStatus(status, callerStack)); } emitter.emit('status', status); @@ -447,7 +449,7 @@ export class Client { } let responseMessage: ResponseType | null = null; let receivedStatus = false; - const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); + const callerStackError = new Error(); call.start(callProperties.metadata, { onReceiveMetadata: (metadata) => { emitter.emit('metadata', metadata); @@ -466,6 +468,7 @@ export class Client { receivedStatus = true; if (status.code === Status.OK) { if (responseMessage === null) { + const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); callProperties.callback!(callErrorFromStatus({ code: Status.INTERNAL, details: 'No message received', @@ -475,6 +478,7 @@ export class Client { callProperties.callback!(null, responseMessage); } } else { + const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); callProperties.callback!(callErrorFromStatus(status, callerStack)); } emitter.emit('status', status); @@ -577,7 +581,7 @@ export class Client { call.setCredentials(callProperties.callOptions.credentials); } let receivedStatus = false; - const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); + const callerStackError = new Error(); call.start(callProperties.metadata, { onReceiveMetadata(metadata: Metadata) { stream.emit('metadata', metadata); @@ -593,6 +597,7 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { + const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); stream.emit('error', callErrorFromStatus(status, callerStack)); } stream.emit('status', status); @@ -675,7 +680,7 @@ export class Client { call.setCredentials(callProperties.callOptions.credentials); } let receivedStatus = false; - const callerStack = (new Error().stack!).split('\n').slice(1).join('\n'); + const callerStackError = new Error(); call.start(callProperties.metadata, { onReceiveMetadata(metadata: Metadata) { stream.emit('metadata', metadata); @@ -690,6 +695,7 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { + const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); stream.emit('error', callErrorFromStatus(status, callerStack)); } stream.emit('status', status); From 5b42e999e4182ac26dbce77c43374c0a4a02d206 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 20 Sep 2022 16:33:01 -0700 Subject: [PATCH 330/694] grpc-js: Refactor getting stack trace into function --- packages/grpc-js/src/client.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index 112fb7c7c..1198dc468 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -108,6 +108,10 @@ export type ClientOptions = Partial & { callInvocationTransformer?: CallInvocationTransformer; }; +function getErrorStackString(error: Error): string { + return error.stack!.split('\n').slice(1).join('\n'); +} + /** * A generic gRPC client. Primarily useful as a base class for all generated * clients. @@ -340,7 +344,7 @@ export class Client { receivedStatus = true; if (status.code === Status.OK) { if (responseMessage === null) { - const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); + const callerStack = getErrorStackString(callerStackError); callProperties.callback!(callErrorFromStatus({ code: Status.INTERNAL, details: 'No message received', @@ -350,7 +354,7 @@ export class Client { callProperties.callback!(null, responseMessage); } } else { - const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); + const callerStack = getErrorStackString(callerStackError); callProperties.callback!(callErrorFromStatus(status, callerStack)); } emitter.emit('status', status); @@ -468,7 +472,7 @@ export class Client { receivedStatus = true; if (status.code === Status.OK) { if (responseMessage === null) { - const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); + const callerStack = getErrorStackString(callerStackError); callProperties.callback!(callErrorFromStatus({ code: Status.INTERNAL, details: 'No message received', @@ -478,7 +482,7 @@ export class Client { callProperties.callback!(null, responseMessage); } } else { - const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); + const callerStack = getErrorStackString(callerStackError); callProperties.callback!(callErrorFromStatus(status, callerStack)); } emitter.emit('status', status); @@ -597,7 +601,7 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { - const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); + const callerStack = getErrorStackString(callerStackError); stream.emit('error', callErrorFromStatus(status, callerStack)); } stream.emit('status', status); @@ -695,7 +699,7 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { - const callerStack = callerStackError.stack!.split('\n').slice(1).join('\n'); + const callerStack = getErrorStackString(callerStackError); stream.emit('error', callErrorFromStatus(status, callerStack)); } stream.emit('status', status); From 1c0b6459feaf8fa5aa393259ec10e4f1a30e3ada Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 21 Sep 2022 10:44:59 -0700 Subject: [PATCH 331/694] proto-loader: Bump to 0.7.3 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index c9cf35b1e..cae7635f6 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.2", + "version": "0.7.3", "author": "Google Inc.", "contributors": [ { From 2aac1da48e82fc3663b75f18c527ffe56ae6cc79 Mon Sep 17 00:00:00 2001 From: Ryosuke Hayashi Date: Sun, 2 Oct 2022 19:13:19 +0900 Subject: [PATCH 332/694] Build arm64 binaries for mac --- packages/grpc-tools/build_binaries.sh | 61 +++++++++++++++------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/grpc-tools/build_binaries.sh b/packages/grpc-tools/build_binaries.sh index b26946afa..73f95f6d2 100755 --- a/packages/grpc-tools/build_binaries.sh +++ b/packages/grpc-tools/build_binaries.sh @@ -27,38 +27,45 @@ tools_version=$(jq '.version' < package.json | tr -d '"') out_dir=$base/../../artifacts/grpc-tools/v$tools_version mkdir -p "$out_dir" -case $(uname -s) in - Linux) - platform=linux - arch_list=( ia32 x64 ) - ;; - Darwin) - platform=darwin - arch_list=( x64 ) - ;; -esac +build () { + cmake_flag=$* -for arch in "${arch_list[@]}"; do - case $arch in - ia32) - toolchain_flag=-DCMAKE_TOOLCHAIN_FILE=linux_32bit.toolchain.cmake - ;; - *) - toolchain_flag=-DCMAKE_TOOLCHAIN_FILE=linux_64bit.toolchain.cmake - ;; - esac - rm -f $base/build/bin/protoc - rm -f $base/build/bin/grpc_node_plugin + rm -rf $base/build/bin rm -f $base/CMakeCache.txt rm -rf $base/CMakeFiles rm -f $protobuf_base/CMakeCache.txt rm -rf $protobuf_base/CMakeFiles - cmake $toolchain_flag . && cmake --build . --target clean && cmake --build . -- -j 12 - mkdir -p "$base/build/bin" + cmake $cmake_flag . && cmake --build . --target clean && cmake --build . -- -j 12 + mkdir -p $base/build/bin cp -L $protobuf_base/protoc $base/build/bin/protoc cp $base/grpc_node_plugin $base/build/bin/ file $base/build/bin/* - cd $base/build - tar -czf "$out_dir/$platform-$arch.tar.gz" bin/ - cd $base -done \ No newline at end of file +} + +artifacts() { + platform=$1 + arch=$2 + dir=$3 + + tar -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) +} + +case $(uname -s) in + Linux) + build -DCMAKE_TOOLCHAIN_FILE=linux_32bit.toolchain.cmake + artifacts linux ia32 $base/build/bin + build -DCMAKE_TOOLCHAIN_FILE=linux_64bit.toolchain.cmake + artifacts linux x64 $base/build/bin + ;; + Darwin) + build -DCMAKE_TOOLCHAIN_FILE=linux_64bit.toolchain.cmake -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" + + for arch in "x86_64" "arm64"; do + mkdir $base/build/bin/$arch + for bin in protoc grpc_node_plugin; do + lipo -extract x86_64 $base/build/bin/$bin -o $base/build/bin/$arch/$bin + done + artifacts darwin $arch $base/build/bin/$arch/ + done + ;; +esac From 7bbbf0d460271e02df9549f070cfa4cd48f57b64 Mon Sep 17 00:00:00 2001 From: nakamura-k30 <51692717+nakamura-k30@users.noreply.github.com> Date: Tue, 4 Oct 2022 20:12:46 +0900 Subject: [PATCH 333/694] list undocumented tracers --- doc/environment_variables.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/environment_variables.md b/doc/environment_variables.md index 0237dff7e..70b32e715 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -29,6 +29,7 @@ can be set. - `channel` - Traces channel events - `connectivity_state` - Traces channel connectivity state changes - `dns_resolver` - Traces DNS resolution + - `ip_resolver` - Traces IPv4/v6 resolution - `pick_first` - Traces the pick first load balancing policy - `proxy` - Traces proxy operations - `resolving_load_balancer` - Traces the resolving load balancer @@ -40,6 +41,9 @@ can be set. - `subchannel_flowctrl` - Traces HTTP/2 flow control. Includes per-call logs. - `subchannel_internals` - Traces HTTP/2 session state. Includes per-call logs. - `channel_stacktrace` - Traces channel construction events with stack traces. + - `keepalive` - Traces gRPC keepalive pings + - `index` - Traces module loading + - `outlier_detection` - Traces outlier detection events The following tracers are added by the `@grpc/grpc-js-xds` library: - `cds_balancer` - Traces the CDS load balancing policy From 98d2506139110f1a604bb9ec871f7833af8178dd Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 5 Oct 2022 09:54:42 -0700 Subject: [PATCH 334/694] grpc-tools: Bump to version 1.11.3 --- packages/grpc-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index 95faa84c4..1c7deb250 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.11.2", + "version": "1.11.3", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", From 7229fc28eb90618fe6f54da0b8a345dc379852c6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 5 Oct 2022 13:22:51 -0700 Subject: [PATCH 335/694] grpc-tools: Fix x64 arch name in build script --- packages/grpc-tools/build_binaries.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/build_binaries.sh b/packages/grpc-tools/build_binaries.sh index 73f95f6d2..e44881083 100755 --- a/packages/grpc-tools/build_binaries.sh +++ b/packages/grpc-tools/build_binaries.sh @@ -60,7 +60,7 @@ case $(uname -s) in Darwin) build -DCMAKE_TOOLCHAIN_FILE=linux_64bit.toolchain.cmake -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" - for arch in "x86_64" "arm64"; do + for arch in "x64" "arm64"; do mkdir $base/build/bin/$arch for bin in protoc grpc_node_plugin; do lipo -extract x86_64 $base/build/bin/$bin -o $base/build/bin/$arch/$bin From 7942b23e79567d74b6b9d8796ab15f985f6926e2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 10 Oct 2022 14:11:16 -0700 Subject: [PATCH 336/694] grpc-js-xds: Validate that endpoint weights sum to no more than 32 bit uint max per priority --- packages/grpc-js-xds/src/xds-stream-state/eds-state.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index 91cb6f30f..dd1adf18a 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -50,6 +50,7 @@ export class EdsState extends BaseXdsStreamState */ public validateResponse(message: ClusterLoadAssignment__Output) { const seenLocalities: {locality: Locality__Output, priority: number}[] = []; + const priorityTotalWeights: Map = new Map(); for (const endpoint of message.endpoints) { if (!endpoint.locality) { return false; @@ -72,6 +73,12 @@ export class EdsState extends BaseXdsStreamState return false; } } + priorityTotalWeights.set(endpoint.priority, (priorityTotalWeights.get(endpoint.priority) ?? 0) + (endpoint.load_balancing_weight?.value ?? 0)); + } + for (const totalWeight of priorityTotalWeights.values()) { + if (totalWeight >= 1<<32) { + return false; + } } return true; } From 8832fc2d39d25417e59eeea295333c6525c9f6a3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 11 Oct 2022 13:55:19 -0700 Subject: [PATCH 337/694] grpc-js-xds: Validate uniqueness of addresses in EDS updates --- .../grpc-js-xds/src/xds-stream-state/eds-state.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index dd1adf18a..370cf7502 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -18,6 +18,7 @@ import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; import { isIPv4, isIPv6 } from "net"; import { Locality__Output } from "../generated/envoy/config/core/v3/Locality"; +import { SocketAddress__Output } from "../generated/envoy/config/core/v3/SocketAddress"; import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; import { Any__Output } from "../generated/google/protobuf/Any"; import { BaseXdsStreamState, HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; @@ -32,6 +33,10 @@ function localitiesEqual(a: Locality__Output, b: Locality__Output) { return a.region === b.region && a.sub_zone === b.sub_zone && a.zone === b.zone; } +function addressesEqual(a: SocketAddress__Output, b: SocketAddress__Output) { + return a.address === b.address && a.port_value === b.port_value; +} + export class EdsState extends BaseXdsStreamState implements XdsStreamState { protected getResourceName(resource: ClusterLoadAssignment__Output): string { return resource.cluster_name; @@ -50,6 +55,7 @@ export class EdsState extends BaseXdsStreamState */ public validateResponse(message: ClusterLoadAssignment__Output) { const seenLocalities: {locality: Locality__Output, priority: number}[] = []; + const seenAddresses: SocketAddress__Output[] = []; const priorityTotalWeights: Map = new Map(); for (const endpoint of message.endpoints) { if (!endpoint.locality) { @@ -72,6 +78,12 @@ export class EdsState extends BaseXdsStreamState if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) { return false; } + for (const address of seenAddresses) { + if (addressesEqual(socketAddress, address)) { + return false; + } + } + seenAddresses.push(socketAddress); } priorityTotalWeights.set(endpoint.priority, (priorityTotalWeights.get(endpoint.priority) ?? 0) + (endpoint.load_balancing_weight?.value ?? 0)); } From bedc9628f5614f5f020c52a40dea064c53834371 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 11 Oct 2022 13:58:57 -0700 Subject: [PATCH 338/694] grpc-js-xds: Validate continuity of priorities in EDS updates --- packages/grpc-js-xds/src/xds-stream-state/eds-state.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index 370cf7502..9b7b9cd5f 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -92,6 +92,11 @@ export class EdsState extends BaseXdsStreamState return false; } } + for (const priority of priorityTotalWeights.keys()) { + if (priority > 0 && !priorityTotalWeights.has(priority - 1)) { + return false; + } + } return true; } } \ No newline at end of file From 339eb37efd5fc6e7da177036913a0840c81c1195 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 26 Apr 2022 10:03:08 -0700 Subject: [PATCH 339/694] grpc-js: Refactor in preparation for retries --- .../grpc-js/src/call-credentials-filter.ts | 86 -- packages/grpc-js/src/call-interface.ts | 169 ++++ packages/grpc-js/src/call-number.ts | 22 + packages/grpc-js/src/call-stream.ts | 882 ------------------ packages/grpc-js/src/call.ts | 2 +- packages/grpc-js/src/channel.ts | 653 +------------ packages/grpc-js/src/client-interceptors.ts | 2 +- packages/grpc-js/src/client.ts | 3 +- packages/grpc-js/src/compression-filter.ts | 4 +- packages/grpc-js/src/deadline-filter.ts | 120 --- packages/grpc-js/src/deadline.ts | 58 ++ packages/grpc-js/src/experimental.ts | 2 +- packages/grpc-js/src/filter-stack.ts | 14 +- packages/grpc-js/src/filter.ts | 12 +- packages/grpc-js/src/index.ts | 5 +- packages/grpc-js/src/internal-channel.ts | 504 ++++++++++ .../src/load-balancer-outlier-detection.ts | 34 +- .../grpc-js/src/load-balancer-pick-first.ts | 2 +- .../grpc-js/src/load-balancer-round-robin.ts | 2 +- packages/grpc-js/src/load-balancing-call.ts | 281 ++++++ .../grpc-js/src/max-message-size-filter.ts | 30 +- packages/grpc-js/src/picker.ts | 23 +- packages/grpc-js/src/resolver-dns.ts | 2 +- packages/grpc-js/src/resolver-ip.ts | 2 +- packages/grpc-js/src/resolver.ts | 2 +- packages/grpc-js/src/resolving-call.ts | 219 +++++ .../grpc-js/src/resolving-load-balancer.ts | 2 +- packages/grpc-js/src/server-call.ts | 5 +- packages/grpc-js/src/service-config.ts | 23 + packages/grpc-js/src/status-builder.ts | 2 +- packages/grpc-js/src/subchannel-call.ts | 504 ++++++++++ packages/grpc-js/src/subchannel.ts | 91 +- packages/grpc-js/test/test-resolver.ts | 2 +- 33 files changed, 1886 insertions(+), 1878 deletions(-) delete mode 100644 packages/grpc-js/src/call-credentials-filter.ts create mode 100644 packages/grpc-js/src/call-interface.ts create mode 100644 packages/grpc-js/src/call-number.ts delete mode 100644 packages/grpc-js/src/call-stream.ts delete mode 100644 packages/grpc-js/src/deadline-filter.ts create mode 100644 packages/grpc-js/src/deadline.ts create mode 100644 packages/grpc-js/src/internal-channel.ts create mode 100644 packages/grpc-js/src/load-balancing-call.ts create mode 100644 packages/grpc-js/src/resolving-call.ts create mode 100644 packages/grpc-js/src/subchannel-call.ts diff --git a/packages/grpc-js/src/call-credentials-filter.ts b/packages/grpc-js/src/call-credentials-filter.ts deleted file mode 100644 index 5c746297e..000000000 --- a/packages/grpc-js/src/call-credentials-filter.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 { Call } from './call-stream'; -import { Channel } from './channel'; -import { BaseFilter, Filter, FilterFactory } from './filter'; -import { Metadata } from './metadata'; -import { Status } from './constants'; -import { splitHostPort } from './uri-parser'; -import { ServiceError } from './call'; - -export class CallCredentialsFilter extends BaseFilter implements Filter { - private serviceUrl: string; - constructor( - private readonly channel: Channel, - private readonly stream: Call - ) { - super(); - this.channel = channel; - this.stream = stream; - const splitPath: string[] = stream.getMethod().split('/'); - let serviceName = ''; - /* The standard path format is "/{serviceName}/{methodName}", so if we split - * by '/', the first item should be empty and the second should be the - * service name */ - if (splitPath.length >= 2) { - serviceName = splitPath[1]; - } - const hostname = splitHostPort(stream.getHost())?.host ?? 'localhost'; - /* Currently, call credentials are only allowed on HTTPS connections, so we - * can assume that the scheme is "https" */ - this.serviceUrl = `https://${hostname}/${serviceName}`; - } - - async sendMetadata(metadata: Promise): Promise { - const credentials = this.stream.getCredentials(); - const credsMetadata = credentials.generateMetadata({ - service_url: this.serviceUrl, - }); - const resultMetadata = await metadata; - try { - resultMetadata.merge(await credsMetadata); - } catch (error) { - this.stream.cancelWithStatus( - Status.UNAUTHENTICATED, - `Failed to retrieve auth metadata with error: ${error.message}` - ); - return Promise.reject('Failed to retrieve auth metadata'); - } - if (resultMetadata.get('authorization').length > 1) { - this.stream.cancelWithStatus( - Status.INTERNAL, - '"authorization" metadata cannot have multiple values' - ); - return Promise.reject( - '"authorization" metadata cannot have multiple values' - ); - } - return resultMetadata; - } -} - -export class CallCredentialsFilterFactory - implements FilterFactory { - constructor(private readonly channel: Channel) { - this.channel = channel; - } - - createFilter(callStream: Call): CallCredentialsFilter { - return new CallCredentialsFilter(this.channel, callStream); - } -} diff --git a/packages/grpc-js/src/call-interface.ts b/packages/grpc-js/src/call-interface.ts new file mode 100644 index 000000000..891170fec --- /dev/null +++ b/packages/grpc-js/src/call-interface.ts @@ -0,0 +1,169 @@ +/* + * Copyright 2022 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 { CallCredentials } from "./call-credentials"; +import { Status } from "./constants"; +import { Deadline } from "./deadline"; +import { Metadata } from "./metadata"; +import { ServerSurfaceCall } from "./server-call"; + +export interface CallStreamOptions { + deadline: Deadline; + flags: number; + host: string; + parentCall: ServerSurfaceCall | null; +} + +export type PartialCallStreamOptions = Partial; + +export interface StatusObject { + code: Status; + details: string; + metadata: Metadata; +} + +export const enum WriteFlags { + BufferHint = 1, + NoCompress = 2, + WriteThrough = 4, +} + +export interface WriteObject { + message: Buffer; + flags?: number; +} + +export interface MetadataListener { + (metadata: Metadata, next: (metadata: Metadata) => void): void; +} + +export interface MessageListener { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (message: any, next: (message: any) => void): void; +} + +export interface StatusListener { + (status: StatusObject, next: (status: StatusObject) => void): void; +} + +export interface FullListener { + onReceiveMetadata: MetadataListener; + onReceiveMessage: MessageListener; + onReceiveStatus: StatusListener; +} + +export type Listener = Partial; + +/** + * An object with methods for handling the responses to a call. + */ +export interface InterceptingListener { + onReceiveMetadata(metadata: Metadata): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any): void; + onReceiveStatus(status: StatusObject): void; +} + +export function isInterceptingListener( + listener: Listener | InterceptingListener +): listener is InterceptingListener { + return ( + listener.onReceiveMetadata !== undefined && + listener.onReceiveMetadata.length === 1 + ); +} + +export class InterceptingListenerImpl implements InterceptingListener { + private processingMetadata = false; + private hasPendingMessage = false; + private pendingMessage: any; + private processingMessage = false; + private pendingStatus: StatusObject | null = null; + constructor( + private listener: FullListener, + private nextListener: InterceptingListener + ) {} + + private processPendingMessage() { + if (this.hasPendingMessage) { + this.nextListener.onReceiveMessage(this.pendingMessage); + this.pendingMessage = null; + this.hasPendingMessage = false; + } + } + + private processPendingStatus() { + if (this.pendingStatus) { + this.nextListener.onReceiveStatus(this.pendingStatus); + } + } + + onReceiveMetadata(metadata: Metadata): void { + this.processingMetadata = true; + this.listener.onReceiveMetadata(metadata, (metadata) => { + this.processingMetadata = false; + this.nextListener.onReceiveMetadata(metadata); + this.processPendingMessage(); + this.processPendingStatus(); + }); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any): void { + /* If this listener processes messages asynchronously, the last message may + * be reordered with respect to the status */ + this.processingMessage = true; + this.listener.onReceiveMessage(message, (msg) => { + this.processingMessage = false; + if (this.processingMetadata) { + this.pendingMessage = msg; + this.hasPendingMessage = true; + } else { + this.nextListener.onReceiveMessage(msg); + this.processPendingStatus(); + } + }); + } + onReceiveStatus(status: StatusObject): void { + this.listener.onReceiveStatus(status, (processedStatus) => { + if (this.processingMetadata || this.processingMessage) { + this.pendingStatus = processedStatus; + } else { + this.nextListener.onReceiveStatus(processedStatus); + } + }); + } +} + +export interface WriteCallback { + (error?: Error | null): void; +} + +export interface MessageContext { + callback?: WriteCallback; + flags?: number; +} + +export interface Call { + cancelWithStatus(status: Status, details: string): void; + getPeer(): string; + start(metadata: Metadata, listener: InterceptingListener): void; + sendMessageWithContext(context: MessageContext, message: Buffer): void; + startRead(): void; + halfClose(): void; + getCallNumber(): number; + setCredentials(credentials: CallCredentials): void; +} \ No newline at end of file diff --git a/packages/grpc-js/src/call-number.ts b/packages/grpc-js/src/call-number.ts new file mode 100644 index 000000000..48d34fac5 --- /dev/null +++ b/packages/grpc-js/src/call-number.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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. + * + */ + +let nextCallNumber = 0; + +export function getNextCallNumber() { + return nextCallNumber++; +} \ No newline at end of file diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts deleted file mode 100644 index e8f312752..000000000 --- a/packages/grpc-js/src/call-stream.ts +++ /dev/null @@ -1,882 +0,0 @@ -/* - * 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 * as http2 from 'http2'; -import * as os from 'os'; - -import { CallCredentials } from './call-credentials'; -import { Propagate, Status } from './constants'; -import { Filter, FilterFactory } from './filter'; -import { FilterStackFactory, FilterStack } from './filter-stack'; -import { Metadata } from './metadata'; -import { StreamDecoder } from './stream-decoder'; -import { ChannelImplementation } from './channel'; -import { SubchannelCallStatsTracker, Subchannel } from './subchannel'; -import * as logging from './logging'; -import { LogVerbosity } from './constants'; -import { ServerSurfaceCall } from './server-call'; - -const TRACER_NAME = 'call_stream'; - -const { - HTTP2_HEADER_STATUS, - HTTP2_HEADER_CONTENT_TYPE, - NGHTTP2_CANCEL, -} = http2.constants; - -/** - * https://nodejs.org/api/errors.html#errors_class_systemerror - */ -interface SystemError extends Error { - address?: string; - code: string; - dest?: string; - errno: number; - info?: object; - message: string; - path?: string; - port?: number; - syscall: string; -} - -/** - * Should do approximately the same thing as util.getSystemErrorName but the - * TypeScript types don't have that function for some reason so I just made my - * own. - * @param errno - */ -function getSystemErrorName(errno: number): string { - for (const [name, num] of Object.entries(os.constants.errno)) { - if (num === errno) { - return name; - } - } - return 'Unknown system error ' + errno; -} - -export type Deadline = Date | number; - -function getMinDeadline(deadlineList: Deadline[]): Deadline { - let minValue = Infinity; - for (const deadline of deadlineList) { - const deadlineMsecs = - deadline instanceof Date ? deadline.getTime() : deadline; - if (deadlineMsecs < minValue) { - minValue = deadlineMsecs; - } - } - return minValue; -} - -export interface CallStreamOptions { - deadline: Deadline; - flags: number; - host: string; - parentCall: ServerSurfaceCall | null; -} - -export type PartialCallStreamOptions = Partial; - -export interface StatusObject { - code: Status; - details: string; - metadata: Metadata; -} - -export const enum WriteFlags { - BufferHint = 1, - NoCompress = 2, - WriteThrough = 4, -} - -export interface WriteObject { - message: Buffer; - flags?: number; -} - -export interface MetadataListener { - (metadata: Metadata, next: (metadata: Metadata) => void): void; -} - -export interface MessageListener { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (message: any, next: (message: any) => void): void; -} - -export interface StatusListener { - (status: StatusObject, next: (status: StatusObject) => void): void; -} - -export interface FullListener { - onReceiveMetadata: MetadataListener; - onReceiveMessage: MessageListener; - onReceiveStatus: StatusListener; -} - -export type Listener = Partial; - -/** - * An object with methods for handling the responses to a call. - */ -export interface InterceptingListener { - onReceiveMetadata(metadata: Metadata): void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onReceiveMessage(message: any): void; - onReceiveStatus(status: StatusObject): void; -} - -export function isInterceptingListener( - listener: Listener | InterceptingListener -): listener is InterceptingListener { - return ( - listener.onReceiveMetadata !== undefined && - listener.onReceiveMetadata.length === 1 - ); -} - -export class InterceptingListenerImpl implements InterceptingListener { - private processingMetadata = false; - private hasPendingMessage = false; - private pendingMessage: any; - private processingMessage = false; - private pendingStatus: StatusObject | null = null; - constructor( - private listener: FullListener, - private nextListener: InterceptingListener - ) {} - - private processPendingMessage() { - if (this.hasPendingMessage) { - this.nextListener.onReceiveMessage(this.pendingMessage); - this.pendingMessage = null; - this.hasPendingMessage = false; - } - } - - private processPendingStatus() { - if (this.pendingStatus) { - this.nextListener.onReceiveStatus(this.pendingStatus); - } - } - - onReceiveMetadata(metadata: Metadata): void { - this.processingMetadata = true; - this.listener.onReceiveMetadata(metadata, (metadata) => { - this.processingMetadata = false; - this.nextListener.onReceiveMetadata(metadata); - this.processPendingMessage(); - this.processPendingStatus(); - }); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onReceiveMessage(message: any): void { - /* If this listener processes messages asynchronously, the last message may - * be reordered with respect to the status */ - this.processingMessage = true; - this.listener.onReceiveMessage(message, (msg) => { - this.processingMessage = false; - if (this.processingMetadata) { - this.pendingMessage = msg; - this.hasPendingMessage = true; - } else { - this.nextListener.onReceiveMessage(msg); - this.processPendingStatus(); - } - }); - } - onReceiveStatus(status: StatusObject): void { - this.listener.onReceiveStatus(status, (processedStatus) => { - if (this.processingMetadata || this.processingMessage) { - this.pendingStatus = processedStatus; - } else { - this.nextListener.onReceiveStatus(processedStatus); - } - }); - } -} - -export interface WriteCallback { - (error?: Error | null): void; -} - -export interface MessageContext { - callback?: WriteCallback; - flags?: number; -} - -export interface Call { - cancelWithStatus(status: Status, details: string): void; - getPeer(): string; - start(metadata: Metadata, listener: InterceptingListener): void; - sendMessageWithContext(context: MessageContext, message: Buffer): void; - startRead(): void; - halfClose(): void; - - getDeadline(): Deadline; - getCredentials(): CallCredentials; - setCredentials(credentials: CallCredentials): void; - getMethod(): string; - getHost(): string; -} - -export class Http2CallStream implements Call { - credentials: CallCredentials; - filterStack: FilterStack; - private http2Stream: http2.ClientHttp2Stream | null = null; - private pendingRead = false; - private isWriteFilterPending = false; - private pendingWrite: Buffer | null = null; - private pendingWriteCallback: WriteCallback | null = null; - private writesClosed = false; - - private decoder = new StreamDecoder(); - - private isReadFilterPending = false; - private canPush = false; - /** - * Indicates that an 'end' event has come from the http2 stream, so there - * will be no more data events. - */ - private readsClosed = false; - - private statusOutput = false; - - private unpushedReadMessages: Buffer[] = []; - private unfilteredReadMessages: Buffer[] = []; - - // Status code mapped from :status. To be used if grpc-status is not received - private mappedStatusCode: Status = Status.UNKNOWN; - - // This is populated (non-null) if and only if the call has ended - private finalStatus: StatusObject | null = null; - - private subchannel: Subchannel | null = null; - private disconnectListener: () => void; - - private listener: InterceptingListener | null = null; - - private internalError: SystemError | null = null; - - private configDeadline: Deadline = Infinity; - - private statusWatchers: ((status: StatusObject) => void)[] = []; - private streamEndWatchers: ((success: boolean) => void)[] = []; - - private callStatsTracker: SubchannelCallStatsTracker | null = null; - - constructor( - private readonly methodName: string, - private readonly channel: ChannelImplementation, - private readonly options: CallStreamOptions, - filterStackFactory: FilterStackFactory, - private readonly channelCallCredentials: CallCredentials, - private readonly callNumber: number - ) { - this.filterStack = filterStackFactory.createFilter(this); - this.credentials = channelCallCredentials; - this.disconnectListener = () => { - this.endCall({ - code: Status.UNAVAILABLE, - details: 'Connection dropped', - metadata: new Metadata(), - }); - }; - if ( - this.options.parentCall && - this.options.flags & Propagate.CANCELLATION - ) { - this.options.parentCall.on('cancelled', () => { - this.cancelWithStatus(Status.CANCELLED, 'Cancelled by parent call'); - }); - } - } - - private outputStatus() { - /* Precondition: this.finalStatus !== null */ - if (this.listener && !this.statusOutput) { - this.statusOutput = true; - const filteredStatus = this.filterStack.receiveTrailers( - this.finalStatus! - ); - this.trace( - 'ended with status: code=' + - filteredStatus.code + - ' details="' + - filteredStatus.details + - '"' - ); - this.statusWatchers.forEach(watcher => watcher(filteredStatus)); - /* We delay the actual action of bubbling up the status to insulate the - * cleanup code in this class from any errors that may be thrown in the - * upper layers as a result of bubbling up the status. In particular, - * if the status is not OK, the "error" event may be emitted - * synchronously at the top level, which will result in a thrown error if - * the user does not handle that event. */ - process.nextTick(() => { - this.listener?.onReceiveStatus(filteredStatus); - }); - if (this.subchannel) { - this.subchannel.callUnref(); - this.subchannel.removeDisconnectListener(this.disconnectListener); - } - } - } - - private trace(text: string): void { - logging.trace( - LogVerbosity.DEBUG, - TRACER_NAME, - '[' + this.callNumber + '] ' + text - ); - } - - /** - * On first call, emits a 'status' event with the given StatusObject. - * Subsequent calls are no-ops. - * @param status The status of the call. - */ - private endCall(status: StatusObject): void { - /* If the status is OK and a new status comes in (e.g. from a - * deserialization failure), that new status takes priority */ - if (this.finalStatus === null || this.finalStatus.code === Status.OK) { - this.finalStatus = status; - this.maybeOutputStatus(); - } - this.destroyHttp2Stream(); - } - - private maybeOutputStatus() { - if (this.finalStatus !== null) { - /* The combination check of readsClosed and that the two message buffer - * arrays are empty checks that there all incoming data has been fully - * processed */ - if ( - this.finalStatus.code !== Status.OK || - (this.readsClosed && - this.unpushedReadMessages.length === 0 && - this.unfilteredReadMessages.length === 0 && - !this.isReadFilterPending) - ) { - this.outputStatus(); - } - } - } - - private push(message: Buffer): void { - this.trace( - 'pushing to reader message of length ' + - (message instanceof Buffer ? message.length : null) - ); - this.canPush = false; - process.nextTick(() => { - /* If we have already output the status any later messages should be - * ignored, and can cause out-of-order operation errors higher up in the - * stack. Checking as late as possible here to avoid any race conditions. - */ - if (this.statusOutput) { - return; - } - this.listener?.onReceiveMessage(message); - this.maybeOutputStatus(); - }); - } - - private handleFilterError(error: Error) { - this.cancelWithStatus(Status.INTERNAL, error.message); - } - - private handleFilteredRead(message: Buffer) { - /* If we the call has already ended with an error, we don't want to do - * anything with this message. Dropping it on the floor is correct - * behavior */ - if (this.finalStatus !== null && this.finalStatus.code !== Status.OK) { - this.maybeOutputStatus(); - return; - } - this.isReadFilterPending = false; - if (this.canPush) { - this.http2Stream!.pause(); - this.push(message); - } else { - this.trace( - 'unpushedReadMessages.push message of length ' + message.length - ); - this.unpushedReadMessages.push(message); - } - if (this.unfilteredReadMessages.length > 0) { - /* nextMessage is guaranteed not to be undefined because - unfilteredReadMessages is non-empty */ - const nextMessage = this.unfilteredReadMessages.shift()!; - this.filterReceivedMessage(nextMessage); - } - } - - private filterReceivedMessage(framedMessage: Buffer) { - /* If we the call has already ended with an error, we don't want to do - * anything with this message. Dropping it on the floor is correct - * behavior */ - if (this.finalStatus !== null && this.finalStatus.code !== Status.OK) { - this.maybeOutputStatus(); - return; - } - this.trace('filterReceivedMessage of length ' + framedMessage.length); - this.isReadFilterPending = true; - this.filterStack - .receiveMessage(Promise.resolve(framedMessage)) - .then( - this.handleFilteredRead.bind(this), - this.handleFilterError.bind(this) - ); - } - - private tryPush(messageBytes: Buffer): void { - if (this.isReadFilterPending) { - this.trace( - 'unfilteredReadMessages.push message of length ' + - (messageBytes && messageBytes.length) - ); - this.unfilteredReadMessages.push(messageBytes); - } else { - this.filterReceivedMessage(messageBytes); - } - } - - private handleTrailers(headers: http2.IncomingHttpHeaders) { - this.streamEndWatchers.forEach(watcher => watcher(true)); - let headersString = ''; - for (const header of Object.keys(headers)) { - headersString += '\t\t' + header + ': ' + headers[header] + '\n'; - } - this.trace('Received server trailers:\n' + headersString); - let metadata: Metadata; - try { - metadata = Metadata.fromHttp2Headers(headers); - } catch (e) { - metadata = new Metadata(); - } - const metadataMap = metadata.getMap(); - let code: Status = this.mappedStatusCode; - if ( - code === Status.UNKNOWN && - typeof metadataMap['grpc-status'] === 'string' - ) { - const receivedStatus = Number(metadataMap['grpc-status']); - if (receivedStatus in Status) { - code = receivedStatus; - this.trace('received status code ' + receivedStatus + ' from server'); - } - metadata.remove('grpc-status'); - } - let details = ''; - if (typeof metadataMap['grpc-message'] === 'string') { - details = decodeURI(metadataMap['grpc-message']); - metadata.remove('grpc-message'); - this.trace( - 'received status details string "' + details + '" from server' - ); - } - const status: StatusObject = { code, details, metadata }; - // This is a no-op if the call was already ended when handling headers. - this.endCall(status); - } - - private writeMessageToStream(message: Buffer, callback: WriteCallback) { - this.callStatsTracker?.addMessageSent(); - this.http2Stream!.write(message, callback); - } - - attachHttp2Stream( - stream: http2.ClientHttp2Stream, - subchannel: Subchannel, - extraFilters: Filter[], - callStatsTracker: SubchannelCallStatsTracker - ): void { - this.filterStack.push(extraFilters); - if (this.finalStatus !== null) { - stream.close(NGHTTP2_CANCEL); - } else { - this.trace( - 'attachHttp2Stream from subchannel ' + subchannel.getAddress() - ); - this.http2Stream = stream; - this.subchannel = subchannel; - this.callStatsTracker = callStatsTracker; - subchannel.addDisconnectListener(this.disconnectListener); - subchannel.callRef(); - stream.on('response', (headers, flags) => { - let headersString = ''; - for (const header of Object.keys(headers)) { - headersString += '\t\t' + header + ': ' + headers[header] + '\n'; - } - this.trace('Received server headers:\n' + headersString); - switch (headers[':status']) { - // TODO(murgatroid99): handle 100 and 101 - case 400: - this.mappedStatusCode = Status.INTERNAL; - break; - case 401: - this.mappedStatusCode = Status.UNAUTHENTICATED; - break; - case 403: - this.mappedStatusCode = Status.PERMISSION_DENIED; - break; - case 404: - this.mappedStatusCode = Status.UNIMPLEMENTED; - break; - case 429: - case 502: - case 503: - case 504: - this.mappedStatusCode = Status.UNAVAILABLE; - break; - default: - this.mappedStatusCode = Status.UNKNOWN; - } - - if (flags & http2.constants.NGHTTP2_FLAG_END_STREAM) { - this.handleTrailers(headers); - } else { - let metadata: Metadata; - try { - metadata = Metadata.fromHttp2Headers(headers); - } catch (error) { - this.endCall({ - code: Status.UNKNOWN, - details: error.message, - metadata: new Metadata(), - }); - return; - } - try { - const finalMetadata = this.filterStack.receiveMetadata(metadata); - this.listener?.onReceiveMetadata(finalMetadata); - } catch (error) { - this.endCall({ - code: Status.UNKNOWN, - details: error.message, - metadata: new Metadata(), - }); - } - } - }); - stream.on('trailers', this.handleTrailers.bind(this)); - stream.on('data', (data: Buffer) => { - this.trace('receive HTTP/2 data frame of length ' + data.length); - const messages = this.decoder.write(data); - - for (const message of messages) { - this.trace('parsed message of length ' + message.length); - this.callStatsTracker!.addMessageReceived(); - this.tryPush(message); - } - }); - stream.on('end', () => { - this.readsClosed = true; - this.maybeOutputStatus(); - }); - stream.on('close', () => { - /* Use process.next tick to ensure that this code happens after any - * "error" event that may be emitted at about the same time, so that - * we can bubble up the error message from that event. */ - process.nextTick(() => { - this.trace('HTTP/2 stream closed with code ' + stream.rstCode); - /* If we have a final status with an OK status code, that means that - * we have received all of the messages and we have processed the - * trailers and the call completed successfully, so it doesn't matter - * how the stream ends after that */ - if (this.finalStatus?.code === Status.OK) { - return; - } - let code: Status; - let details = ''; - switch (stream.rstCode) { - case http2.constants.NGHTTP2_NO_ERROR: - /* If we get a NO_ERROR code and we already have a status, the - * stream completed properly and we just haven't fully processed - * it yet */ - if (this.finalStatus !== null) { - return; - } - code = Status.INTERNAL; - details = `Received RST_STREAM with code ${stream.rstCode}`; - break; - case http2.constants.NGHTTP2_REFUSED_STREAM: - code = Status.UNAVAILABLE; - details = 'Stream refused by server'; - break; - case http2.constants.NGHTTP2_CANCEL: - code = Status.CANCELLED; - details = 'Call cancelled'; - break; - case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM: - code = Status.RESOURCE_EXHAUSTED; - details = 'Bandwidth exhausted or memory limit exceeded'; - break; - case http2.constants.NGHTTP2_INADEQUATE_SECURITY: - code = Status.PERMISSION_DENIED; - details = 'Protocol not secure enough'; - break; - case http2.constants.NGHTTP2_INTERNAL_ERROR: - code = Status.INTERNAL; - if (this.internalError === null) { - /* This error code was previously handled in the default case, and - * there are several instances of it online, so I wanted to - * preserve the original error message so that people find existing - * information in searches, but also include the more recognizable - * "Internal server error" message. */ - details = `Received RST_STREAM with code ${stream.rstCode} (Internal server error)`; - } else { - if (this.internalError.code === 'ECONNRESET' || this.internalError.code === 'ETIMEDOUT') { - code = Status.UNAVAILABLE; - details = this.internalError.message; - } else { - /* The "Received RST_STREAM with code ..." error is preserved - * here for continuity with errors reported online, but the - * error message at the end will probably be more relevant in - * most cases. */ - details = `Received RST_STREAM with code ${stream.rstCode} triggered by internal client error: ${this.internalError.message}`; - } - } - break; - default: - code = Status.INTERNAL; - details = `Received RST_STREAM with code ${stream.rstCode}`; - } - // This is a no-op if trailers were received at all. - // This is OK, because status codes emitted here correspond to more - // catastrophic issues that prevent us from receiving trailers in the - // first place. - this.endCall({ code, details, metadata: new Metadata() }); - }); - }); - stream.on('error', (err: SystemError) => { - /* We need an error handler here to stop "Uncaught Error" exceptions - * from bubbling up. However, errors here should all correspond to - * "close" events, where we will handle the error more granularly */ - /* Specifically looking for stream errors that were *not* constructed - * from a RST_STREAM response here: - * https://github.com/nodejs/node/blob/8b8620d580314050175983402dfddf2674e8e22a/lib/internal/http2/core.js#L2267 - */ - if (err.code !== 'ERR_HTTP2_STREAM_ERROR') { - this.trace( - 'Node error event: message=' + - err.message + - ' code=' + - err.code + - ' errno=' + - getSystemErrorName(err.errno) + - ' syscall=' + - err.syscall - ); - this.internalError = err; - } - this.streamEndWatchers.forEach(watcher => watcher(false)); - }); - if (!this.pendingRead) { - stream.pause(); - } - if (this.pendingWrite) { - if (!this.pendingWriteCallback) { - throw new Error('Invalid state in write handling code'); - } - this.trace( - 'sending data chunk of length ' + - this.pendingWrite.length + - ' (deferred)' - ); - try { - this.writeMessageToStream(this.pendingWrite, this.pendingWriteCallback); - } catch (error) { - this.endCall({ - code: Status.UNAVAILABLE, - details: `Write failed with error ${error.message}`, - metadata: new Metadata() - }); - } - } - this.maybeCloseWrites(); - } - } - - start(metadata: Metadata, listener: InterceptingListener) { - this.trace('Sending metadata'); - this.listener = listener; - this.channel._startCallStream(this, metadata); - this.maybeOutputStatus(); - } - - private destroyHttp2Stream() { - // The http2 stream could already have been destroyed if cancelWithStatus - // is called in response to an internal http2 error. - if (this.http2Stream !== null && !this.http2Stream.destroyed) { - /* If the call has ended with an OK status, communicate that when closing - * the stream, partly to avoid a situation in which we detect an error - * RST_STREAM as a result after we have the status */ - let code: number; - if (this.finalStatus?.code === Status.OK) { - code = http2.constants.NGHTTP2_NO_ERROR; - } else { - code = http2.constants.NGHTTP2_CANCEL; - } - this.trace('close http2 stream with code ' + code); - this.http2Stream.close(code); - } - } - - cancelWithStatus(status: Status, details: string): void { - this.trace( - 'cancelWithStatus code: ' + status + ' details: "' + details + '"' - ); - this.endCall({ code: status, details, metadata: new Metadata() }); - } - - getDeadline(): Deadline { - const deadlineList = [this.options.deadline]; - if (this.options.parentCall && this.options.flags & Propagate.DEADLINE) { - deadlineList.push(this.options.parentCall.getDeadline()); - } - if (this.configDeadline) { - deadlineList.push(this.configDeadline); - } - return getMinDeadline(deadlineList); - } - - getCredentials(): CallCredentials { - return this.credentials; - } - - setCredentials(credentials: CallCredentials): void { - this.credentials = this.channelCallCredentials.compose(credentials); - } - - getStatus(): StatusObject | null { - return this.finalStatus; - } - - getPeer(): string { - return this.subchannel?.getAddress() ?? this.channel.getTarget(); - } - - getMethod(): string { - return this.methodName; - } - - getHost(): string { - return this.options.host; - } - - setConfigDeadline(configDeadline: Deadline) { - this.configDeadline = configDeadline; - } - - addStatusWatcher(watcher: (status: StatusObject) => void) { - this.statusWatchers.push(watcher); - } - - addStreamEndWatcher(watcher: (success: boolean) => void) { - this.streamEndWatchers.push(watcher); - } - - addFilters(extraFilters: Filter[]) { - this.filterStack.push(extraFilters); - } - - getCallNumber() { - return this.callNumber; - } - - startRead() { - /* If the stream has ended with an error, we should not emit any more - * messages and we should communicate that the stream has ended */ - if (this.finalStatus !== null && this.finalStatus.code !== Status.OK) { - this.readsClosed = true; - this.maybeOutputStatus(); - return; - } - this.canPush = true; - if (this.http2Stream === null) { - this.pendingRead = true; - } else { - if (this.unpushedReadMessages.length > 0) { - const nextMessage: Buffer = this.unpushedReadMessages.shift()!; - this.push(nextMessage); - return; - } - /* Only resume reading from the http2Stream if we don't have any pending - * messages to emit */ - this.http2Stream.resume(); - } - } - - private maybeCloseWrites() { - if ( - this.writesClosed && - !this.isWriteFilterPending && - this.http2Stream !== null - ) { - this.trace('calling end() on HTTP/2 stream'); - this.http2Stream.end(); - } - } - - sendMessageWithContext(context: MessageContext, message: Buffer) { - this.trace('write() called with message of length ' + message.length); - const writeObj: WriteObject = { - message, - flags: context.flags, - }; - const cb: WriteCallback = (error?: Error | null) => { - let code: Status = Status.UNAVAILABLE; - if ((error as NodeJS.ErrnoException)?.code === 'ERR_STREAM_WRITE_AFTER_END') { - code = Status.INTERNAL; - } - if (error) { - this.cancelWithStatus(code, `Write error: ${error.message}`); - } - context.callback?.(); - }; - this.isWriteFilterPending = true; - this.filterStack.sendMessage(Promise.resolve(writeObj)).then((message) => { - this.isWriteFilterPending = false; - if (this.http2Stream === null) { - this.trace( - 'deferring writing data chunk of length ' + message.message.length - ); - this.pendingWrite = message.message; - this.pendingWriteCallback = cb; - } else { - this.trace('sending data chunk of length ' + message.message.length); - try { - this.writeMessageToStream(message.message, cb); - } catch (error) { - this.endCall({ - code: Status.UNAVAILABLE, - details: `Write failed with error ${error.message}`, - metadata: new Metadata() - }); - } - this.maybeCloseWrites(); - } - }, this.handleFilterError.bind(this)); - } - - halfClose() { - this.trace('end() called'); - this.writesClosed = true; - this.maybeCloseWrites(); - } -} diff --git a/packages/grpc-js/src/call.ts b/packages/grpc-js/src/call.ts index fcc3159db..c587a195d 100644 --- a/packages/grpc-js/src/call.ts +++ b/packages/grpc-js/src/call.ts @@ -18,7 +18,7 @@ import { EventEmitter } from 'events'; import { Duplex, Readable, Writable } from 'stream'; -import { StatusObject, MessageContext } from './call-stream'; +import { StatusObject, MessageContext } from './call-interface'; import { Status } from './constants'; import { EmitterAugmentation1 } from './events'; import { Metadata } from './metadata'; diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 635b52d6f..aaf14bb22 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -15,57 +15,15 @@ * */ -import { - Deadline, - Call, - Http2CallStream, - CallStreamOptions, -} from './call-stream'; import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; -import { ResolvingLoadBalancer } from './resolving-load-balancer'; -import { SubchannelPool, getSubchannelPool } from './subchannel-pool'; -import { ChannelControlHelper } from './load-balancer'; -import { UnavailablePicker, Picker, PickResultType } from './picker'; -import { Metadata } from './metadata'; -import { Status, LogVerbosity, Propagate } from './constants'; -import { FilterStackFactory } from './filter-stack'; -import { CallCredentialsFilterFactory } from './call-credentials-filter'; -import { DeadlineFilterFactory } from './deadline-filter'; -import { CompressionFilterFactory } from './compression-filter'; -import { - CallConfig, - ConfigSelector, - getDefaultAuthority, - mapUriDefaultScheme, -} from './resolver'; -import { trace, log } from './logging'; -import { SubchannelAddress } from './subchannel-address'; -import { MaxMessageSizeFilterFactory } from './max-message-size-filter'; -import { mapProxyName } from './http_proxy'; -import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; -import { Filter } from './filter'; import { ConnectivityState } from './connectivity-state'; -import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; -import { Subchannel } from './subchannel'; - -/** - * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args - */ -const MAX_TIMEOUT_TIME = 2147483647; - -let nextCallNumber = 0; - -function getNewCallNumber(): number { - const callNumber = nextCallNumber; - nextCallNumber += 1; - if (nextCallNumber >= Number.MAX_SAFE_INTEGER) { - nextCallNumber = 0; - } - return callNumber; -} +import { ChannelRef } from './channelz'; +import { Call } from './call-interface'; +import { InternalChannel } from './internal-channel'; +import { Deadline } from './deadline'; /** * An interface that represents a communication channel to a server specified @@ -132,57 +90,14 @@ export interface Channel { ): Call; } -interface ConnectivityStateWatcher { - currentState: ConnectivityState; - timer: NodeJS.Timeout | null; - callback: (error?: Error) => void; -} - export class ChannelImplementation implements Channel { - private resolvingLoadBalancer: ResolvingLoadBalancer; - private subchannelPool: SubchannelPool; - private connectivityState: ConnectivityState = ConnectivityState.IDLE; - private currentPicker: Picker = new UnavailablePicker(); - /** - * Calls queued up to get a call config. Should only be populated before the - * first time the resolver returns a result, which includes the ConfigSelector. - */ - private configSelectionQueue: Array<{ - callStream: Http2CallStream; - callMetadata: Metadata; - }> = []; - private pickQueue: Array<{ - callStream: Http2CallStream; - callMetadata: Metadata; - callConfig: CallConfig; - dynamicFilters: Filter[]; - }> = []; - private connectivityStateWatchers: ConnectivityStateWatcher[] = []; - private defaultAuthority: string; - private filterStackFactory: FilterStackFactory; - private target: GrpcUri; - /** - * This timer does not do anything on its own. Its purpose is to hold the - * event loop open while there are any pending calls for the channel that - * have not yet been assigned to specific subchannels. In other words, - * the invariant is that callRefTimer is reffed if and only if pickQueue - * is non-empty. - */ - private callRefTimer: NodeJS.Timer; - private configSelector: ConfigSelector | null = null; - // Channelz info - private readonly channelzEnabled: boolean = true; - private originalTarget: string; - private channelzRef: ChannelRef; - private channelzTrace: ChannelzTrace; - private callTracker = new ChannelzCallTracker(); - private childrenTracker = new ChannelzChildrenTracker(); + private internalChannel: InternalChannel; constructor( target: string, - private readonly credentials: ChannelCredentials, - private readonly options: ChannelOptions + credentials: ChannelCredentials, + options: ChannelOptions ) { if (typeof target !== 'string') { throw new TypeError('Channel target must be a string'); @@ -197,497 +112,20 @@ export class ChannelImplementation implements Channel { throw new TypeError('Channel options must be an object'); } } - this.originalTarget = target; - const originalTargetUri = parseUri(target); - if (originalTargetUri === null) { - throw new Error(`Could not parse target name "${target}"`); - } - /* This ensures that the target has a scheme that is registered with the - * resolver */ - const defaultSchemeMapResult = mapUriDefaultScheme(originalTargetUri); - if (defaultSchemeMapResult === null) { - throw new Error( - `Could not find a default scheme for target name "${target}"` - ); - } - - this.callRefTimer = setInterval(() => {}, MAX_TIMEOUT_TIME); - this.callRefTimer.unref?.(); - - if (this.options['grpc.enable_channelz'] === 0) { - this.channelzEnabled = false; - } - - this.channelzTrace = new ChannelzTrace(); - if (this.channelzEnabled) { - this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); - this.channelzTrace.addTrace('CT_INFO', 'Channel created'); - } else { - // Dummy channelz ref that will never be used - this.channelzRef = { - kind: 'channel', - id: -1, - name: '' - }; - } - - if (this.options['grpc.default_authority']) { - this.defaultAuthority = this.options['grpc.default_authority'] as string; - } else { - this.defaultAuthority = getDefaultAuthority(defaultSchemeMapResult); - } - const proxyMapResult = mapProxyName(defaultSchemeMapResult, options); - this.target = proxyMapResult.target; - this.options = Object.assign({}, this.options, proxyMapResult.extraOptions); - - /* The global boolean parameter to getSubchannelPool has the inverse meaning to what - * the grpc.use_local_subchannel_pool channel option means. */ - this.subchannelPool = getSubchannelPool( - (options['grpc.use_local_subchannel_pool'] ?? 0) === 0 - ); - const channelControlHelper: ChannelControlHelper = { - createSubchannel: ( - subchannelAddress: SubchannelAddress, - subchannelArgs: ChannelOptions - ) => { - const subchannel = this.subchannelPool.getOrCreateSubchannel( - this.target, - subchannelAddress, - Object.assign({}, this.options, subchannelArgs), - this.credentials - ); - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); - } - return subchannel; - }, - updateState: (connectivityState: ConnectivityState, picker: Picker) => { - this.currentPicker = picker; - const queueCopy = this.pickQueue.slice(); - this.pickQueue = []; - this.callRefTimerUnref(); - for (const { callStream, callMetadata, callConfig, dynamicFilters } of queueCopy) { - this.tryPick(callStream, callMetadata, callConfig, dynamicFilters); - } - this.updateState(connectivityState); - }, - requestReresolution: () => { - // This should never be called. - throw new Error( - 'Resolving load balancer should never call requestReresolution' - ); - }, - addChannelzChild: (child: ChannelRef | SubchannelRef) => { - if (this.channelzEnabled) { - this.childrenTracker.refChild(child); - } - }, - removeChannelzChild: (child: ChannelRef | SubchannelRef) => { - if (this.channelzEnabled) { - this.childrenTracker.unrefChild(child); - } - } - }; - this.resolvingLoadBalancer = new ResolvingLoadBalancer( - this.target, - channelControlHelper, - options, - (configSelector) => { - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); - } - this.configSelector = configSelector; - /* We process the queue asynchronously to ensure that the corresponding - * load balancer update has completed. */ - process.nextTick(() => { - const localQueue = this.configSelectionQueue; - this.configSelectionQueue = []; - this.callRefTimerUnref(); - for (const { callStream, callMetadata } of localQueue) { - this.tryGetConfig(callStream, callMetadata); - } - this.configSelectionQueue = []; - }); - }, - (status) => { - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); - } - if (this.configSelectionQueue.length > 0) { - this.trace('Name resolution failed with calls queued for config selection'); - } - const localQueue = this.configSelectionQueue; - this.configSelectionQueue = []; - this.callRefTimerUnref(); - for (const { callStream, callMetadata } of localQueue) { - if (callMetadata.getOptions().waitForReady) { - this.callRefTimerRef(); - this.configSelectionQueue.push({ callStream, callMetadata }); - } else { - callStream.cancelWithStatus(status.code, status.details); - } - } - } - ); - this.filterStackFactory = new FilterStackFactory([ - new CallCredentialsFilterFactory(this), - new DeadlineFilterFactory(this), - new MaxMessageSizeFilterFactory(this.options), - new CompressionFilterFactory(this, this.options), - ]); - this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2)); - const error = new Error(); - trace(LogVerbosity.DEBUG, 'channel_stacktrace', '(' + this.channelzRef.id + ') ' + 'Channel constructed \n' + error.stack?.substring(error.stack.indexOf('\n')+1)); - } - - private getChannelzInfo(): ChannelInfo { - return { - target: this.originalTarget, - state: this.connectivityState, - trace: this.channelzTrace, - callTracker: this.callTracker, - children: this.childrenTracker.getChildLists() - }; - } - - private trace(text: string, verbosityOverride?: LogVerbosity) { - trace(verbosityOverride ?? LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + uriToString(this.target) + ' ' + text); - } - - private callRefTimerRef() { - // If the hasRef function does not exist, always run the code - if (!this.callRefTimer.hasRef?.()) { - this.trace( - 'callRefTimer.ref | configSelectionQueue.length=' + - this.configSelectionQueue.length + - ' pickQueue.length=' + - this.pickQueue.length - ); - this.callRefTimer.ref?.(); - } - } - - private callRefTimerUnref() { - // If the hasRef function does not exist, always run the code - if (!this.callRefTimer.hasRef || this.callRefTimer.hasRef()) { - this.trace( - 'callRefTimer.unref | configSelectionQueue.length=' + - this.configSelectionQueue.length + - ' pickQueue.length=' + - this.pickQueue.length - ); - this.callRefTimer.unref?.(); - } - } - - private pushPick( - callStream: Http2CallStream, - callMetadata: Metadata, - callConfig: CallConfig, - dynamicFilters: Filter[] - ) { - this.pickQueue.push({ callStream, callMetadata, callConfig, dynamicFilters }); - this.callRefTimerRef(); - } - - /** - * Check the picker output for the given call and corresponding metadata, - * and take any relevant actions. Should not be called while iterating - * over pickQueue. - * @param callStream - * @param callMetadata - */ - private tryPick( - callStream: Http2CallStream, - callMetadata: Metadata, - callConfig: CallConfig, - dynamicFilters: Filter[] - ) { - const pickResult = this.currentPicker.pick({ - metadata: callMetadata, - extraPickInfo: callConfig.pickInformation, - }); - const subchannelString = pickResult.subchannel ? - '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : - '' + pickResult.subchannel; - this.trace( - 'Pick result for call [' + - callStream.getCallNumber() + - ']: ' + - PickResultType[pickResult.pickResultType] + - ' subchannel: ' + - subchannelString + - ' status: ' + - pickResult.status?.code + - ' ' + - pickResult.status?.details - ); - switch (pickResult.pickResultType) { - case PickResultType.COMPLETE: - if (pickResult.subchannel === null) { - callStream.cancelWithStatus( - Status.UNAVAILABLE, - 'Request dropped by load balancing policy' - ); - // End the call with an error - } else { - /* If the subchannel is not in the READY state, that indicates a bug - * somewhere in the load balancer or picker. So, we log an error and - * queue the pick to be tried again later. */ - if ( - pickResult.subchannel!.getConnectivityState() !== - ConnectivityState.READY - ) { - log( - LogVerbosity.ERROR, - 'Error: COMPLETE pick result subchannel ' + - subchannelString + - ' has state ' + - ConnectivityState[pickResult.subchannel!.getConnectivityState()] - ); - this.pushPick(callStream, callMetadata, callConfig, dynamicFilters); - break; - } - /* We need to clone the callMetadata here because the transparent - * retry code in the promise resolution handler use the same - * callMetadata object, so it needs to stay unmodified */ - callStream.filterStack - .sendMetadata(Promise.resolve(callMetadata.clone())) - .then( - (finalMetadata) => { - const subchannelState: ConnectivityState = pickResult.subchannel!.getConnectivityState(); - if (subchannelState === ConnectivityState.READY) { - try { - const pickExtraFilters = pickResult.extraFilterFactories.map(factory => factory.createFilter(callStream)); - pickResult.subchannel?.getRealSubchannel().startCallStream( - finalMetadata, - callStream, - [...dynamicFilters, ...pickExtraFilters] - ); - /* If we reach this point, the call stream has started - * successfully */ - callConfig.onCommitted?.(); - pickResult.onCallStarted?.(); - } catch (error) { - const errorCode = (error as NodeJS.ErrnoException).code; - if (errorCode === 'ERR_HTTP2_GOAWAY_SESSION' || - errorCode === 'ERR_HTTP2_INVALID_SESSION' - ) { - /* An error here indicates that something went wrong with - * the picked subchannel's http2 stream right before we - * tried to start the stream. We are handling a promise - * result here, so this is asynchronous with respect to the - * original tryPick call, so calling it again is not - * recursive. We call tryPick immediately instead of - * queueing this pick again because handling the queue is - * triggered by state changes, and we want to immediately - * check if the state has already changed since the - * previous tryPick call. We do this instead of cancelling - * the stream because the correct behavior may be - * re-queueing instead, based on the logic in the rest of - * tryPick */ - this.trace( - 'Failed to start call on picked subchannel ' + - subchannelString + - ' with error ' + - (error as Error).message + - '. Retrying pick', - LogVerbosity.INFO - ); - this.tryPick(callStream, callMetadata, callConfig, dynamicFilters); - } else { - this.trace( - 'Failed to start call on picked subchanel ' + - subchannelString + - ' with error ' + - (error as Error).message + - '. Ending call', - LogVerbosity.INFO - ); - callStream.cancelWithStatus( - Status.INTERNAL, - `Failed to start HTTP/2 stream with error: ${ - (error as Error).message - }` - ); - } - } - } else { - /* The logic for doing this here is the same as in the catch - * block above */ - this.trace( - 'Picked subchannel ' + - subchannelString + - ' has state ' + - ConnectivityState[subchannelState] + - ' after metadata filters. Retrying pick', - LogVerbosity.INFO - ); - this.tryPick(callStream, callMetadata, callConfig, dynamicFilters); - } - }, - (error: Error & { code: number }) => { - // We assume the error code isn't 0 (Status.OK) - callStream.cancelWithStatus( - typeof error.code === 'number' ? error.code : Status.UNKNOWN, - `Getting metadata from plugin failed with error: ${error.message}` - ); - } - ); - } - break; - case PickResultType.QUEUE: - this.pushPick(callStream, callMetadata, callConfig, dynamicFilters); - break; - case PickResultType.TRANSIENT_FAILURE: - if (callMetadata.getOptions().waitForReady) { - this.pushPick(callStream, callMetadata, callConfig, dynamicFilters); - } else { - callStream.cancelWithStatus( - pickResult.status!.code, - pickResult.status!.details - ); - } - break; - case PickResultType.DROP: - callStream.cancelWithStatus( - pickResult.status!.code, - pickResult.status!.details - ); - break; - default: - throw new Error( - `Invalid state: unknown pickResultType ${pickResult.pickResultType}` - ); - } - } - - private removeConnectivityStateWatcher( - watcherObject: ConnectivityStateWatcher - ) { - const watcherIndex = this.connectivityStateWatchers.findIndex( - (value) => value === watcherObject - ); - if (watcherIndex >= 0) { - this.connectivityStateWatchers.splice(watcherIndex, 1); - } - } - - private updateState(newState: ConnectivityState): void { - trace( - LogVerbosity.DEBUG, - 'connectivity_state', - '(' + this.channelzRef.id + ') ' + - uriToString(this.target) + - ' ' + - ConnectivityState[this.connectivityState] + - ' -> ' + - ConnectivityState[newState] - ); - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); - } - this.connectivityState = newState; - const watchersCopy = this.connectivityStateWatchers.slice(); - for (const watcherObject of watchersCopy) { - if (newState !== watcherObject.currentState) { - if (watcherObject.timer) { - clearTimeout(watcherObject.timer); - } - this.removeConnectivityStateWatcher(watcherObject); - watcherObject.callback(); - } - } - } - - private tryGetConfig(stream: Http2CallStream, metadata: Metadata) { - if (stream.getStatus() !== null) { - /* If the stream has a status, it has already finished and we don't need - * to take any more actions on it. */ - return; - } - if (this.configSelector === null) { - /* This branch will only be taken at the beginning of the channel's life, - * before the resolver ever returns a result. So, the - * ResolvingLoadBalancer may be idle and if so it needs to be kicked - * because it now has a pending request. */ - this.resolvingLoadBalancer.exitIdle(); - this.configSelectionQueue.push({ - callStream: stream, - callMetadata: metadata, - }); - this.callRefTimerRef(); - } else { - const callConfig = this.configSelector(stream.getMethod(), metadata); - if (callConfig.status === Status.OK) { - if (callConfig.methodConfig.timeout) { - const deadline = new Date(); - deadline.setSeconds( - deadline.getSeconds() + callConfig.methodConfig.timeout.seconds - ); - deadline.setMilliseconds( - deadline.getMilliseconds() + - callConfig.methodConfig.timeout.nanos / 1_000_000 - ); - stream.setConfigDeadline(deadline); - // Refreshing the filters makes the deadline filter pick up the new deadline - stream.filterStack.refresh(); - } - if (callConfig.dynamicFilterFactories.length > 0) { - /* These dynamicFilters are the mechanism for implementing gRFC A39: - * https://github.com/grpc/proposal/blob/master/A39-xds-http-filters.md - * We run them here instead of with the rest of the filters because - * that spec says "the xDS HTTP filters will run in between name - * resolution and load balancing". - * - * We use the filter stack here to simplify the multi-filter async - * waterfall logic, but we pass along the underlying list of filters - * to avoid having nested filter stacks when combining it with the - * original filter stack. We do not pass along the original filter - * factory list because these filters may need to persist data - * between sending headers and other operations. */ - const dynamicFilterStackFactory = new FilterStackFactory(callConfig.dynamicFilterFactories); - const dynamicFilterStack = dynamicFilterStackFactory.createFilter(stream); - dynamicFilterStack.sendMetadata(Promise.resolve(metadata)).then(filteredMetadata => { - this.tryPick(stream, filteredMetadata, callConfig, dynamicFilterStack.getFilters()); - }); - } else { - this.tryPick(stream, metadata, callConfig, []); - } - } else { - stream.cancelWithStatus( - callConfig.status, - 'Failed to route call to method ' + stream.getMethod() - ); - } - } - } - _startCallStream(stream: Http2CallStream, metadata: Metadata) { - this.tryGetConfig(stream, metadata.clone()); + this.internalChannel = new InternalChannel(target, credentials, options); } close() { - this.resolvingLoadBalancer.destroy(); - this.updateState(ConnectivityState.SHUTDOWN); - clearInterval(this.callRefTimer); - if (this.channelzEnabled) { - unregisterChannelzRef(this.channelzRef); - } - - this.subchannelPool.unrefUnusedSubchannels(); + this.internalChannel.close(); } getTarget() { - return uriToString(this.target); + return this.internalChannel.getTarget(); } getConnectivityState(tryToConnect: boolean) { - const connectivityState = this.connectivityState; - if (tryToConnect) { - this.resolvingLoadBalancer.exitIdle(); - } - return connectivityState; + return this.internalChannel.getConnectivityState(tryToConnect); } watchConnectivityState( @@ -695,34 +133,7 @@ export class ChannelImplementation implements Channel { deadline: Date | number, callback: (error?: Error) => void ): void { - if (this.connectivityState === ConnectivityState.SHUTDOWN) { - throw new Error('Channel has been shut down'); - } - let timer = null; - if (deadline !== Infinity) { - const deadlineDate: Date = - deadline instanceof Date ? deadline : new Date(deadline); - const now = new Date(); - if (deadline === -Infinity || deadlineDate <= now) { - process.nextTick( - callback, - new Error('Deadline passed without connectivity state change') - ); - return; - } - timer = setTimeout(() => { - this.removeConnectivityStateWatcher(watcherObject); - callback( - new Error('Deadline passed without connectivity state change') - ); - }, deadlineDate.getTime() - now.getTime()); - } - const watcherObject = { - currentState, - callback, - timer, - }; - this.connectivityStateWatchers.push(watcherObject); + this.internalChannel.watchConnectivityState(currentState, deadline, callback); } /** @@ -731,7 +142,7 @@ export class ChannelImplementation implements Channel { * @returns */ getChannelzRef() { - return this.channelzRef; + return this.internalChannel.getChannelzRef(); } createCall( @@ -749,42 +160,6 @@ export class ChannelImplementation implements Channel { 'Channel#createCall: deadline must be a number or Date' ); } - if (this.connectivityState === ConnectivityState.SHUTDOWN) { - throw new Error('Channel has been shut down'); - } - const callNumber = getNewCallNumber(); - this.trace( - 'createCall [' + - callNumber + - '] method="' + - method + - '", deadline=' + - deadline - ); - const finalOptions: CallStreamOptions = { - deadline: deadline, - flags: propagateFlags ?? Propagate.DEFAULTS, - host: host ?? this.defaultAuthority, - parentCall: parentCall, - }; - const stream: Http2CallStream = new Http2CallStream( - method, - this, - finalOptions, - this.filterStackFactory, - this.credentials._getCallCredentials(), - callNumber - ); - if (this.channelzEnabled) { - this.callTracker.addCallStarted(); - stream.addStatusWatcher(status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); - } - return stream; + return this.internalChannel.createCall(method, deadline, host, parentCall, propagateFlags); } } diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index ddb296ff1..52320b0b3 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -28,7 +28,7 @@ import { isInterceptingListener, MessageContext, Call, -} from './call-stream'; +} from './call-interface'; import { Status } from './constants'; import { Channel } from './channel'; import { CallOptions } from './client'; diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index ed9407cd8..688d8016c 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -29,7 +29,7 @@ import { SurfaceCall, } from './call'; import { CallCredentials } from './call-credentials'; -import { Deadline, StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { Channel, ChannelImplementation } from './channel'; import { ConnectivityState } from './connectivity-state'; import { ChannelCredentials } from './channel-credentials'; @@ -50,6 +50,7 @@ import { ServerWritableStream, ServerDuplexStream, } from './server-call'; +import { Deadline } from './deadline'; const CHANNEL_SYMBOL = Symbol(); const INTERCEPTOR_SYMBOL = Symbol(); diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 40825467e..f87614114 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -17,7 +17,7 @@ import * as zlib from 'zlib'; -import { Call, WriteObject, WriteFlags } from './call-stream'; +import { WriteObject, WriteFlags } from './call-interface'; import { Channel } from './channel'; import { ChannelOptions } from './channel-options'; import { CompressionAlgorithms } from './compression-algorithms'; @@ -283,7 +283,7 @@ export class CompressionFilterFactory implements FilterFactory { private sharedFilterConfig: SharedCompressionFilterConfig = {}; constructor(private readonly channel: Channel, private readonly options: ChannelOptions) {} - createFilter(callStream: Call): CompressionFilter { + createFilter(): CompressionFilter { return new CompressionFilter(this.options, this.sharedFilterConfig); } } diff --git a/packages/grpc-js/src/deadline-filter.ts b/packages/grpc-js/src/deadline-filter.ts deleted file mode 100644 index 7bdd764f2..000000000 --- a/packages/grpc-js/src/deadline-filter.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 { Call, StatusObject } from './call-stream'; -import { Channel } from './channel'; -import { Status } from './constants'; -import { BaseFilter, Filter, FilterFactory } from './filter'; -import { Metadata } from './metadata'; - -const units: Array<[string, number]> = [ - ['m', 1], - ['S', 1000], - ['M', 60 * 1000], - ['H', 60 * 60 * 1000], -]; - -function getDeadline(deadline: number) { - const now = new Date().getTime(); - const timeoutMs = Math.max(deadline - now, 0); - for (const [unit, factor] of units) { - const amount = timeoutMs / factor; - if (amount < 1e8) { - return String(Math.ceil(amount)) + unit; - } - } - throw new Error('Deadline is too far in the future'); -} - -export class DeadlineFilter extends BaseFilter implements Filter { - private timer: NodeJS.Timer | null = null; - private deadline = Infinity; - constructor( - private readonly channel: Channel, - private readonly callStream: Call - ) { - super(); - this.retreiveDeadline(); - this.runTimer(); - } - - private retreiveDeadline() { - const callDeadline = this.callStream.getDeadline(); - if (callDeadline instanceof Date) { - this.deadline = callDeadline.getTime(); - } else { - this.deadline = callDeadline; - } - } - - private runTimer() { - if (this.timer) { - clearTimeout(this.timer); - } - const now: number = new Date().getTime(); - const timeout = this.deadline - now; - if (timeout <= 0) { - process.nextTick(() => { - this.callStream.cancelWithStatus( - Status.DEADLINE_EXCEEDED, - 'Deadline exceeded' - ); - }); - } else if (this.deadline !== Infinity) { - this.timer = setTimeout(() => { - this.callStream.cancelWithStatus( - Status.DEADLINE_EXCEEDED, - 'Deadline exceeded' - ); - }, timeout); - this.timer.unref?.(); - } - } - - refresh() { - this.retreiveDeadline(); - this.runTimer(); - } - - async sendMetadata(metadata: Promise) { - if (this.deadline === Infinity) { - return metadata; - } - /* The input metadata promise depends on the original channel.connect() - * promise, so when it is complete that implies that the channel is - * connected */ - const finalMetadata = await metadata; - const timeoutString = getDeadline(this.deadline); - finalMetadata.set('grpc-timeout', timeoutString); - return finalMetadata; - } - - receiveTrailers(status: StatusObject) { - if (this.timer) { - clearTimeout(this.timer); - } - return status; - } -} - -export class DeadlineFilterFactory implements FilterFactory { - constructor(private readonly channel: Channel) {} - - createFilter(callStream: Call): DeadlineFilter { - return new DeadlineFilter(this.channel, callStream); - } -} diff --git a/packages/grpc-js/src/deadline.ts b/packages/grpc-js/src/deadline.ts new file mode 100644 index 000000000..58ea0a805 --- /dev/null +++ b/packages/grpc-js/src/deadline.ts @@ -0,0 +1,58 @@ +/* + * 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. + * + */ + +export type Deadline = Date | number; + +export function minDeadline(...deadlineList: Deadline[]): Deadline { + let minValue = Infinity; + for (const deadline of deadlineList) { + const deadlineMsecs = + deadline instanceof Date ? deadline.getTime() : deadline; + if (deadlineMsecs < minValue) { + minValue = deadlineMsecs; + } + } + return minValue; +} + +const units: Array<[string, number]> = [ + ['m', 1], + ['S', 1000], + ['M', 60 * 1000], + ['H', 60 * 60 * 1000], +]; + +export function getDeadlineTimeoutString(deadline: Deadline) { + const now = new Date().getTime(); + if (deadline instanceof Date) { + deadline = deadline.getTime(); + } + const timeoutMs = Math.max(deadline - now, 0); + for (const [unit, factor] of units) { + const amount = timeoutMs / factor; + if (amount < 1e8) { + return String(Math.ceil(amount)) + unit; + } + } + throw new Error('Deadline is too far in the future') +} + +export function getRelativeTimeout(deadline: Deadline) { + const deadlineMs = deadline instanceof Date ? deadline.getTime() : deadline; + const now = new Date().getTime(); + return deadlineMs - now; +} \ No newline at end of file diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index fcafbeb01..f286bbcd6 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -31,7 +31,7 @@ export { PickArgs, PickResultType, } from './picker'; -export { Call as CallStream } from './call-stream'; +export { Call as CallStream } from './call-interface'; export { Filter, BaseFilter, FilterFactory } from './filter'; export { FilterStackFactory } from './filter-stack'; export { registerAdminService } from './admin'; diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts index a9e754428..f44c43839 100644 --- a/packages/grpc-js/src/filter-stack.ts +++ b/packages/grpc-js/src/filter-stack.ts @@ -15,14 +15,14 @@ * */ -import { Call, StatusObject, WriteObject } from './call-stream'; +import { StatusObject, WriteObject } from './call-interface'; import { Filter, FilterFactory } from './filter'; import { Metadata } from './metadata'; export class FilterStack implements Filter { constructor(private readonly filters: Filter[]) {} - sendMetadata(metadata: Promise) { + sendMetadata(metadata: Promise): Promise { let result: Promise = metadata; for (let i = 0; i < this.filters.length; i++) { @@ -72,12 +72,6 @@ export class FilterStack implements Filter { return result; } - refresh(): void { - for (const filter of this.filters) { - filter.refresh(); - } - } - push(filters: Filter[]) { this.filters.unshift(...filters); } @@ -94,9 +88,9 @@ export class FilterStackFactory implements FilterFactory { this.factories.unshift(...filterFactories); } - createFilter(callStream: Call): FilterStack { + createFilter(): FilterStack { return new FilterStack( - this.factories.map((factory) => factory.createFilter(callStream)) + this.factories.map((factory) => factory.createFilter()) ); } } diff --git a/packages/grpc-js/src/filter.ts b/packages/grpc-js/src/filter.ts index 8475a0a5a..5313f91a8 100644 --- a/packages/grpc-js/src/filter.ts +++ b/packages/grpc-js/src/filter.ts @@ -15,12 +15,14 @@ * */ -import { Call, StatusObject, WriteObject } from './call-stream'; +import { StatusObject, WriteObject } from './call-interface'; import { Metadata } from './metadata'; /** * Filter classes represent related per-call logic and state that is primarily - * used to modify incoming and outgoing data + * used to modify incoming and outgoing data. All async filters can be + * rejected. The rejection error must be a StatusObject, and a rejection will + * cause the call to end with that status. */ export interface Filter { sendMetadata(metadata: Promise): Promise; @@ -32,8 +34,6 @@ export interface Filter { receiveMessage(message: Promise): Promise; receiveTrailers(status: StatusObject): StatusObject; - - refresh(): void; } export abstract class BaseFilter implements Filter { @@ -56,10 +56,8 @@ export abstract class BaseFilter implements Filter { receiveTrailers(status: StatusObject): StatusObject { return status; } - - refresh(): void {} } export interface FilterFactory { - createFilter(callStream: Call): T; + createFilter(): T; } diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index b4855fb59..786fa9925 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -23,7 +23,7 @@ import { ServiceError, } from './call'; import { CallCredentials, OAuth2Client } from './call-credentials'; -import { Deadline, StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { Channel, ChannelImplementation } from './channel'; import { CompressionAlgorithms } from './compression-algorithms'; import { ConnectivityState } from './connectivity-state'; @@ -237,7 +237,7 @@ export const getClientChannel = (client: Client) => { export { StatusBuilder }; -export { Listener } from './call-stream'; +export { Listener } from './call-interface'; export { Requester, @@ -275,6 +275,7 @@ import * as load_balancer_pick_first from './load-balancer-pick-first'; import * as load_balancer_round_robin from './load-balancer-round-robin'; import * as load_balancer_outlier_detection from './load-balancer-outlier-detection'; import * as channelz from './channelz'; +import { Deadline } from './deadline'; const clientVersion = require('../../package.json').version; diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts new file mode 100644 index 000000000..772ed8e1d --- /dev/null +++ b/packages/grpc-js/src/internal-channel.ts @@ -0,0 +1,504 @@ +/* + * 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 { ChannelCredentials } from './channel-credentials'; +import { ChannelOptions } from './channel-options'; +import { ResolvingLoadBalancer } from './resolving-load-balancer'; +import { SubchannelPool, getSubchannelPool } from './subchannel-pool'; +import { ChannelControlHelper } from './load-balancer'; +import { UnavailablePicker, Picker, PickResultType } from './picker'; +import { Metadata } from './metadata'; +import { Status, LogVerbosity, Propagate } from './constants'; +import { FilterStackFactory } from './filter-stack'; +import { CompressionFilterFactory } from './compression-filter'; +import { + CallConfig, + ConfigSelector, + getDefaultAuthority, + mapUriDefaultScheme, +} from './resolver'; +import { trace, log } from './logging'; +import { SubchannelAddress } from './subchannel-address'; +import { MaxMessageSizeFilterFactory } from './max-message-size-filter'; +import { mapProxyName } from './http_proxy'; +import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; +import { ServerSurfaceCall } from './server-call'; +import { Filter } from './filter'; + +import { ConnectivityState } from './connectivity-state'; +import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; +import { Subchannel } from './subchannel'; +import { LoadBalancingCall } from './load-balancing-call'; +import { CallCredentials } from './call-credentials'; +import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from './call-interface'; +import { SubchannelCall } from './subchannel-call'; +import { Deadline, getDeadlineTimeoutString } from './deadline'; +import { ResolvingCall } from './resolving-call'; +import { getNextCallNumber } from './call-number'; + +/** + * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args + */ +const MAX_TIMEOUT_TIME = 2147483647; + +interface ConnectivityStateWatcher { + currentState: ConnectivityState; + timer: NodeJS.Timeout | null; + callback: (error?: Error) => void; +} + +export class InternalChannel { + + private resolvingLoadBalancer: ResolvingLoadBalancer; + private subchannelPool: SubchannelPool; + private connectivityState: ConnectivityState = ConnectivityState.IDLE; + private currentPicker: Picker = new UnavailablePicker(); + /** + * Calls queued up to get a call config. Should only be populated before the + * first time the resolver returns a result, which includes the ConfigSelector. + */ + private configSelectionQueue: ResolvingCall[] = []; + private pickQueue: LoadBalancingCall[] = []; + private connectivityStateWatchers: ConnectivityStateWatcher[] = []; + private defaultAuthority: string; + private filterStackFactory: FilterStackFactory; + private target: GrpcUri; + /** + * This timer does not do anything on its own. Its purpose is to hold the + * event loop open while there are any pending calls for the channel that + * have not yet been assigned to specific subchannels. In other words, + * the invariant is that callRefTimer is reffed if and only if pickQueue + * is non-empty. + */ + private callRefTimer: NodeJS.Timer; + private configSelector: ConfigSelector | null = null; + + // Channelz info + private readonly channelzEnabled: boolean = true; + private originalTarget: string; + private channelzRef: ChannelRef; + private channelzTrace: ChannelzTrace; + private callTracker = new ChannelzCallTracker(); + private childrenTracker = new ChannelzChildrenTracker(); + + constructor( + target: string, + private readonly credentials: ChannelCredentials, + private readonly options: ChannelOptions + ) { + if (typeof target !== 'string') { + throw new TypeError('Channel target must be a string'); + } + if (!(credentials instanceof ChannelCredentials)) { + throw new TypeError( + 'Channel credentials must be a ChannelCredentials object' + ); + } + if (options) { + if (typeof options !== 'object') { + throw new TypeError('Channel options must be an object'); + } + } + this.originalTarget = target; + const originalTargetUri = parseUri(target); + if (originalTargetUri === null) { + throw new Error(`Could not parse target name "${target}"`); + } + /* This ensures that the target has a scheme that is registered with the + * resolver */ + const defaultSchemeMapResult = mapUriDefaultScheme(originalTargetUri); + if (defaultSchemeMapResult === null) { + throw new Error( + `Could not find a default scheme for target name "${target}"` + ); + } + + this.callRefTimer = setInterval(() => {}, MAX_TIMEOUT_TIME); + this.callRefTimer.unref?.(); + + if (this.options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + + this.channelzTrace = new ChannelzTrace(); + if (this.channelzEnabled) { + this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo()); + this.channelzTrace.addTrace('CT_INFO', 'Channel created'); + } else { + // Dummy channelz ref that will never be used + this.channelzRef = { + kind: 'channel', + id: -1, + name: '' + }; + } + + if (this.options['grpc.default_authority']) { + this.defaultAuthority = this.options['grpc.default_authority'] as string; + } else { + this.defaultAuthority = getDefaultAuthority(defaultSchemeMapResult); + } + const proxyMapResult = mapProxyName(defaultSchemeMapResult, options); + this.target = proxyMapResult.target; + this.options = Object.assign({}, this.options, proxyMapResult.extraOptions); + + /* The global boolean parameter to getSubchannelPool has the inverse meaning to what + * the grpc.use_local_subchannel_pool channel option means. */ + this.subchannelPool = getSubchannelPool( + (options['grpc.use_local_subchannel_pool'] ?? 0) === 0 + ); + const channelControlHelper: ChannelControlHelper = { + createSubchannel: ( + subchannelAddress: SubchannelAddress, + subchannelArgs: ChannelOptions + ) => { + const subchannel = this.subchannelPool.getOrCreateSubchannel( + this.target, + subchannelAddress, + Object.assign({}, this.options, subchannelArgs), + this.credentials + ); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); + } + return subchannel; + }, + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + this.currentPicker = picker; + const queueCopy = this.pickQueue.slice(); + this.pickQueue = []; + this.callRefTimerUnref(); + for (const call of queueCopy) { + call.doPick(); + } + this.updateState(connectivityState); + }, + requestReresolution: () => { + // This should never be called. + throw new Error( + 'Resolving load balancer should never call requestReresolution' + ); + }, + addChannelzChild: (child: ChannelRef | SubchannelRef) => { + if (this.channelzEnabled) { + this.childrenTracker.refChild(child); + } + }, + removeChannelzChild: (child: ChannelRef | SubchannelRef) => { + if (this.channelzEnabled) { + this.childrenTracker.unrefChild(child); + } + } + }; + this.resolvingLoadBalancer = new ResolvingLoadBalancer( + this.target, + channelControlHelper, + options, + (configSelector) => { + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); + } + this.configSelector = configSelector; + /* We process the queue asynchronously to ensure that the corresponding + * load balancer update has completed. */ + process.nextTick(() => { + const localQueue = this.configSelectionQueue; + this.configSelectionQueue = []; + this.callRefTimerUnref(); + for (const call of localQueue) { + call.getConfig(); + } + this.configSelectionQueue = []; + }); + }, + (status) => { + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); + } + if (this.configSelectionQueue.length > 0) { + this.trace('Name resolution failed with calls queued for config selection'); + } + const localQueue = this.configSelectionQueue; + this.configSelectionQueue = []; + this.callRefTimerUnref(); + for (const call of localQueue) { + call.reportResolverError(status); + } + } + ); + this.filterStackFactory = new FilterStackFactory([ + new MaxMessageSizeFilterFactory(this.options), + new CompressionFilterFactory(this, this.options), + ]); + this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2)); + const error = new Error(); + trace(LogVerbosity.DEBUG, 'channel_stacktrace', '(' + this.channelzRef.id + ') ' + 'Channel constructed \n' + error.stack?.substring(error.stack.indexOf('\n')+1)); + } + + private getChannelzInfo(): ChannelInfo { + return { + target: this.originalTarget, + state: this.connectivityState, + trace: this.channelzTrace, + callTracker: this.callTracker, + children: this.childrenTracker.getChildLists() + }; + } + + private trace(text: string, verbosityOverride?: LogVerbosity) { + trace(verbosityOverride ?? LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + uriToString(this.target) + ' ' + text); + } + + private callRefTimerRef() { + // If the hasRef function does not exist, always run the code + if (!this.callRefTimer.hasRef?.()) { + this.trace( + 'callRefTimer.ref | configSelectionQueue.length=' + + this.configSelectionQueue.length + + ' pickQueue.length=' + + this.pickQueue.length + ); + this.callRefTimer.ref?.(); + } + } + + private callRefTimerUnref() { + // If the hasRef function does not exist, always run the code + if (!this.callRefTimer.hasRef || this.callRefTimer.hasRef()) { + this.trace( + 'callRefTimer.unref | configSelectionQueue.length=' + + this.configSelectionQueue.length + + ' pickQueue.length=' + + this.pickQueue.length + ); + this.callRefTimer.unref?.(); + } + } + + private removeConnectivityStateWatcher( + watcherObject: ConnectivityStateWatcher + ) { + const watcherIndex = this.connectivityStateWatchers.findIndex( + (value) => value === watcherObject + ); + if (watcherIndex >= 0) { + this.connectivityStateWatchers.splice(watcherIndex, 1); + } + } + + private updateState(newState: ConnectivityState): void { + trace( + LogVerbosity.DEBUG, + 'connectivity_state', + '(' + this.channelzRef.id + ') ' + + uriToString(this.target) + + ' ' + + ConnectivityState[this.connectivityState] + + ' -> ' + + ConnectivityState[newState] + ); + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + } + this.connectivityState = newState; + const watchersCopy = this.connectivityStateWatchers.slice(); + for (const watcherObject of watchersCopy) { + if (newState !== watcherObject.currentState) { + if (watcherObject.timer) { + clearTimeout(watcherObject.timer); + } + this.removeConnectivityStateWatcher(watcherObject); + watcherObject.callback(); + } + } + } + + doPick(metadata: Metadata, extraPickInfo: {[key: string]: string}) { + return this.currentPicker.pick({metadata: metadata, extraPickInfo: extraPickInfo}); + } + + queueCallForPick(call: LoadBalancingCall) { + this.pickQueue.push(call); + this.callRefTimerRef(); + } + + getConfig(method: string, metadata: Metadata) { + this.resolvingLoadBalancer.exitIdle(); + return this.configSelector?.(method, metadata) ?? null; + } + + queueCallForConfig(call: ResolvingCall) { + this.configSelectionQueue.push(call); + this.callRefTimerRef(); + } + + createLoadBalancingCall( + callConfig: CallConfig, + method: string, + host: string, + credentials: CallCredentials, + deadline: Deadline + ): LoadBalancingCall { + const callNumber = getNextCallNumber(); + this.trace( + 'createLoadBalancingCall [' + + callNumber + + '] method="' + + method + + '"' + ); + return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, this.filterStackFactory, callNumber); + } + + createInnerCall( + callConfig: CallConfig, + method: string, + host: string, + credentials: CallCredentials, + deadline: Deadline + ): Call { + // Create a RetryingCall if retries are enabled + return this.createLoadBalancingCall(callConfig, method, host, credentials, deadline); + } + + createResolvingCall( + method: string, + deadline: Deadline, + host: string | null | undefined, + parentCall: ServerSurfaceCall | null, + propagateFlags: number | null | undefined + ): ResolvingCall { + const callNumber = getNextCallNumber(); + this.trace( + 'createResolvingCall [' + + callNumber + + '] method="' + + method + + '", deadline=' + + deadline + ); + const finalOptions: CallStreamOptions = { + deadline: deadline, + flags: propagateFlags ?? Propagate.DEFAULTS, + host: host ?? this.defaultAuthority, + parentCall: parentCall, + }; + + const call = new ResolvingCall(this, method, finalOptions, this.filterStackFactory, this.credentials._getCallCredentials(), getNextCallNumber()); + + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + call.addStatusWatcher(status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + } + return call; + + } + + close() { + this.resolvingLoadBalancer.destroy(); + this.updateState(ConnectivityState.SHUTDOWN); + clearInterval(this.callRefTimer); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } + + this.subchannelPool.unrefUnusedSubchannels(); + } + + getTarget() { + return uriToString(this.target); + } + + getConnectivityState(tryToConnect: boolean) { + const connectivityState = this.connectivityState; + if (tryToConnect) { + this.resolvingLoadBalancer.exitIdle(); + } + return connectivityState; + } + + watchConnectivityState( + currentState: ConnectivityState, + deadline: Date | number, + callback: (error?: Error) => void + ): void { + if (this.connectivityState === ConnectivityState.SHUTDOWN) { + throw new Error('Channel has been shut down'); + } + let timer = null; + if (deadline !== Infinity) { + const deadlineDate: Date = + deadline instanceof Date ? deadline : new Date(deadline); + const now = new Date(); + if (deadline === -Infinity || deadlineDate <= now) { + process.nextTick( + callback, + new Error('Deadline passed without connectivity state change') + ); + return; + } + timer = setTimeout(() => { + this.removeConnectivityStateWatcher(watcherObject); + callback( + new Error('Deadline passed without connectivity state change') + ); + }, deadlineDate.getTime() - now.getTime()); + } + const watcherObject = { + currentState, + callback, + timer, + }; + this.connectivityStateWatchers.push(watcherObject); + } + + /** + * Get the channelz reference object for this channel. The returned value is + * garbage if channelz is disabled for this channel. + * @returns + */ + getChannelzRef() { + return this.channelzRef; + } + + createCall( + method: string, + deadline: Deadline, + host: string | null | undefined, + parentCall: ServerSurfaceCall | null, + propagateFlags: number | null | undefined + ): Call { + if (typeof method !== 'string') { + throw new TypeError('Channel#createCall: method must be a string'); + } + if (!(typeof deadline === 'number' || deadline instanceof Date)) { + throw new TypeError( + 'Channel#createCall: deadline must be a number or Date' + ); + } + if (this.connectivityState === ConnectivityState.SHUTDOWN) { + throw new Error('Channel has been shut down'); + } + return this.createResolvingCall(method, deadline, host, parentCall, propagateFlags); + } +} diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index e69e2ef99..5af0e79e7 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -15,8 +15,7 @@ * */ -import { ChannelOptions, connectivityState, StatusObject } from "."; -import { Call } from "./call-stream"; +import { ChannelOptions } from "./channel-options"; import { ConnectivityState } from "./connectivity-state"; import { Status } from "./constants"; import { durationToMs, isDuration, msToDuration } from "./duration"; @@ -311,28 +310,6 @@ interface MapEntry { subchannelWrappers: OutlierDetectionSubchannelWrapper[]; } -class OutlierDetectionCounterFilter extends BaseFilter implements Filter { - constructor(private callCounter: CallCounter) { - super(); - } - receiveTrailers(status: StatusObject): StatusObject { - if (status.code === Status.OK) { - this.callCounter.addSuccess(); - } else { - this.callCounter.addFailure(); - } - return status; - } -} - -class OutlierDetectionCounterFilterFactory implements FilterFactory { - constructor(private callCounter: CallCounter) {} - createFilter(callStream: Call): OutlierDetectionCounterFilter { - return new OutlierDetectionCounterFilter(this.callCounter); - } - -} - class OutlierDetectionPicker implements Picker { constructor(private wrappedPicker: Picker) {} pick(pickArgs: PickArgs): PickResult { @@ -344,7 +321,14 @@ class OutlierDetectionPicker implements Picker { return { ...wrappedPick, subchannel: subchannelWrapper.getWrappedSubchannel(), - extraFilterFactories: [...wrappedPick.extraFilterFactories, new OutlierDetectionCounterFilterFactory(mapEntry.counter)] + onCallEnded: statusCode => { + if (statusCode === Status.OK) { + mapEntry.counter.addSuccess(); + } else { + mapEntry.counter.addFailure(); + } + wrappedPick.onCallEnded?.(statusCode); + } }; } else { return wrappedPick; diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 884af50b7..bb95aba13 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -84,8 +84,8 @@ class PickFirstPicker implements Picker { pickResultType: PickResultType.COMPLETE, subchannel: this.subchannel, status: null, - extraFilterFactories: [], onCallStarted: null, + onCallEnded: null }; } } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 8a4094a02..91c10b238 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -78,8 +78,8 @@ class RoundRobinPicker implements Picker { pickResultType: PickResultType.COMPLETE, subchannel: pickedSubchannel, status: null, - extraFilterFactories: [], onCallStarted: null, + onCallEnded: null }; } diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts new file mode 100644 index 000000000..f791b86bc --- /dev/null +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -0,0 +1,281 @@ +/* + * Copyright 2022 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 { CallCredentials } from "./call-credentials"; +import { Call, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; +import { SubchannelCall } from "./subchannel-call"; +import { ConnectivityState } from "./connectivity-state"; +import { LogVerbosity, Status } from "./constants"; +import { Deadline, getDeadlineTimeoutString } from "./deadline"; +import { FilterStack, FilterStackFactory } from "./filter-stack"; +import { InternalChannel } from "./internal-channel"; +import { Metadata } from "./metadata"; +import { PickResultType } from "./picker"; +import { CallConfig } from "./resolver"; +import { splitHostPort } from "./uri-parser"; +import * as logging from './logging'; + +const TRACER_NAME = 'load_balancing_call'; + +export type RpcProgress = 'NOT_STARTED' | 'DROP' | 'REFUSED' | 'PROCESSED'; + +export interface StatusObjectWithProgress extends StatusObject { + progress: RpcProgress; +} + +export class LoadBalancingCall implements Call { + private child: SubchannelCall | null = null; + private readPending = false; + private writeFilterPending = false; + private pendingMessage: {context: MessageContext, message: Buffer} | null = null; + private pendingHalfClose = false; + private ended = false; + private serviceUrl: string; + private filterStack: FilterStack; + private metadata: Metadata | null = null; + private listener: InterceptingListener | null = null; + private onCallEnded: ((statusCode: Status) => void) | null = null; + constructor( + private readonly channel: InternalChannel, + private readonly callConfig: CallConfig, + private readonly methodName: string, + private readonly host : string, + private readonly credentials: CallCredentials, + private readonly deadline: Deadline, + filterStackFactory: FilterStackFactory, + private readonly callNumber: number + ) { + this.filterStack = filterStackFactory.createFilter(); + + const splitPath: string[] = this.methodName.split('/'); + let serviceName = ''; + /* The standard path format is "/{serviceName}/{methodName}", so if we split + * by '/', the first item should be empty and the second should be the + * service name */ + if (splitPath.length >= 2) { + serviceName = splitPath[1]; + } + const hostname = splitHostPort(this.host)?.host ?? 'localhost'; + /* Currently, call credentials are only allowed on HTTPS connections, so we + * can assume that the scheme is "https" */ + this.serviceUrl = `https://${hostname}/${serviceName}`; + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callNumber + '] ' + text + ); + } + + private outputStatus(status: StatusObject, progress: RpcProgress) { + if (!this.ended) { + this.ended = true; + this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); + const filteredStatus = this.filterStack.receiveTrailers(status); + const finalStatus = {...filteredStatus, progress}; + this.listener?.onReceiveStatus(finalStatus); + this.onCallEnded?.(finalStatus.code); + } + } + + doPick() { + if (this.ended) { + return; + } + if (!this.metadata) { + throw new Error('doPick called before start'); + } + const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation); + const subchannelString = pickResult.subchannel ? + '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : + '' + pickResult.subchannel; + this.trace( + 'Pick result: ' + + PickResultType[pickResult.pickResultType] + + ' subchannel: ' + + subchannelString + + ' status: ' + + pickResult.status?.code + + ' ' + + pickResult.status?.details + ); + switch (pickResult.pickResultType) { + case PickResultType.COMPLETE: + this.credentials.generateMetadata({service_url: this.serviceUrl}).then( + (credsMetadata) => { + const finalMetadata = this.metadata!.clone(); + finalMetadata.merge(credsMetadata); + if (finalMetadata.get('authorization').length > 1) { + this.outputStatus( + { + code: Status.INTERNAL, + details: '"authorization" metadata cannot have multiple values', + metadata: new Metadata() + }, + 'PROCESSED' + ); + } + if (pickResult.subchannel!.getConnectivityState() !== ConnectivityState.READY) { + this.trace( + 'Picked subchannel ' + + subchannelString + + ' has state ' + + ConnectivityState[pickResult.subchannel!.getConnectivityState()] + + ' after getting credentials metadata. Retrying pick' + ); + this.doPick(); + return; + } + + if (this.deadline !== Infinity) { + finalMetadata.set('grpc-timeout', getDeadlineTimeoutString(this.deadline)); + } + try { + this.child = pickResult.subchannel!.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, { + onReceiveMetadata: metadata => { + this.listener!.onReceiveMetadata(this.filterStack.receiveMetadata(metadata)); + }, + onReceiveMessage: message => { + this.filterStack.receiveMessage(message).then(filteredMesssage => { + this.listener!.onReceiveMessage(filteredMesssage); + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }); + }, + onReceiveStatus: status => { + this.outputStatus(status, 'PROCESSED'); + } + }); + } catch (error) { + this.trace( + 'Failed to start call on picked subchannel ' + + subchannelString + + ' with error ' + + (error as Error).message + ); + this.outputStatus( + { + code: Status.INTERNAL, + details: 'Failed to start HTTP/2 stream with error ' + (error as Error).message, + metadata: new Metadata() + }, + 'NOT_STARTED' + ); + return; + } + this.callConfig.onCommitted?.(); + pickResult.onCallStarted?.(); + this.onCallEnded = pickResult.onCallEnded; + this.trace('Created child call [' + this.child.getCallNumber() + ']'); + if (this.readPending) { + this.child.startRead(); + } + if (this.pendingMessage) { + this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); + } + if (this.pendingHalfClose && !this.writeFilterPending) { + this.child.halfClose(); + } + }, (error: Error & { code: number }) => { + // We assume the error code isn't 0 (Status.OK) + this.outputStatus( + { + code: typeof error.code === 'number' ? error.code : Status.UNKNOWN, + details: `Getting metadata from plugin failed with error: ${error.message}`, + metadata: new Metadata() + }, + 'PROCESSED' + ); + } + ); + break; + case PickResultType.DROP: + this.outputStatus(pickResult.status!, 'DROP'); + break; + case PickResultType.TRANSIENT_FAILURE: + if (this.metadata.getOptions().waitForReady) { + this.channel.queueCallForPick(this); + } else { + this.outputStatus(pickResult.status!, 'PROCESSED'); + } + break; + case PickResultType.QUEUE: + this.channel.queueCallForPick(this); + } + } + + cancelWithStatus(status: Status, details: string): void { + this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.child?.cancelWithStatus(status, details); + this.outputStatus({code: status, details: details, metadata: new Metadata()}, 'PROCESSED'); + } + getPeer(): string { + return this.child?.getPeer() ?? this.channel.getTarget(); + } + start(metadata: Metadata, listener: InterceptingListener): void { + this.trace('start called'); + this.listener = listener; + this.filterStack.sendMetadata(Promise.resolve(metadata)).then(filteredMetadata => { + this.metadata = filteredMetadata; + this.doPick(); + }, (status: StatusObject) => { + this.outputStatus(status, 'PROCESSED'); + }); + } + sendMessageWithContext(context: MessageContext, message: Buffer): void { + this.trace('write() called with message of length ' + message.length); + this.writeFilterPending = true; + this.filterStack.sendMessage(Promise.resolve({message: message, flags: context.flags})).then((filteredMessage) => { + this.writeFilterPending = false; + if (this.child) { + this.child.sendMessageWithContext(context, filteredMessage.message); + if (this.pendingHalfClose) { + this.child.halfClose(); + } + } else { + this.pendingMessage = {context, message: filteredMessage.message}; + } + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }) + } + startRead(): void { + this.trace('startRead called'); + if (this.child) { + this.child.startRead(); + } else { + this.readPending = true; + } + } + halfClose(): void { + this.trace('halfClose called'); + if (this.child && !this.writeFilterPending) { + this.child.halfClose(); + } else { + this.pendingHalfClose = true; + } + } + setCredentials(credentials: CallCredentials): void { + throw new Error("Method not implemented."); + } + + getCallNumber(): number { + return this.callNumber; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/max-message-size-filter.ts b/packages/grpc-js/src/max-message-size-filter.ts index 9a3bc9c0a..62d01077c 100644 --- a/packages/grpc-js/src/max-message-size-filter.ts +++ b/packages/grpc-js/src/max-message-size-filter.ts @@ -16,20 +16,20 @@ */ import { BaseFilter, Filter, FilterFactory } from './filter'; -import { Call, WriteObject } from './call-stream'; +import { WriteObject } from './call-interface'; import { Status, DEFAULT_MAX_SEND_MESSAGE_LENGTH, DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, } from './constants'; import { ChannelOptions } from './channel-options'; +import { Metadata } from './metadata'; export class MaxMessageSizeFilter extends BaseFilter implements Filter { private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; constructor( - private readonly options: ChannelOptions, - private readonly callStream: Call + private readonly options: ChannelOptions ) { super(); if ('grpc.max_send_message_length' in options) { @@ -48,11 +48,11 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { } else { const concreteMessage = await message; if (concreteMessage.message.length > this.maxSendMessageSize) { - this.callStream.cancelWithStatus( - Status.RESOURCE_EXHAUSTED, - `Sent message larger than max (${concreteMessage.message.length} vs. ${this.maxSendMessageSize})` - ); - return Promise.reject('Message too large'); + throw { + code: Status.RESOURCE_EXHAUSTED, + details: `Sent message larger than max (${concreteMessage.message.length} vs. ${this.maxSendMessageSize})`, + metadata: new Metadata() + }; } else { return concreteMessage; } @@ -67,11 +67,11 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { } else { const concreteMessage = await message; if (concreteMessage.length > this.maxReceiveMessageSize) { - this.callStream.cancelWithStatus( - Status.RESOURCE_EXHAUSTED, - `Received message larger than max (${concreteMessage.length} vs. ${this.maxReceiveMessageSize})` - ); - return Promise.reject('Message too large'); + throw { + code: Status.RESOURCE_EXHAUSTED, + details: `Received message larger than max (${concreteMessage.length} vs. ${this.maxReceiveMessageSize})`, + metadata: new Metadata() + }; } else { return concreteMessage; } @@ -83,7 +83,7 @@ export class MaxMessageSizeFilterFactory implements FilterFactory { constructor(private readonly options: ChannelOptions) {} - createFilter(callStream: Call): MaxMessageSizeFilter { - return new MaxMessageSizeFilter(this.options, callStream); + createFilter(): MaxMessageSizeFilter { + return new MaxMessageSizeFilter(this.options); } } diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index f366a6919..162596ef5 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -15,12 +15,10 @@ * */ -import { Subchannel } from './subchannel'; -import { StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { Metadata } from './metadata'; import { Status } from './constants'; import { LoadBalancer } from './load-balancer'; -import { FilterFactory, Filter } from './filter'; import { SubchannelInterface } from './subchannel-interface'; export enum PickResultType { @@ -43,45 +41,40 @@ export interface PickResult { * `pickResultType` is TRANSIENT_FAILURE. */ status: StatusObject | null; - /** - * Extra FilterFactory (can be multiple encapsulated in a FilterStackFactory) - * provided by the load balancer to be used with the call. For technical - * reasons filters from this factory will not see sendMetadata events. - */ - extraFilterFactories: FilterFactory[]; onCallStarted: (() => void) | null; + onCallEnded: ((statusCode: Status) => void) | null; } export interface CompletePickResult extends PickResult { pickResultType: PickResultType.COMPLETE; subchannel: SubchannelInterface | null; status: null; - extraFilterFactories: FilterFactory[]; onCallStarted: (() => void) | null; + onCallEnded: ((statusCode: Status) => void) | null; } export interface QueuePickResult extends PickResult { pickResultType: PickResultType.QUEUE; subchannel: null; status: null; - extraFilterFactories: []; onCallStarted: null; + onCallEnded: null; } export interface TransientFailurePickResult extends PickResult { pickResultType: PickResultType.TRANSIENT_FAILURE; subchannel: null; status: StatusObject; - extraFilterFactories: []; onCallStarted: null; + onCallEnded: null; } export interface DropCallPickResult extends PickResult { pickResultType: PickResultType.DROP; subchannel: null; status: StatusObject; - extraFilterFactories: []; onCallStarted: null; + onCallEnded: null; } export interface PickArgs { @@ -120,8 +113,8 @@ export class UnavailablePicker implements Picker { pickResultType: PickResultType.TRANSIENT_FAILURE, subchannel: null, status: this.status, - extraFilterFactories: [], onCallStarted: null, + onCallEnded: null }; } } @@ -149,8 +142,8 @@ export class QueuePicker { pickResultType: PickResultType.QUEUE, subchannel: null, status: null, - extraFilterFactories: [], onCallStarted: null, + onCallEnded: null }; } } diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index c4cb64a74..7c4028b06 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -24,7 +24,7 @@ import * as dns from 'dns'; import * as util from 'util'; import { extractAndSelectServiceConfig, ServiceConfig } from './service-config'; import { Status } from './constants'; -import { StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; diff --git a/packages/grpc-js/src/resolver-ip.ts b/packages/grpc-js/src/resolver-ip.ts index 24efc3fdc..efb0b8dcb 100644 --- a/packages/grpc-js/src/resolver-ip.ts +++ b/packages/grpc-js/src/resolver-ip.ts @@ -15,7 +15,7 @@ */ import { isIPv4, isIPv6 } from 'net'; -import { StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { ChannelOptions } from './channel-options'; import { LogVerbosity, Status } from './constants'; import { Metadata } from './metadata'; diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index fcbc6894a..770004487 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -16,7 +16,7 @@ */ import { MethodConfig, ServiceConfig } from './service-config'; -import { StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { SubchannelAddress } from './subchannel-address'; import { GrpcUri, uriToString } from './uri-parser'; import { ChannelOptions } from './channel-options'; diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts new file mode 100644 index 000000000..4cf24b4b1 --- /dev/null +++ b/packages/grpc-js/src/resolving-call.ts @@ -0,0 +1,219 @@ +/* + * Copyright 2022 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 { CallCredentials } from "./call-credentials"; +import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; +import { LogVerbosity, Propagate, Status } from "./constants"; +import { Deadline, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; +import { FilterStackFactory } from "./filter-stack"; +import { InternalChannel } from "./internal-channel"; +import { Metadata } from "./metadata"; +import * as logging from './logging'; + +const TRACER_NAME = 'resolving_call'; + +export class ResolvingCall implements Call { + private child: Call | null = null; + private readPending = false; + private pendingMessage: {context: MessageContext, message: Buffer} | null = null; + private pendingHalfClose = false; + private ended = false; + private metadata: Metadata | null = null; + private listener: InterceptingListener | null = null; + private deadline: Deadline; + private host: string; + private statusWatchers: ((status: StatusObject) => void)[] = []; + private deadlineTimer: NodeJS.Timer = setTimeout(() => {}, 0); + + constructor( + private readonly channel: InternalChannel, + private readonly method: string, + options: CallStreamOptions, + private readonly filterStackFactory: FilterStackFactory, + private credentials: CallCredentials, + private callNumber: number + ) { + this.deadline = options.deadline; + this.host = options.host; + if (options.parentCall) { + if (options.flags & Propagate.CANCELLATION) { + options.parentCall.on('cancelled', () => { + this.cancelWithStatus(Status.CANCELLED, 'Cancelled by parent call'); + }); + } + if (options.flags & Propagate.DEADLINE) { + this.trace('Propagating deadline from parent: ' + options.parentCall.getDeadline()); + this.deadline = minDeadline(this.deadline, options.parentCall.getDeadline()); + } + } + this.trace('Created'); + this.runDeadlineTimer(); + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callNumber + '] ' + text + ); + } + + private runDeadlineTimer() { + clearTimeout(this.deadlineTimer); + this.trace('Deadline: ' + this.deadline); + if (this.deadline !== Infinity) { + const timeout = getRelativeTimeout(this.deadline); + this.trace('Deadline will be reached in ' + timeout + 'ms'); + const handleDeadline = () => { + this.cancelWithStatus( + Status.DEADLINE_EXCEEDED, + 'Deadline exceeded' + ); + } + if (timeout <= 0) { + process.nextTick(handleDeadline); + } else { + this.deadlineTimer = setTimeout(handleDeadline, timeout); + } + } + } + + private outputStatus(status: StatusObject) { + if (!this.ended) { + this.ended = true; + this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); + this.statusWatchers.forEach(watcher => watcher(status)); + process.nextTick(() => { + this.listener?.onReceiveStatus(status); + }); + } + } + + getConfig(): void { + if (this.ended) { + return; + } + if (!this.metadata || !this.listener) { + throw new Error('getConfig called before start'); + } + const config = this.channel.getConfig(this.method, this.metadata); + if (!config) { + this.channel.queueCallForConfig(this); + return; + } + if (config.status !== Status.OK) { + this.outputStatus({ + code: config.status, + details: 'Failed to route call to ' + this.method, + metadata: new Metadata() + }); + return; + } + + if (config.methodConfig.timeout) { + const configDeadline = new Date(); + configDeadline.setSeconds( + configDeadline.getSeconds() + config.methodConfig.timeout.seconds + ); + configDeadline.setMilliseconds( + configDeadline.getMilliseconds() + + config.methodConfig.timeout.nanos / 1_000_000 + ); + this.deadline = minDeadline(this.deadline, configDeadline); + } + + this.filterStackFactory.push(config.dynamicFilterFactories); + + this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); + this.child.start(this.metadata, { + onReceiveMetadata: metadata => { + this.listener!.onReceiveMetadata(metadata); + }, + onReceiveMessage: message => { + this.listener!.onReceiveMessage(message); + }, + onReceiveStatus: status => { + this.outputStatus(status); + } + }); + if (this.readPending) { + this.child.startRead(); + } + if (this.pendingMessage) { + this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); + } + if (this.pendingHalfClose) { + this.child.halfClose(); + } + } + reportResolverError(status: StatusObject) { + if (this.metadata?.getOptions().waitForReady) { + this.channel.queueCallForConfig(this); + } else { + this.outputStatus(status); + } + } + cancelWithStatus(status: Status, details: string): void { + this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.child?.cancelWithStatus(status, details); + this.outputStatus({code: status, details: details, metadata: new Metadata()}); + } + getPeer(): string { + return this.child?.getPeer() ?? this.channel.getTarget(); + } + start(metadata: Metadata, listener: InterceptingListener): void { + this.trace('start called'); + this.metadata = metadata.clone(); + this.listener = listener; + this.getConfig(); + } + sendMessageWithContext(context: MessageContext, message: Buffer): void { + this.trace('write() called with message of length ' + message.length); + if (this.child) { + this.child.sendMessageWithContext(context, message); + } else { + this.pendingMessage = {context, message}; + } + } + startRead(): void { + this.trace('startRead called'); + if (this.child) { + this.child.startRead(); + } else { + this.readPending = true; + } + } + halfClose(): void { + this.trace('halfClose called'); + if (this.child) { + this.child.halfClose(); + } else { + this.pendingHalfClose = true; + } + } + setCredentials(credentials: CallCredentials): void { + this.credentials = this.credentials.compose(credentials); + } + + addStatusWatcher(watcher: (status: StatusObject) => void) { + this.statusWatchers.push(watcher); + } + + getCallNumber(): number { + return this.callNumber; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 907067dfc..47f7c3bb1 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -28,7 +28,7 @@ import { ServiceError } from './call'; import { Picker, UnavailablePicker, QueuePicker } from './picker'; import { BackoffOptions, BackoffTimeout } from './backoff-timeout'; import { Status } from './constants'; -import { StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 315c9d9aa..388a8d339 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -20,7 +20,6 @@ import * as http2 from 'http2'; import { Duplex, Readable, Writable } from 'stream'; import * as zlib from 'zlib'; -import { Deadline, StatusObject } from './call-stream'; import { Status, DEFAULT_MAX_SEND_MESSAGE_LENGTH, @@ -33,6 +32,8 @@ import { StreamDecoder } from './stream-decoder'; import { ObjectReadable, ObjectWritable } from './object-stream'; import { ChannelOptions } from './channel-options'; import * as logging from './logging'; +import { StatusObject } from './call-interface'; +import { Deadline } from './deadline'; const TRACER_NAME = 'server_call'; @@ -508,6 +509,8 @@ export class Http2ServerCallStream< receiveMetadata(headers: http2.IncomingHttpHeaders) { const metadata = Metadata.fromHttp2Headers(headers); + trace('Request to ' + this.handler.path + ' received headers ' + JSON.stringify(metadata.toJSON())); + // TODO(cjihrig): Receive compression metadata. const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER); diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index f310597e9..12802dad8 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -27,6 +27,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as os from 'os'; +import { Status } from './constants'; import { Duration } from './duration'; import { LoadBalancingConfig, @@ -38,18 +39,40 @@ export interface MethodConfigName { method?: string; } +export interface RetryPolicy { + maxAttempts: number; + initialBackoff: string; + maxBackoff: string; + backoffMultiplier: number; + retryableStatusCodes: (Status | string)[]; +} + +export interface HedgingPolicy { + maxAttempts: number; + hedgingDelay?: string; + nonFatalStatusCodes: (Status | string)[]; +} + export interface MethodConfig { name: MethodConfigName[]; waitForReady?: boolean; timeout?: Duration; maxRequestBytes?: number; maxResponseBytes?: number; + retryPolicy?: RetryPolicy; + hedgingPolicy?: HedgingPolicy; +} + +export interface RetryThrottling { + maxTokens: number; + tokenRatio: number; } export interface ServiceConfig { loadBalancingPolicy?: string; loadBalancingConfig: LoadBalancingConfig[]; methodConfig: MethodConfig[]; + retryThrottling?: RetryThrottling; } export interface ServiceConfigCanaryConfig { diff --git a/packages/grpc-js/src/status-builder.ts b/packages/grpc-js/src/status-builder.ts index 1109af1ac..78e2ea310 100644 --- a/packages/grpc-js/src/status-builder.ts +++ b/packages/grpc-js/src/status-builder.ts @@ -15,7 +15,7 @@ * */ -import { StatusObject } from './call-stream'; +import { StatusObject } from './call-interface'; import { Status } from './constants'; import { Metadata } from './metadata'; diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts new file mode 100644 index 000000000..ac34823ba --- /dev/null +++ b/packages/grpc-js/src/subchannel-call.ts @@ -0,0 +1,504 @@ +/* + * 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 * as http2 from 'http2'; +import * as os from 'os'; + +import { Status } from './constants'; +import { Metadata } from './metadata'; +import { StreamDecoder } from './stream-decoder'; +import { SubchannelCallStatsTracker, Subchannel } from './subchannel'; +import * as logging from './logging'; +import { LogVerbosity } from './constants'; +import { ServerSurfaceCall } from './server-call'; +import { Deadline } from './deadline'; +import { InterceptingListener, MessageContext, StatusObject, WriteCallback } from './call-interface'; + +const TRACER_NAME = 'subchannel_call'; + +const { + HTTP2_HEADER_STATUS, + HTTP2_HEADER_CONTENT_TYPE, + NGHTTP2_CANCEL, +} = http2.constants; + +/** + * https://nodejs.org/api/errors.html#errors_class_systemerror + */ +interface SystemError extends Error { + address?: string; + code: string; + dest?: string; + errno: number; + info?: object; + message: string; + path?: string; + port?: number; + syscall: string; +} + +/** + * Should do approximately the same thing as util.getSystemErrorName but the + * TypeScript types don't have that function for some reason so I just made my + * own. + * @param errno + */ +function getSystemErrorName(errno: number): string { + for (const [name, num] of Object.entries(os.constants.errno)) { + if (num === errno) { + return name; + } + } + return 'Unknown system error ' + errno; +} + +export interface SubchannelCall { + cancelWithStatus(status: Status, details: string): void; + getPeer(): string; + sendMessageWithContext(context: MessageContext, message: Buffer): void; + startRead(): void; + halfClose(): void; + getCallNumber(): number; +} + +export class Http2SubchannelCall implements SubchannelCall { + private decoder = new StreamDecoder(); + + private isReadFilterPending = false; + private canPush = false; + /** + * Indicates that an 'end' event has come from the http2 stream, so there + * will be no more data events. + */ + private readsClosed = false; + + private statusOutput = false; + + private unpushedReadMessages: Buffer[] = []; + + // Status code mapped from :status. To be used if grpc-status is not received + private mappedStatusCode: Status = Status.UNKNOWN; + + // This is populated (non-null) if and only if the call has ended + private finalStatus: StatusObject | null = null; + + private disconnectListener: () => void; + + private internalError: SystemError | null = null; + + constructor( + private readonly http2Stream: http2.ClientHttp2Stream, + private readonly callStatsTracker: SubchannelCallStatsTracker, + private readonly listener: InterceptingListener, + private readonly subchannel: Subchannel, + private readonly callId: number + ) { + this.disconnectListener = () => { + this.endCall({ + code: Status.UNAVAILABLE, + details: 'Connection dropped', + metadata: new Metadata(), + }); + }; + subchannel.addDisconnectListener(this.disconnectListener); + subchannel.callRef(); + http2Stream.on('response', (headers, flags) => { + let headersString = ''; + for (const header of Object.keys(headers)) { + headersString += '\t\t' + header + ': ' + headers[header] + '\n'; + } + this.trace('Received server headers:\n' + headersString); + switch (headers[':status']) { + // TODO(murgatroid99): handle 100 and 101 + case 400: + this.mappedStatusCode = Status.INTERNAL; + break; + case 401: + this.mappedStatusCode = Status.UNAUTHENTICATED; + break; + case 403: + this.mappedStatusCode = Status.PERMISSION_DENIED; + break; + case 404: + this.mappedStatusCode = Status.UNIMPLEMENTED; + break; + case 429: + case 502: + case 503: + case 504: + this.mappedStatusCode = Status.UNAVAILABLE; + break; + default: + this.mappedStatusCode = Status.UNKNOWN; + } + + if (flags & http2.constants.NGHTTP2_FLAG_END_STREAM) { + this.handleTrailers(headers); + } else { + let metadata: Metadata; + try { + metadata = Metadata.fromHttp2Headers(headers); + } catch (error) { + this.endCall({ + code: Status.UNKNOWN, + details: (error as Error).message, + metadata: new Metadata(), + }); + return; + } + this.listener.onReceiveMetadata(metadata); + } + }); + http2Stream.on('trailers', (headers: http2.IncomingHttpHeaders) => { + this.handleTrailers(headers); + }); + http2Stream.on('data', (data: Buffer) => { + this.trace('receive HTTP/2 data frame of length ' + data.length); + const messages = this.decoder.write(data); + + for (const message of messages) { + this.trace('parsed message of length ' + message.length); + this.callStatsTracker!.addMessageReceived(); + this.tryPush(message); + } + }); + http2Stream.on('end', () => { + this.readsClosed = true; + this.maybeOutputStatus(); + }); + http2Stream.on('close', () => { + /* Use process.next tick to ensure that this code happens after any + * "error" event that may be emitted at about the same time, so that + * we can bubble up the error message from that event. */ + process.nextTick(() => { + this.trace('HTTP/2 stream closed with code ' + http2Stream.rstCode); + /* If we have a final status with an OK status code, that means that + * we have received all of the messages and we have processed the + * trailers and the call completed successfully, so it doesn't matter + * how the stream ends after that */ + if (this.finalStatus?.code === Status.OK) { + return; + } + let code: Status; + let details = ''; + switch (http2Stream.rstCode) { + case http2.constants.NGHTTP2_NO_ERROR: + /* If we get a NO_ERROR code and we already have a status, the + * stream completed properly and we just haven't fully processed + * it yet */ + if (this.finalStatus !== null) { + return; + } + code = Status.INTERNAL; + details = `Received RST_STREAM with code ${http2Stream.rstCode}`; + break; + case http2.constants.NGHTTP2_REFUSED_STREAM: + code = Status.UNAVAILABLE; + details = 'Stream refused by server'; + break; + case http2.constants.NGHTTP2_CANCEL: + code = Status.CANCELLED; + details = 'Call cancelled'; + break; + case http2.constants.NGHTTP2_ENHANCE_YOUR_CALM: + code = Status.RESOURCE_EXHAUSTED; + details = 'Bandwidth exhausted or memory limit exceeded'; + break; + case http2.constants.NGHTTP2_INADEQUATE_SECURITY: + code = Status.PERMISSION_DENIED; + details = 'Protocol not secure enough'; + break; + case http2.constants.NGHTTP2_INTERNAL_ERROR: + code = Status.INTERNAL; + if (this.internalError === null) { + /* This error code was previously handled in the default case, and + * there are several instances of it online, so I wanted to + * preserve the original error message so that people find existing + * information in searches, but also include the more recognizable + * "Internal server error" message. */ + details = `Received RST_STREAM with code ${http2Stream.rstCode} (Internal server error)`; + } else { + if (this.internalError.code === 'ECONNRESET' || this.internalError.code === 'ETIMEDOUT') { + code = Status.UNAVAILABLE; + details = this.internalError.message; + } else { + /* The "Received RST_STREAM with code ..." error is preserved + * here for continuity with errors reported online, but the + * error message at the end will probably be more relevant in + * most cases. */ + details = `Received RST_STREAM with code ${http2Stream.rstCode} triggered by internal client error: ${this.internalError.message}`; + } + } + break; + default: + code = Status.INTERNAL; + details = `Received RST_STREAM with code ${http2Stream.rstCode}`; + } + // This is a no-op if trailers were received at all. + // This is OK, because status codes emitted here correspond to more + // catastrophic issues that prevent us from receiving trailers in the + // first place. + this.endCall({ code, details, metadata: new Metadata() }); + }); + }); + http2Stream.on('error', (err: SystemError) => { + /* We need an error handler here to stop "Uncaught Error" exceptions + * from bubbling up. However, errors here should all correspond to + * "close" events, where we will handle the error more granularly */ + /* Specifically looking for stream errors that were *not* constructed + * from a RST_STREAM response here: + * https://github.com/nodejs/node/blob/8b8620d580314050175983402dfddf2674e8e22a/lib/internal/http2/core.js#L2267 + */ + if (err.code !== 'ERR_HTTP2_STREAM_ERROR') { + this.trace( + 'Node error event: message=' + + err.message + + ' code=' + + err.code + + ' errno=' + + getSystemErrorName(err.errno) + + ' syscall=' + + err.syscall + ); + this.internalError = err; + } + this.callStatsTracker.onStreamEnd(false); + }); + } + + private outputStatus() { + /* Precondition: this.finalStatus !== null */ + if (!this.statusOutput) { + this.statusOutput = true; + this.trace( + 'ended with status: code=' + + this.finalStatus!.code + + ' details="' + + this.finalStatus!.details + + '"' + ); + this.callStatsTracker.onCallEnd(this.finalStatus!); + /* We delay the actual action of bubbling up the status to insulate the + * cleanup code in this class from any errors that may be thrown in the + * upper layers as a result of bubbling up the status. In particular, + * if the status is not OK, the "error" event may be emitted + * synchronously at the top level, which will result in a thrown error if + * the user does not handle that event. */ + process.nextTick(() => { + this.listener.onReceiveStatus(this.finalStatus!); + }); + this.subchannel.callUnref(); + this.subchannel.removeDisconnectListener(this.disconnectListener); + } + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callId + '] ' + text + ); + } + + /** + * On first call, emits a 'status' event with the given StatusObject. + * Subsequent calls are no-ops. + * @param status The status of the call. + */ + private endCall(status: StatusObject): void { + /* If the status is OK and a new status comes in (e.g. from a + * deserialization failure), that new status takes priority */ + if (this.finalStatus === null || this.finalStatus.code === Status.OK) { + this.finalStatus = status; + this.maybeOutputStatus(); + } + this.destroyHttp2Stream(); + } + + private maybeOutputStatus() { + if (this.finalStatus !== null) { + /* The combination check of readsClosed and that the two message buffer + * arrays are empty checks that there all incoming data has been fully + * processed */ + if ( + this.finalStatus.code !== Status.OK || + (this.readsClosed && + this.unpushedReadMessages.length === 0 && + !this.isReadFilterPending) + ) { + this.outputStatus(); + } + } + } + + private push(message: Buffer): void { + this.trace( + 'pushing to reader message of length ' + + (message instanceof Buffer ? message.length : null) + ); + this.canPush = false; + process.nextTick(() => { + /* If we have already output the status any later messages should be + * ignored, and can cause out-of-order operation errors higher up in the + * stack. Checking as late as possible here to avoid any race conditions. + */ + if (this.statusOutput) { + return; + } + this.listener.onReceiveMessage(message); + this.maybeOutputStatus(); + }); + } + + private tryPush(messageBytes: Buffer): void { + if (this.canPush) { + this.http2Stream!.pause(); + this.push(messageBytes); + } else { + this.trace( + 'unpushedReadMessages.push message of length ' + messageBytes.length + ); + this.unpushedReadMessages.push(messageBytes); + } + } + + private handleTrailers(headers: http2.IncomingHttpHeaders) { + this.callStatsTracker.onStreamEnd(true); + let headersString = ''; + for (const header of Object.keys(headers)) { + headersString += '\t\t' + header + ': ' + headers[header] + '\n'; + } + this.trace('Received server trailers:\n' + headersString); + let metadata: Metadata; + try { + metadata = Metadata.fromHttp2Headers(headers); + } catch (e) { + metadata = new Metadata(); + } + const metadataMap = metadata.getMap(); + let code: Status = this.mappedStatusCode; + if ( + code === Status.UNKNOWN && + typeof metadataMap['grpc-status'] === 'string' + ) { + const receivedStatus = Number(metadataMap['grpc-status']); + if (receivedStatus in Status) { + code = receivedStatus; + this.trace('received status code ' + receivedStatus + ' from server'); + } + metadata.remove('grpc-status'); + } + let details = ''; + if (typeof metadataMap['grpc-message'] === 'string') { + details = decodeURI(metadataMap['grpc-message']); + metadata.remove('grpc-message'); + this.trace( + 'received status details string "' + details + '" from server' + ); + } + const status: StatusObject = { code, details, metadata }; + // This is a no-op if the call was already ended when handling headers. + this.endCall(status); + } + + private destroyHttp2Stream() { + // The http2 stream could already have been destroyed if cancelWithStatus + // is called in response to an internal http2 error. + if (!this.http2Stream.destroyed) { + /* If the call has ended with an OK status, communicate that when closing + * the stream, partly to avoid a situation in which we detect an error + * RST_STREAM as a result after we have the status */ + let code: number; + if (this.finalStatus?.code === Status.OK) { + code = http2.constants.NGHTTP2_NO_ERROR; + } else { + code = http2.constants.NGHTTP2_CANCEL; + } + this.trace('close http2 stream with code ' + code); + this.http2Stream.close(code); + } + } + + cancelWithStatus(status: Status, details: string): void { + this.trace( + 'cancelWithStatus code: ' + status + ' details: "' + details + '"' + ); + this.endCall({ code: status, details, metadata: new Metadata() }); + } + + getStatus(): StatusObject | null { + return this.finalStatus; + } + + getPeer(): string { + return this.subchannel.getAddress(); + } + + getCallNumber(): number { + return this.callId; + } + + startRead() { + /* If the stream has ended with an error, we should not emit any more + * messages and we should communicate that the stream has ended */ + if (this.finalStatus !== null && this.finalStatus.code !== Status.OK) { + this.readsClosed = true; + this.maybeOutputStatus(); + return; + } + this.canPush = true; + if (this.unpushedReadMessages.length > 0) { + const nextMessage: Buffer = this.unpushedReadMessages.shift()!; + this.push(nextMessage); + return; + } + /* Only resume reading from the http2Stream if we don't have any pending + * messages to emit */ + this.http2Stream.resume(); + } + + sendMessageWithContext(context: MessageContext, message: Buffer) { + this.trace('write() called with message of length ' + message.length); + const cb: WriteCallback = (error?: Error | null) => { + let code: Status = Status.UNAVAILABLE; + if ((error as NodeJS.ErrnoException)?.code === 'ERR_STREAM_WRITE_AFTER_END') { + code = Status.INTERNAL; + } + if (error) { + this.cancelWithStatus(code, `Write error: ${error.message}`); + } + context.callback?.(); + }; + this.trace('sending data chunk of length ' + message.length); + this.callStatsTracker.addMessageSent(); + try { + this.http2Stream!.write(message, cb); + } catch (error) { + this.endCall({ + code: Status.UNAVAILABLE, + details: `Write failed with error ${(error as Error).message}`, + metadata: new Metadata() + }); + } + } + + halfClose() { + this.trace('end() called'); + this.trace('calling end() on HTTP/2 stream'); + this.http2Stream.end(); + } +} diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 800274f99..563e69464 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -18,7 +18,6 @@ import * as http2 from 'http2'; import { ChannelCredentials } from './channel-credentials'; import { Metadata } from './metadata'; -import { Call, Http2CallStream, WriteObject } from './call-stream'; import { ChannelOptions } from './channel-options'; import { PeerCertificate, checkServerIdentity, TLSSocket, CipherNameAndProtocol } from 'tls'; import { ConnectivityState } from './connectivity-state'; @@ -30,7 +29,6 @@ import { getProxiedConnection, ProxyConnectionResult } from './http_proxy'; import * as net from 'net'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { ConnectionOptions } from 'tls'; -import { FilterFactory, Filter, BaseFilter } from './filter'; import { stringToSubchannelAddress, SubchannelAddress, @@ -38,18 +36,16 @@ import { } from './subchannel-address'; import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, SocketInfo, SocketRef, unregisterChannelzRef, registerChannelzSocket, TlsInfo } from './channelz'; import { ConnectivityStateListener } from './subchannel-interface'; +import { Http2SubchannelCall } from './subchannel-call'; +import { getNextCallNumber } from './call-number'; +import { SubchannelCall } from './subchannel-call'; +import { InterceptingListener, StatusObject } from './call-interface'; const clientVersion = require('../../package.json').version; const TRACER_NAME = 'subchannel'; const FLOW_CONTROL_TRACER_NAME = 'subchannel_flowctrl'; -const MIN_CONNECT_TIMEOUT_MS = 20000; -const INITIAL_BACKOFF_MS = 1000; -const BACKOFF_MULTIPLIER = 1.6; -const MAX_BACKOFF_MS = 120000; -const BACKOFF_JITTER = 0.2; - /* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't * have a constant for the max signed 32 bit integer, so this is a simple way * to calculate it */ @@ -59,6 +55,8 @@ const KEEPALIVE_TIMEOUT_MS = 20000; export interface SubchannelCallStatsTracker { addMessageSent(): void; addMessageReceived(): void; + onCallEnd(status: StatusObject): void; + onStreamEnd(success: boolean): void; } const { @@ -70,15 +68,6 @@ const { HTTP2_HEADER_USER_AGENT, } = http2.constants; -/** - * Get a number uniformly at random in the range [min, max) - * @param min - * @param max - */ -function uniformRandom(min: number, max: number) { - return Math.random() * (max - min) + min; -} - const tooManyPingsData: Buffer = Buffer.from('too_many_pings', 'ascii'); export class Subchannel { @@ -808,24 +797,13 @@ export class Subchannel { return false; } - /** - * Start a stream on the current session with the given `metadata` as headers - * and then attach it to the `callStream`. Must only be called if the - * subchannel's current connectivity state is READY. - * @param metadata - * @param callStream - */ - startCallStream( - metadata: Metadata, - callStream: Http2CallStream, - extraFilters: Filter[] - ) { + createCall(metadata: Metadata, host: string, method: string, listener: InterceptingListener): SubchannelCall { const headers = metadata.toHttp2Headers(); - headers[HTTP2_HEADER_AUTHORITY] = callStream.getHost(); + headers[HTTP2_HEADER_AUTHORITY] = host; headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; headers[HTTP2_HEADER_METHOD] = 'POST'; - headers[HTTP2_HEADER_PATH] = callStream.getMethod(); + headers[HTTP2_HEADER_PATH] = method; headers[HTTP2_HEADER_TE] = 'trailers'; let http2Stream: http2.ClientHttp2Stream; /* In theory, if an error is thrown by session.request because session has @@ -845,19 +823,6 @@ export class Subchannel { ); throw e; } - let headersString = ''; - for (const header of Object.keys(headers)) { - headersString += '\t\t' + header + ': ' + headers[header] + '\n'; - } - logging.trace( - LogVerbosity.DEBUG, - 'call_stream', - 'Starting stream [' + callStream.getCallNumber() + '] on subchannel ' + - '(' + this.channelzRef.id + ') ' + - this.subchannelAddressString + - ' with headers\n' + - headersString - ); this.flowControlTrace( 'local window size: ' + this.session!.state.localWindowSize + @@ -875,23 +840,7 @@ export class Subchannel { let statsTracker: SubchannelCallStatsTracker; if (this.channelzEnabled) { this.callTracker.addCallStarted(); - callStream.addStatusWatcher(status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); this.streamTracker.addCallStarted(); - callStream.addStreamEndWatcher(success => { - if (streamSession === this.session) { - if (success) { - this.streamTracker.addCallSucceeded(); - } else { - this.streamTracker.addCallFailed(); - } - } - }); statsTracker = { addMessageSent: () => { this.messagesSent += 1; @@ -899,15 +848,33 @@ export class Subchannel { }, addMessageReceived: () => { this.messagesReceived += 1; + }, + onCallEnd: status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }, + onStreamEnd: success => { + if (streamSession === this.session) { + if (success) { + this.streamTracker.addCallSucceeded(); + } else { + this.streamTracker.addCallFailed(); + } + } } } } else { statsTracker = { addMessageSent: () => {}, - addMessageReceived: () => {} + addMessageReceived: () => {}, + onCallEnd: () => {}, + onStreamEnd: () => {} } } - callStream.attachHttp2Stream(http2Stream, this, extraFilters, statsTracker); + return new Http2SubchannelCall(http2Stream, statsTracker, listener, this, getNextCallNumber()); } /** diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index 354413ea6..1bcaabeb4 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -23,7 +23,7 @@ import * as resolver_dns from '../src/resolver-dns'; import * as resolver_uds from '../src/resolver-uds'; import * as resolver_ip from '../src/resolver-ip'; import { ServiceConfig } from '../src/service-config'; -import { StatusObject } from '../src/call-stream'; +import { StatusObject } from '../src/call-interface'; import { SubchannelAddress, isTcpSubchannelAddress, subchannelAddressToString } from "../src/subchannel-address"; import { parseUri, GrpcUri } from '../src/uri-parser'; From 8a312e63b719fe0166acb07c8a43f2a91253ea17 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 11 Oct 2022 16:50:49 -0700 Subject: [PATCH 340/694] grpc-js-xds: Update code to handle modified experimental APIs --- .../src/http-filter/fault-injection-filter.ts | 8 ++--- .../src/http-filter/router-filter.ts | 2 +- packages/grpc-js-xds/src/load-balancer-eds.ts | 28 +++-------------- packages/grpc-js-xds/src/load-balancer-lrs.ts | 31 +++---------------- .../src/load-balancer-xds-cluster-manager.ts | 4 +-- 5 files changed, 16 insertions(+), 57 deletions(-) diff --git a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts index a49ec77d4..b02dfbc80 100644 --- a/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts +++ b/packages/grpc-js-xds/src/http-filter/fault-injection-filter.ts @@ -231,7 +231,7 @@ const NUMBER_REGEX = /\d+/; let totalActiveFaults = 0; class FaultInjectionFilter extends BaseFilter implements Filter { - constructor(private callStream: CallStream, private config: FaultInjectionConfig) { + constructor(private config: FaultInjectionConfig) { super(); } @@ -316,7 +316,7 @@ class FaultInjectionFilter extends BaseFilter implements Filter { } } if (abortStatus !== null && rollRandomPercentage(numerator, denominator)) { - this.callStream.cancelWithStatus(abortStatus, 'Fault injected'); + return Promise.reject({code: abortStatus, details: 'Fault injected', metadata: new Metadata()}); } } return metadata; @@ -333,8 +333,8 @@ class FaultInjectionFilterFactory implements FilterFactory } } - createFilter(callStream: experimental.CallStream): FaultInjectionFilter { - return new FaultInjectionFilter(callStream, this.config); + createFilter(): FaultInjectionFilter { + return new FaultInjectionFilter(this.config); } } diff --git a/packages/grpc-js-xds/src/http-filter/router-filter.ts b/packages/grpc-js-xds/src/http-filter/router-filter.ts index 81450c0ff..172a08740 100644 --- a/packages/grpc-js-xds/src/http-filter/router-filter.ts +++ b/packages/grpc-js-xds/src/http-filter/router-filter.ts @@ -26,7 +26,7 @@ class RouterFilter extends BaseFilter implements Filter {} class RouterFilterFactory implements FilterFactory { constructor(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig) {} - createFilter(callStream: experimental.CallStream): RouterFilter { + createFilter(): RouterFilter { return new RouterFilter(); } } diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index e7aac0571..39ad2c2cb 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -146,24 +146,6 @@ export class EdsLoadBalancingConfig implements LoadBalancingConfig { } } -class CallEndTrackingFilter extends BaseFilter implements Filter { - constructor(private onCallEnd: () => void) { - super(); - } - receiveTrailers(status: StatusObject) { - this.onCallEnd(); - return status; - } -} - -class CallTrackingFilterFactory implements FilterFactory { - constructor(private onCallEnd: () => void) {} - - createFilter(callStream: CallStream) { - return new CallEndTrackingFilter(this.onCallEnd); - } -} - /** * This class load balances over a cluster by making an EDS request and then * transforming the result into a configuration for another load balancing @@ -217,9 +199,6 @@ export class EdsLoadBalancer implements LoadBalancer { * balancer. */ if (dropCategory === null) { const originalPick = originalPicker.pick(pickArgs); - const trackingFilterFactory: FilterFactory = new CallTrackingFilterFactory(() => { - this.concurrentRequests -= 1; - }); return { pickResultType: originalPick.pickResultType, status: originalPick.status, @@ -228,7 +207,10 @@ export class EdsLoadBalancer implements LoadBalancer { originalPick.onCallStarted?.(); this.concurrentRequests += 1; }, - extraFilterFactories: originalPick.extraFilterFactories.concat(trackingFilterFactory) + onCallEnded: status => { + originalPick.onCallEnded?.(status); + this.concurrentRequests -= 1; + } }; } else { let details: string; @@ -247,7 +229,7 @@ export class EdsLoadBalancer implements LoadBalancer { metadata: new Metadata(), }, subchannel: null, - extraFilterFactories: [], + onCallEnded: null, onCallStarted: null }; } diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 145501fe9..0eaeeee34 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -108,29 +108,6 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig { } } -/** - * Filter class that reports when the call ends. - */ -class CallEndTrackingFilter extends BaseFilter implements Filter { - constructor(private localityStatsReporter: XdsClusterLocalityStats) { - super(); - } - - receiveTrailers(status: StatusObject) { - this.localityStatsReporter.addCallFinished(status.code !== Status.OK); - return status; - } -} - -class CallEndTrackingFilterFactory - implements FilterFactory { - constructor(private localityStatsReporter: XdsClusterLocalityStats) {} - - createFilter(callStream: Call): CallEndTrackingFilter { - return new CallEndTrackingFilter(this.localityStatsReporter); - } -} - /** * Picker that delegates picking to another picker, and reports when calls * created using those picks start and end. @@ -144,9 +121,6 @@ class LoadReportingPicker implements Picker { pick(pickArgs: PickArgs): PickResult { const wrappedPick = this.wrappedPicker.pick(pickArgs); if (wrappedPick.pickResultType === PickResultType.COMPLETE) { - const trackingFilterFactory = new CallEndTrackingFilterFactory( - this.localityStatsReporter - ); return { pickResultType: PickResultType.COMPLETE, subchannel: wrappedPick.subchannel, @@ -155,7 +129,10 @@ class LoadReportingPicker implements Picker { wrappedPick.onCallStarted?.(); this.localityStatsReporter.addCallStarted(); }, - extraFilterFactories: wrappedPick.extraFilterFactories.concat(trackingFilterFactory), + onCallEnded: status => { + wrappedPick.onCallEnded?.(status); + this.localityStatsReporter.addCallFinished(status !== Status.OK); + } }; } else { return wrappedPick; diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index bfc55c809..bfdb4dccc 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -107,8 +107,8 @@ class XdsClusterManagerPicker implements Picker { metadata: new Metadata(), }, subchannel: null, - extraFilterFactories: [], - onCallStarted: null + onCallStarted: null, + onCallEnded: null }; } } From 3003dbea52a9c78ae78292b874ee4677f4667010 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Oct 2022 13:47:53 -0700 Subject: [PATCH 341/694] grpc-js-xds: Delete generated code for xDS v2 --- packages/grpc-js-xds/package.json | 3 +- packages/grpc-js-xds/src/generated/ads.ts | 57 ----- .../envoy/api/v2/DeltaDiscoveryRequest.ts | 202 ------------------ .../envoy/api/v2/DeltaDiscoveryResponse.ts | 63 ------ .../envoy/api/v2/DiscoveryRequest.ts | 110 ---------- .../envoy/api/v2/DiscoveryResponse.ts | 108 ---------- .../src/generated/envoy/api/v2/Resource.ts | 43 ---- .../generated/envoy/api/v2/core/Address.ts | 26 --- .../envoy/api/v2/core/AsyncDataSource.ts | 34 --- .../envoy/api/v2/core/BackoffStrategy.ts | 43 ---- .../generated/envoy/api/v2/core/BindConfig.ts | 49 ----- .../envoy/api/v2/core/BuildVersion.ts | 36 ---- .../generated/envoy/api/v2/core/CidrRange.ts | 33 --- .../envoy/api/v2/core/ControlPlane.ts | 26 --- .../generated/envoy/api/v2/core/DataSource.ts | 40 ---- .../generated/envoy/api/v2/core/Extension.ts | 75 ------- .../generated/envoy/api/v2/core/HeaderMap.ts | 17 -- .../envoy/api/v2/core/HeaderValue.ts | 38 ---- .../envoy/api/v2/core/HeaderValueOption.ts | 34 --- .../generated/envoy/api/v2/core/HttpUri.ts | 79 ------- .../generated/envoy/api/v2/core/Locality.ts | 56 ----- .../generated/envoy/api/v2/core/Metadata.ts | 67 ------ .../src/generated/envoy/api/v2/core/Node.ts | 173 --------------- .../src/generated/envoy/api/v2/core/Pipe.ts | 30 --- .../envoy/api/v2/core/RemoteDataSource.ts | 40 ---- .../envoy/api/v2/core/RequestMethod.ts | 17 -- .../envoy/api/v2/core/RetryPolicy.ts | 38 ---- .../envoy/api/v2/core/RoutingPriority.ts | 15 -- .../envoy/api/v2/core/RuntimeDouble.ts | 30 --- .../envoy/api/v2/core/RuntimeFeatureFlag.ts | 35 --- .../api/v2/core/RuntimeFractionalPercent.ts | 49 ----- .../envoy/api/v2/core/RuntimeUInt32.ts | 30 --- .../envoy/api/v2/core/SocketAddress.ts | 97 --------- .../envoy/api/v2/core/SocketOption.ts | 90 -------- .../envoy/api/v2/core/TcpKeepalive.ts | 43 ---- .../envoy/api/v2/core/TrafficDirection.ts | 19 -- .../envoy/api/v2/core/TransportSocket.ts | 46 ---- .../envoy/api/v2/endpoint/ClusterStats.ts | 117 ---------- .../v2/endpoint/EndpointLoadMetricStats.ts | 41 ---- .../api/v2/endpoint/UpstreamEndpointStats.ts | 106 --------- .../api/v2/endpoint/UpstreamLocalityStats.ts | 108 ---------- .../envoy/service/discovery/v2/AdsDummy.ts | 16 -- .../v2/AggregatedDiscoveryService.ts | 58 ----- .../load_stats/v2/LoadReportingService.ts | 113 ---------- .../service/load_stats/v2/LoadStatsRequest.ts | 34 --- .../load_stats/v2/LoadStatsResponse.ts | 71 ------ .../generated/envoy/type/FractionalPercent.ts | 68 ------ .../src/generated/envoy/type/Percent.ts | 16 -- .../generated/envoy/type/SemanticVersion.ts | 24 --- packages/grpc-js-xds/src/generated/lrs.ts | 51 ----- 50 files changed, 1 insertion(+), 2813 deletions(-) delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryResponse.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/ControlPlane.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/DataSource.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderMap.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValue.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/Locality.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/Pipe.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RequestMethod.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RoutingPriority.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeUInt32.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketAddress.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketOption.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/TrafficDirection.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AdsDummy.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/type/FractionalPercent.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/type/Percent.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/type/SemanticVersion.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index fcb9279f0..124c3ed7d 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/v2/ads.proto envoy/service/load_stats/v2/lrs.proto 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", + "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", "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" }, "repository": { @@ -56,7 +56,6 @@ "src/**/*.ts", "build/src/**/*.{js,d.ts,js.map}", "deps/envoy-api/envoy/admin/v3/**/*.proto", - "deps/envoy-api/envoy/api/v2/**/*.proto", "deps/envoy-api/envoy/config/**/*.proto", "deps/envoy-api/envoy/service/**/*.proto", "deps/envoy-api/envoy/type/**/*.proto", diff --git a/packages/grpc-js-xds/src/generated/ads.ts b/packages/grpc-js-xds/src/generated/ads.ts index e0e46bb25..228f6f1d4 100644 --- a/packages/grpc-js-xds/src/generated/ads.ts +++ b/packages/grpc-js-xds/src/generated/ads.ts @@ -1,7 +1,6 @@ import type * as grpc from '@grpc/grpc-js'; import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; -import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v2_AggregatedDiscoveryServiceClient, AggregatedDiscoveryServiceDefinition as _envoy_service_discovery_v2_AggregatedDiscoveryServiceDefinition } from './envoy/service/discovery/v2/AggregatedDiscoveryService'; import type { AggregatedDiscoveryServiceClient as _envoy_service_discovery_v3_AggregatedDiscoveryServiceClient, AggregatedDiscoveryServiceDefinition as _envoy_service_discovery_v3_AggregatedDiscoveryServiceDefinition } from './envoy/service/discovery/v3/AggregatedDiscoveryService'; type SubtypeConstructor any, Subtype> = { @@ -12,47 +11,6 @@ export interface ProtoGrpcType { envoy: { annotations: { } - api: { - v2: { - DeltaDiscoveryRequest: MessageTypeDefinition - DeltaDiscoveryResponse: MessageTypeDefinition - DiscoveryRequest: MessageTypeDefinition - DiscoveryResponse: MessageTypeDefinition - Resource: MessageTypeDefinition - core: { - Address: MessageTypeDefinition - AsyncDataSource: MessageTypeDefinition - BackoffStrategy: MessageTypeDefinition - BindConfig: MessageTypeDefinition - BuildVersion: MessageTypeDefinition - CidrRange: MessageTypeDefinition - ControlPlane: MessageTypeDefinition - DataSource: MessageTypeDefinition - Extension: MessageTypeDefinition - HeaderMap: MessageTypeDefinition - HeaderValue: MessageTypeDefinition - HeaderValueOption: MessageTypeDefinition - HttpUri: MessageTypeDefinition - Locality: MessageTypeDefinition - Metadata: MessageTypeDefinition - Node: MessageTypeDefinition - Pipe: MessageTypeDefinition - RemoteDataSource: MessageTypeDefinition - RequestMethod: EnumTypeDefinition - RetryPolicy: MessageTypeDefinition - RoutingPriority: EnumTypeDefinition - RuntimeDouble: MessageTypeDefinition - RuntimeFeatureFlag: MessageTypeDefinition - RuntimeFractionalPercent: MessageTypeDefinition - RuntimeUInt32: MessageTypeDefinition - SocketAddress: MessageTypeDefinition - SocketOption: MessageTypeDefinition - TcpKeepalive: MessageTypeDefinition - TrafficDirection: EnumTypeDefinition - TransportSocket: MessageTypeDefinition - } - } - } config: { core: { v3: { @@ -95,18 +53,6 @@ export interface ProtoGrpcType { } service: { discovery: { - v2: { - AdsDummy: MessageTypeDefinition - /** - * See https://github.com/lyft/envoy-api#apis for a description of the role of - * ADS and how it is intended to be used by a management server. ADS requests - * have the same structure as their singleton xDS counterparts, but can - * multiplex many resource types on a single stream. The type_url in the - * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover - * the multiplexed singleton APIs at the Envoy instance and management server. - */ - AggregatedDiscoveryService: SubtypeConstructor & { service: _envoy_service_discovery_v2_AggregatedDiscoveryServiceDefinition } - } v3: { AdsDummy: MessageTypeDefinition /** @@ -127,9 +73,6 @@ export interface ProtoGrpcType { } } type: { - FractionalPercent: MessageTypeDefinition - Percent: MessageTypeDefinition - SemanticVersion: MessageTypeDefinition v3: { FractionalPercent: MessageTypeDefinition Percent: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts deleted file mode 100644 index 20ddb1b14..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryRequest.ts +++ /dev/null @@ -1,202 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/discovery.proto - -import type { Node as _envoy_api_v2_core_Node, Node__Output as _envoy_api_v2_core_Node__Output } from '../../../envoy/api/v2/core/Node'; -import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../google/rpc/Status'; - -/** - * DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC - * endpoint for Delta xDS. - * - * With Delta xDS, the DeltaDiscoveryResponses do not need to include a full - * snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a - * diff to the state of a xDS client. - * In Delta XDS there are per-resource versions, which allow tracking state at - * the resource granularity. - * An xDS Delta session is always in the context of a gRPC bidirectional - * stream. This allows the xDS server to keep track of the state of xDS clients - * connected to it. - * - * In Delta xDS the nonce field is required and used to pair - * DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. - * Optionally, a response message level system_version_info is present for - * debugging purposes only. - * - * DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest - * can be either or both of: [1] informing the server of what resources the - * client has gained/lost interest in (using resource_names_subscribe and - * resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from - * the server (using response_nonce, with presence of error_detail making it a NACK). - * Additionally, the first message (for a given type_url) of a reconnected gRPC stream - * has a third role: informing the server of the resources (and their versions) - * that the client already possesses, using the initial_resource_versions field. - * - * As with state-of-the-world, when multiple resource types are multiplexed (ADS), - * all requests/acknowledgments/updates are logically walled off by type_url: - * a Cluster ACK exists in a completely separate world from a prior Route NACK. - * In particular, initial_resource_versions being sent at the "start" of every - * gRPC stream actually entails a message for each type_url, each with its own - * initial_resource_versions. - * [#next-free-field: 8] - */ -export interface DeltaDiscoveryRequest { - /** - * The node making the request. - */ - 'node'?: (_envoy_api_v2_core_Node | null); - /** - * Type of the resource that is being requested, e.g. - * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". - */ - 'type_url'?: (string); - /** - * DeltaDiscoveryRequests allow the client to add or remove individual - * resources to the set of tracked resources in the context of a stream. - * All resource names in the resource_names_subscribe list are added to the - * set of tracked resources and all resource names in the resource_names_unsubscribe - * list are removed from the set of tracked resources. - * - * *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or - * resource_names_unsubscribe list simply means that no resources are to be - * added or removed to the resource list. - * *Like* state-of-the-world xDS, the server must send updates for all tracked - * resources, but can also send updates for resources the client has not subscribed to. - * - * NOTE: the server must respond with all resources listed in resource_names_subscribe, - * even if it believes the client has the most recent version of them. The reason: - * the client may have dropped them, but then regained interest before it had a chance - * to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. - * - * These two fields can be set in any DeltaDiscoveryRequest, including ACKs - * and initial_resource_versions. - * - * A list of Resource names to add to the list of tracked resources. - */ - 'resource_names_subscribe'?: (string)[]; - /** - * A list of Resource names to remove from the list of tracked resources. - */ - 'resource_names_unsubscribe'?: (string)[]; - /** - * Informs the server of the versions of the resources the xDS client knows of, to enable the - * client to continue the same logical xDS session even in the face of gRPC stream reconnection. - * It will not be populated: [1] in the very first stream of a session, since the client will - * not yet have any resources, [2] in any message after the first in a stream (for a given - * type_url), since the server will already be correctly tracking the client's state. - * (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) - * The map's keys are names of xDS resources known to the xDS client. - * The map's values are opaque resource versions. - */ - 'initial_resource_versions'?: ({[key: string]: string}); - /** - * When the DeltaDiscoveryRequest is a ACK or NACK message in response - * to a previous DeltaDiscoveryResponse, the response_nonce must be the - * nonce in the DeltaDiscoveryResponse. - * Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. - */ - 'response_nonce'?: (string); - /** - * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* - * provides the Envoy internal exception related to the failure. - */ - 'error_detail'?: (_google_rpc_Status | null); -} - -/** - * DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC - * endpoint for Delta xDS. - * - * With Delta xDS, the DeltaDiscoveryResponses do not need to include a full - * snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a - * diff to the state of a xDS client. - * In Delta XDS there are per-resource versions, which allow tracking state at - * the resource granularity. - * An xDS Delta session is always in the context of a gRPC bidirectional - * stream. This allows the xDS server to keep track of the state of xDS clients - * connected to it. - * - * In Delta xDS the nonce field is required and used to pair - * DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. - * Optionally, a response message level system_version_info is present for - * debugging purposes only. - * - * DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest - * can be either or both of: [1] informing the server of what resources the - * client has gained/lost interest in (using resource_names_subscribe and - * resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from - * the server (using response_nonce, with presence of error_detail making it a NACK). - * Additionally, the first message (for a given type_url) of a reconnected gRPC stream - * has a third role: informing the server of the resources (and their versions) - * that the client already possesses, using the initial_resource_versions field. - * - * As with state-of-the-world, when multiple resource types are multiplexed (ADS), - * all requests/acknowledgments/updates are logically walled off by type_url: - * a Cluster ACK exists in a completely separate world from a prior Route NACK. - * In particular, initial_resource_versions being sent at the "start" of every - * gRPC stream actually entails a message for each type_url, each with its own - * initial_resource_versions. - * [#next-free-field: 8] - */ -export interface DeltaDiscoveryRequest__Output { - /** - * The node making the request. - */ - 'node': (_envoy_api_v2_core_Node__Output | null); - /** - * Type of the resource that is being requested, e.g. - * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". - */ - 'type_url': (string); - /** - * DeltaDiscoveryRequests allow the client to add or remove individual - * resources to the set of tracked resources in the context of a stream. - * All resource names in the resource_names_subscribe list are added to the - * set of tracked resources and all resource names in the resource_names_unsubscribe - * list are removed from the set of tracked resources. - * - * *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or - * resource_names_unsubscribe list simply means that no resources are to be - * added or removed to the resource list. - * *Like* state-of-the-world xDS, the server must send updates for all tracked - * resources, but can also send updates for resources the client has not subscribed to. - * - * NOTE: the server must respond with all resources listed in resource_names_subscribe, - * even if it believes the client has the most recent version of them. The reason: - * the client may have dropped them, but then regained interest before it had a chance - * to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. - * - * These two fields can be set in any DeltaDiscoveryRequest, including ACKs - * and initial_resource_versions. - * - * A list of Resource names to add to the list of tracked resources. - */ - 'resource_names_subscribe': (string)[]; - /** - * A list of Resource names to remove from the list of tracked resources. - */ - 'resource_names_unsubscribe': (string)[]; - /** - * Informs the server of the versions of the resources the xDS client knows of, to enable the - * client to continue the same logical xDS session even in the face of gRPC stream reconnection. - * It will not be populated: [1] in the very first stream of a session, since the client will - * not yet have any resources, [2] in any message after the first in a stream (for a given - * type_url), since the server will already be correctly tracking the client's state. - * (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) - * The map's keys are names of xDS resources known to the xDS client. - * The map's values are opaque resource versions. - */ - 'initial_resource_versions': ({[key: string]: string}); - /** - * When the DeltaDiscoveryRequest is a ACK or NACK message in response - * to a previous DeltaDiscoveryResponse, the response_nonce must be the - * nonce in the DeltaDiscoveryResponse. - * Otherwise (unlike in DiscoveryRequest) response_nonce must be omitted. - */ - 'response_nonce': (string); - /** - * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* - * provides the Envoy internal exception related to the failure. - */ - 'error_detail': (_google_rpc_Status__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryResponse.ts deleted file mode 100644 index 1a2584b95..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/DeltaDiscoveryResponse.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/discovery.proto - -import type { Resource as _envoy_api_v2_Resource, Resource__Output as _envoy_api_v2_Resource__Output } from '../../../envoy/api/v2/Resource'; - -/** - * [#next-free-field: 7] - */ -export interface DeltaDiscoveryResponse { - /** - * The version of the response data (used for debugging). - */ - 'system_version_info'?: (string); - /** - * The response resources. These are typed resources, whose types must match - * the type_url field. - */ - 'resources'?: (_envoy_api_v2_Resource)[]; - /** - * Type URL for resources. Identifies the xDS API when muxing over ADS. - * Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. - */ - 'type_url'?: (string); - /** - * The nonce provides a way for DeltaDiscoveryRequests to uniquely - * reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. - */ - 'nonce'?: (string); - /** - * Resources names of resources that have be deleted and to be removed from the xDS Client. - * Removed resources for missing resources can be ignored. - */ - 'removed_resources'?: (string)[]; -} - -/** - * [#next-free-field: 7] - */ -export interface DeltaDiscoveryResponse__Output { - /** - * The version of the response data (used for debugging). - */ - 'system_version_info': (string); - /** - * The response resources. These are typed resources, whose types must match - * the type_url field. - */ - 'resources': (_envoy_api_v2_Resource__Output)[]; - /** - * Type URL for resources. Identifies the xDS API when muxing over ADS. - * Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. - */ - 'type_url': (string); - /** - * The nonce provides a way for DeltaDiscoveryRequests to uniquely - * reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. - */ - 'nonce': (string); - /** - * Resources names of resources that have be deleted and to be removed from the xDS Client. - * Removed resources for missing resources can be ignored. - */ - 'removed_resources': (string)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts deleted file mode 100644 index 2386e71c4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryRequest.ts +++ /dev/null @@ -1,110 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/discovery.proto - -import type { Node as _envoy_api_v2_core_Node, Node__Output as _envoy_api_v2_core_Node__Output } from '../../../envoy/api/v2/core/Node'; -import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../google/rpc/Status'; - -/** - * A DiscoveryRequest requests a set of versioned resources of the same type for - * a given Envoy node on some API. - * [#next-free-field: 7] - */ -export interface DiscoveryRequest { - /** - * The version_info provided in the request messages will be the version_info - * received with the most recent successfully processed response or empty on - * the first request. It is expected that no new request is sent after a - * response is received until the Envoy instance is ready to ACK/NACK the new - * configuration. ACK/NACK takes place by returning the new API config version - * as applied or the previous API config version respectively. Each type_url - * (see below) has an independent version associated with it. - */ - 'version_info'?: (string); - /** - * The node making the request. - */ - 'node'?: (_envoy_api_v2_core_Node | null); - /** - * List of resources to subscribe to, e.g. list of cluster names or a route - * configuration name. If this is empty, all resources for the API are - * returned. LDS/CDS may have empty resource_names, which will cause all - * resources for the Envoy instance to be returned. The LDS and CDS responses - * will then imply a number of resources that need to be fetched via EDS/RDS, - * which will be explicitly enumerated in resource_names. - */ - 'resource_names'?: (string)[]; - /** - * Type of the resource that is being requested, e.g. - * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This is implicit - * in requests made via singleton xDS APIs such as CDS, LDS, etc. but is - * required for ADS. - */ - 'type_url'?: (string); - /** - * nonce corresponding to DiscoveryResponse being ACK/NACKed. See above - * discussion on version_info and the DiscoveryResponse nonce comment. This - * may be empty only if 1) this is a non-persistent-stream xDS such as HTTP, - * or 2) the client has not yet accepted an update in this xDS stream (unlike - * delta, where it is populated only for new explicit ACKs). - */ - 'response_nonce'?: (string); - /** - * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* provides the Envoy - * internal exception related to the failure. It is only intended for consumption during manual - * debugging, the string provided is not guaranteed to be stable across Envoy versions. - */ - 'error_detail'?: (_google_rpc_Status | null); -} - -/** - * A DiscoveryRequest requests a set of versioned resources of the same type for - * a given Envoy node on some API. - * [#next-free-field: 7] - */ -export interface DiscoveryRequest__Output { - /** - * The version_info provided in the request messages will be the version_info - * received with the most recent successfully processed response or empty on - * the first request. It is expected that no new request is sent after a - * response is received until the Envoy instance is ready to ACK/NACK the new - * configuration. ACK/NACK takes place by returning the new API config version - * as applied or the previous API config version respectively. Each type_url - * (see below) has an independent version associated with it. - */ - 'version_info': (string); - /** - * The node making the request. - */ - 'node': (_envoy_api_v2_core_Node__Output | null); - /** - * List of resources to subscribe to, e.g. list of cluster names or a route - * configuration name. If this is empty, all resources for the API are - * returned. LDS/CDS may have empty resource_names, which will cause all - * resources for the Envoy instance to be returned. The LDS and CDS responses - * will then imply a number of resources that need to be fetched via EDS/RDS, - * which will be explicitly enumerated in resource_names. - */ - 'resource_names': (string)[]; - /** - * Type of the resource that is being requested, e.g. - * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This is implicit - * in requests made via singleton xDS APIs such as CDS, LDS, etc. but is - * required for ADS. - */ - 'type_url': (string); - /** - * nonce corresponding to DiscoveryResponse being ACK/NACKed. See above - * discussion on version_info and the DiscoveryResponse nonce comment. This - * may be empty only if 1) this is a non-persistent-stream xDS such as HTTP, - * or 2) the client has not yet accepted an update in this xDS stream (unlike - * delta, where it is populated only for new explicit ACKs). - */ - 'response_nonce': (string); - /** - * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* provides the Envoy - * internal exception related to the failure. It is only intended for consumption during manual - * debugging, the string provided is not guaranteed to be stable across Envoy versions. - */ - 'error_detail': (_google_rpc_Status__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts deleted file mode 100644 index 179143c67..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/DiscoveryResponse.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/discovery.proto - -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; -import type { ControlPlane as _envoy_api_v2_core_ControlPlane, ControlPlane__Output as _envoy_api_v2_core_ControlPlane__Output } from '../../../envoy/api/v2/core/ControlPlane'; - -/** - * [#next-free-field: 7] - */ -export interface DiscoveryResponse { - /** - * The version of the response data. - */ - 'version_info'?: (string); - /** - * The response resources. These resources are typed and depend on the API being called. - */ - 'resources'?: (_google_protobuf_Any)[]; - /** - * [#not-implemented-hide:] - * Canary is used to support two Envoy command line flags: - * - * * --terminate-on-canary-transition-failure. When set, Envoy is able to - * terminate if it detects that configuration is stuck at canary. Consider - * this example sequence of updates: - * - Management server applies a canary config successfully. - * - Management server rolls back to a production config. - * - Envoy rejects the new production config. - * Since there is no sensible way to continue receiving configuration - * updates, Envoy will then terminate and apply production config from a - * clean slate. - * * --dry-run-canary. When set, a canary response will never be applied, only - * validated via a dry run. - */ - 'canary'?: (boolean); - /** - * Type URL for resources. Identifies the xDS API when muxing over ADS. - * Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). - */ - 'type_url'?: (string); - /** - * For gRPC based subscriptions, the nonce provides a way to explicitly ack a - * specific DiscoveryResponse in a following DiscoveryRequest. Additional - * messages may have been sent by Envoy to the management server for the - * previous version on the stream prior to this DiscoveryResponse, that were - * unprocessed at response send time. The nonce allows the management server - * to ignore any further DiscoveryRequests for the previous version until a - * DiscoveryRequest bearing the nonce. The nonce is optional and is not - * required for non-stream based xDS implementations. - */ - 'nonce'?: (string); - /** - * [#not-implemented-hide:] - * The control plane instance that sent the response. - */ - 'control_plane'?: (_envoy_api_v2_core_ControlPlane | null); -} - -/** - * [#next-free-field: 7] - */ -export interface DiscoveryResponse__Output { - /** - * The version of the response data. - */ - 'version_info': (string); - /** - * The response resources. These resources are typed and depend on the API being called. - */ - 'resources': (_google_protobuf_Any__Output)[]; - /** - * [#not-implemented-hide:] - * Canary is used to support two Envoy command line flags: - * - * * --terminate-on-canary-transition-failure. When set, Envoy is able to - * terminate if it detects that configuration is stuck at canary. Consider - * this example sequence of updates: - * - Management server applies a canary config successfully. - * - Management server rolls back to a production config. - * - Envoy rejects the new production config. - * Since there is no sensible way to continue receiving configuration - * updates, Envoy will then terminate and apply production config from a - * clean slate. - * * --dry-run-canary. When set, a canary response will never be applied, only - * validated via a dry run. - */ - 'canary': (boolean); - /** - * Type URL for resources. Identifies the xDS API when muxing over ADS. - * Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). - */ - 'type_url': (string); - /** - * For gRPC based subscriptions, the nonce provides a way to explicitly ack a - * specific DiscoveryResponse in a following DiscoveryRequest. Additional - * messages may have been sent by Envoy to the management server for the - * previous version on the stream prior to this DiscoveryResponse, that were - * unprocessed at response send time. The nonce allows the management server - * to ignore any further DiscoveryRequests for the previous version until a - * DiscoveryRequest bearing the nonce. The nonce is optional and is not - * required for non-stream based xDS implementations. - */ - 'nonce': (string); - /** - * [#not-implemented-hide:] - * The control plane instance that sent the response. - */ - 'control_plane': (_envoy_api_v2_core_ControlPlane__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts deleted file mode 100644 index 6ce856ee7..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/Resource.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/discovery.proto - -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; - -export interface Resource { - /** - * The resource level version. It allows xDS to track the state of individual - * resources. - */ - 'version'?: (string); - /** - * The resource being tracked. - */ - 'resource'?: (_google_protobuf_Any | null); - /** - * The resource's name, to distinguish it from others of the same type of resource. - */ - 'name'?: (string); - /** - * The aliases are a list of other names that this resource can go by. - */ - 'aliases'?: (string)[]; -} - -export interface Resource__Output { - /** - * The resource level version. It allows xDS to track the state of individual - * resources. - */ - 'version': (string); - /** - * The resource being tracked. - */ - 'resource': (_google_protobuf_Any__Output | null); - /** - * The resource's name, to distinguish it from others of the same type of resource. - */ - 'name': (string); - /** - * The aliases are a list of other names that this resource can go by. - */ - 'aliases': (string)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts deleted file mode 100644 index 65044b74a..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Address.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/address.proto - -import type { SocketAddress as _envoy_api_v2_core_SocketAddress, SocketAddress__Output as _envoy_api_v2_core_SocketAddress__Output } from '../../../../envoy/api/v2/core/SocketAddress'; -import type { Pipe as _envoy_api_v2_core_Pipe, Pipe__Output as _envoy_api_v2_core_Pipe__Output } from '../../../../envoy/api/v2/core/Pipe'; - -/** - * Addresses specify either a logical or physical address and port, which are - * used to tell Envoy where to bind/listen, connect to upstream and find - * management servers. - */ -export interface Address { - 'socket_address'?: (_envoy_api_v2_core_SocketAddress | null); - 'pipe'?: (_envoy_api_v2_core_Pipe | null); - 'address'?: "socket_address"|"pipe"; -} - -/** - * Addresses specify either a logical or physical address and port, which are - * used to tell Envoy where to bind/listen, connect to upstream and find - * management servers. - */ -export interface Address__Output { - 'socket_address'?: (_envoy_api_v2_core_SocketAddress__Output | null); - 'pipe'?: (_envoy_api_v2_core_Pipe__Output | null); - 'address': "socket_address"|"pipe"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts deleted file mode 100644 index 139f82689..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/AsyncDataSource.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { DataSource as _envoy_api_v2_core_DataSource, DataSource__Output as _envoy_api_v2_core_DataSource__Output } from '../../../../envoy/api/v2/core/DataSource'; -import type { RemoteDataSource as _envoy_api_v2_core_RemoteDataSource, RemoteDataSource__Output as _envoy_api_v2_core_RemoteDataSource__Output } from '../../../../envoy/api/v2/core/RemoteDataSource'; - -/** - * Async data source which support async data fetch. - */ -export interface AsyncDataSource { - /** - * Local async data source. - */ - 'local'?: (_envoy_api_v2_core_DataSource | null); - /** - * Remote async data source. - */ - 'remote'?: (_envoy_api_v2_core_RemoteDataSource | null); - 'specifier'?: "local"|"remote"; -} - -/** - * Async data source which support async data fetch. - */ -export interface AsyncDataSource__Output { - /** - * Local async data source. - */ - 'local'?: (_envoy_api_v2_core_DataSource__Output | null); - /** - * Remote async data source. - */ - 'remote'?: (_envoy_api_v2_core_RemoteDataSource__Output | null); - 'specifier': "local"|"remote"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts deleted file mode 100644 index 173aff0e1..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BackoffStrategy.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/backoff.proto - -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; - -/** - * Configuration defining a jittered exponential back off strategy. - */ -export interface BackoffStrategy { - /** - * The base interval to be used for the next back off computation. It should - * be greater than zero and less than or equal to :ref:`max_interval - * `. - */ - 'base_interval'?: (_google_protobuf_Duration | null); - /** - * Specifies the maximum interval between retries. This parameter is optional, - * but must be greater than or equal to the :ref:`base_interval - * ` if set. The default - * is 10 times the :ref:`base_interval - * `. - */ - 'max_interval'?: (_google_protobuf_Duration | null); -} - -/** - * Configuration defining a jittered exponential back off strategy. - */ -export interface BackoffStrategy__Output { - /** - * The base interval to be used for the next back off computation. It should - * be greater than zero and less than or equal to :ref:`max_interval - * `. - */ - 'base_interval': (_google_protobuf_Duration__Output | null); - /** - * Specifies the maximum interval between retries. This parameter is optional, - * but must be greater than or equal to the :ref:`base_interval - * ` if set. The default - * is 10 times the :ref:`base_interval - * `. - */ - 'max_interval': (_google_protobuf_Duration__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts deleted file mode 100644 index d4765c1ba..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BindConfig.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/address.proto - -import type { SocketAddress as _envoy_api_v2_core_SocketAddress, SocketAddress__Output as _envoy_api_v2_core_SocketAddress__Output } from '../../../../envoy/api/v2/core/SocketAddress'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { SocketOption as _envoy_api_v2_core_SocketOption, SocketOption__Output as _envoy_api_v2_core_SocketOption__Output } from '../../../../envoy/api/v2/core/SocketOption'; - -export interface BindConfig { - /** - * The address to bind to when creating a socket. - */ - 'source_address'?: (_envoy_api_v2_core_SocketAddress | null); - /** - * Whether to set the *IP_FREEBIND* option when creating the socket. When this - * flag is set to true, allows the :ref:`source_address - * ` to be an IP address - * that is not configured on the system running Envoy. When this flag is set - * to false, the option *IP_FREEBIND* is disabled on the socket. When this - * flag is not set (default), the socket is not modified, i.e. the option is - * neither enabled nor disabled. - */ - 'freebind'?: (_google_protobuf_BoolValue | null); - /** - * Additional socket options that may not be present in Envoy source code or - * precompiled binaries. - */ - 'socket_options'?: (_envoy_api_v2_core_SocketOption)[]; -} - -export interface BindConfig__Output { - /** - * The address to bind to when creating a socket. - */ - 'source_address': (_envoy_api_v2_core_SocketAddress__Output | null); - /** - * Whether to set the *IP_FREEBIND* option when creating the socket. When this - * flag is set to true, allows the :ref:`source_address - * ` to be an IP address - * that is not configured on the system running Envoy. When this flag is set - * to false, the option *IP_FREEBIND* is disabled on the socket. When this - * flag is not set (default), the socket is not modified, i.e. the option is - * neither enabled nor disabled. - */ - 'freebind': (_google_protobuf_BoolValue__Output | null); - /** - * Additional socket options that may not be present in Envoy source code or - * precompiled binaries. - */ - 'socket_options': (_envoy_api_v2_core_SocketOption__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts deleted file mode 100644 index a33015d89..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/BuildVersion.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { SemanticVersion as _envoy_type_SemanticVersion, SemanticVersion__Output as _envoy_type_SemanticVersion__Output } from '../../../../envoy/type/SemanticVersion'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; - -/** - * BuildVersion combines SemVer version of extension with free-form build information - * (i.e. 'alpha', 'private-build') as a set of strings. - */ -export interface BuildVersion { - /** - * SemVer version of extension. - */ - 'version'?: (_envoy_type_SemanticVersion | null); - /** - * Free-form build information. - * Envoy defines several well known keys in the source/common/version/version.h file - */ - 'metadata'?: (_google_protobuf_Struct | null); -} - -/** - * BuildVersion combines SemVer version of extension with free-form build information - * (i.e. 'alpha', 'private-build') as a set of strings. - */ -export interface BuildVersion__Output { - /** - * SemVer version of extension. - */ - 'version': (_envoy_type_SemanticVersion__Output | null); - /** - * Free-form build information. - * Envoy defines several well known keys in the source/common/version/version.h file - */ - 'metadata': (_google_protobuf_Struct__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts deleted file mode 100644 index 5e1df8a13..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/CidrRange.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/address.proto - -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; - -/** - * CidrRange specifies an IP Address and a prefix length to construct - * the subnet mask for a `CIDR `_ range. - */ -export interface CidrRange { - /** - * IPv4 or IPv6 address, e.g. ``192.0.0.0`` or ``2001:db8::``. - */ - 'address_prefix'?: (string); - /** - * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. - */ - 'prefix_len'?: (_google_protobuf_UInt32Value | null); -} - -/** - * CidrRange specifies an IP Address and a prefix length to construct - * the subnet mask for a `CIDR `_ range. - */ -export interface CidrRange__Output { - /** - * IPv4 or IPv6 address, e.g. ``192.0.0.0`` or ``2001:db8::``. - */ - 'address_prefix': (string); - /** - * Length of prefix, e.g. 0, 32. Defaults to 0 when unset. - */ - 'prefix_len': (_google_protobuf_UInt32Value__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ControlPlane.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ControlPlane.ts deleted file mode 100644 index 551f693a2..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/ControlPlane.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - - -/** - * Identifies a specific ControlPlane instance that Envoy is connected to. - */ -export interface ControlPlane { - /** - * An opaque control plane identifier that uniquely identifies an instance - * of control plane. This can be used to identify which control plane instance, - * the Envoy is connected to. - */ - 'identifier'?: (string); -} - -/** - * Identifies a specific ControlPlane instance that Envoy is connected to. - */ -export interface ControlPlane__Output { - /** - * An opaque control plane identifier that uniquely identifies an instance - * of control plane. This can be used to identify which control plane instance, - * the Envoy is connected to. - */ - 'identifier': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/DataSource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/DataSource.ts deleted file mode 100644 index a04100054..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/DataSource.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - - -/** - * Data source consisting of either a file or an inline value. - */ -export interface DataSource { - /** - * Local filesystem data source. - */ - 'filename'?: (string); - /** - * Bytes inlined in the configuration. - */ - 'inline_bytes'?: (Buffer | Uint8Array | string); - /** - * String inlined in the configuration. - */ - 'inline_string'?: (string); - 'specifier'?: "filename"|"inline_bytes"|"inline_string"; -} - -/** - * Data source consisting of either a file or an inline value. - */ -export interface DataSource__Output { - /** - * Local filesystem data source. - */ - 'filename'?: (string); - /** - * Bytes inlined in the configuration. - */ - 'inline_bytes'?: (Buffer); - /** - * String inlined in the configuration. - */ - 'inline_string'?: (string); - 'specifier': "filename"|"inline_bytes"|"inline_string"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts deleted file mode 100644 index 67c5e1736..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Extension.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { BuildVersion as _envoy_api_v2_core_BuildVersion, BuildVersion__Output as _envoy_api_v2_core_BuildVersion__Output } from '../../../../envoy/api/v2/core/BuildVersion'; - -/** - * Version and identification for an Envoy extension. - * [#next-free-field: 6] - */ -export interface Extension { - /** - * This is the name of the Envoy filter as specified in the Envoy - * configuration, e.g. envoy.filters.http.router, com.acme.widget. - */ - 'name'?: (string); - /** - * Category of the extension. - * Extension category names use reverse DNS notation. For instance "envoy.filters.listener" - * for Envoy's built-in listener filters or "com.acme.filters.http" for HTTP filters from - * acme.com vendor. - * [#comment:TODO(yanavlasov): Link to the doc with existing envoy category names.] - */ - 'category'?: (string); - /** - * [#not-implemented-hide:] Type descriptor of extension configuration proto. - * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] - * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] - */ - 'type_descriptor'?: (string); - /** - * The version is a property of the extension and maintained independently - * of other extensions and the Envoy API. - * This field is not set when extension did not provide version information. - */ - 'version'?: (_envoy_api_v2_core_BuildVersion | null); - /** - * Indicates that the extension is present but was disabled via dynamic configuration. - */ - 'disabled'?: (boolean); -} - -/** - * Version and identification for an Envoy extension. - * [#next-free-field: 6] - */ -export interface Extension__Output { - /** - * This is the name of the Envoy filter as specified in the Envoy - * configuration, e.g. envoy.filters.http.router, com.acme.widget. - */ - 'name': (string); - /** - * Category of the extension. - * Extension category names use reverse DNS notation. For instance "envoy.filters.listener" - * for Envoy's built-in listener filters or "com.acme.filters.http" for HTTP filters from - * acme.com vendor. - * [#comment:TODO(yanavlasov): Link to the doc with existing envoy category names.] - */ - 'category': (string); - /** - * [#not-implemented-hide:] Type descriptor of extension configuration proto. - * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] - * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] - */ - 'type_descriptor': (string); - /** - * The version is a property of the extension and maintained independently - * of other extensions and the Envoy API. - * This field is not set when extension did not provide version information. - */ - 'version': (_envoy_api_v2_core_BuildVersion__Output | null); - /** - * Indicates that the extension is present but was disabled via dynamic configuration. - */ - 'disabled': (boolean); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderMap.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderMap.ts deleted file mode 100644 index e093d4761..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderMap.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { HeaderValue as _envoy_api_v2_core_HeaderValue, HeaderValue__Output as _envoy_api_v2_core_HeaderValue__Output } from '../../../../envoy/api/v2/core/HeaderValue'; - -/** - * Wrapper for a set of headers. - */ -export interface HeaderMap { - 'headers'?: (_envoy_api_v2_core_HeaderValue)[]; -} - -/** - * Wrapper for a set of headers. - */ -export interface HeaderMap__Output { - 'headers': (_envoy_api_v2_core_HeaderValue__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValue.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValue.ts deleted file mode 100644 index b36605324..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValue.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - - -/** - * Header name/value pair. - */ -export interface HeaderValue { - /** - * Header name. - */ - 'key'?: (string); - /** - * Header value. - * - * The same :ref:`format specifier ` as used for - * :ref:`HTTP access logging ` applies here, however - * unknown header values are replaced with the empty string instead of `-`. - */ - 'value'?: (string); -} - -/** - * Header name/value pair. - */ -export interface HeaderValue__Output { - /** - * Header name. - */ - 'key': (string); - /** - * Header value. - * - * The same :ref:`format specifier ` as used for - * :ref:`HTTP access logging ` applies here, however - * unknown header values are replaced with the empty string instead of `-`. - */ - 'value': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts deleted file mode 100644 index 12421d8f4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HeaderValueOption.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { HeaderValue as _envoy_api_v2_core_HeaderValue, HeaderValue__Output as _envoy_api_v2_core_HeaderValue__Output } from '../../../../envoy/api/v2/core/HeaderValue'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; - -/** - * Header name/value pair plus option to control append behavior. - */ -export interface HeaderValueOption { - /** - * Header name/value pair that this option applies to. - */ - 'header'?: (_envoy_api_v2_core_HeaderValue | null); - /** - * Should the value be appended? If true (default), the value is appended to - * existing values. - */ - 'append'?: (_google_protobuf_BoolValue | null); -} - -/** - * Header name/value pair plus option to control append behavior. - */ -export interface HeaderValueOption__Output { - /** - * Header name/value pair that this option applies to. - */ - 'header': (_envoy_api_v2_core_HeaderValue__Output | null); - /** - * Should the value be appended? If true (default), the value is appended to - * existing values. - */ - 'append': (_google_protobuf_BoolValue__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts deleted file mode 100644 index c206a2454..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/HttpUri.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/http_uri.proto - -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; - -/** - * Envoy external URI descriptor - */ -export interface HttpUri { - /** - * The HTTP server URI. It should be a full FQDN with protocol, host and path. - * - * Example: - * - * .. code-block:: yaml - * - * uri: https://www.googleapis.com/oauth2/v1/certs - */ - 'uri'?: (string); - /** - * A cluster is created in the Envoy "cluster_manager" config - * section. This field specifies the cluster name. - * - * Example: - * - * .. code-block:: yaml - * - * cluster: jwks_cluster - */ - 'cluster'?: (string); - /** - * Sets the maximum duration in milliseconds that a response can take to arrive upon request. - */ - 'timeout'?: (_google_protobuf_Duration | null); - /** - * Specify how `uri` is to be fetched. Today, this requires an explicit - * cluster, but in the future we may support dynamic cluster creation or - * inline DNS resolution. See `issue - * `_. - */ - 'http_upstream_type'?: "cluster"; -} - -/** - * Envoy external URI descriptor - */ -export interface HttpUri__Output { - /** - * The HTTP server URI. It should be a full FQDN with protocol, host and path. - * - * Example: - * - * .. code-block:: yaml - * - * uri: https://www.googleapis.com/oauth2/v1/certs - */ - 'uri': (string); - /** - * A cluster is created in the Envoy "cluster_manager" config - * section. This field specifies the cluster name. - * - * Example: - * - * .. code-block:: yaml - * - * cluster: jwks_cluster - */ - 'cluster'?: (string); - /** - * Sets the maximum duration in milliseconds that a response can take to arrive upon request. - */ - 'timeout': (_google_protobuf_Duration__Output | null); - /** - * Specify how `uri` is to be fetched. Today, this requires an explicit - * cluster, but in the future we may support dynamic cluster creation or - * inline DNS resolution. See `issue - * `_. - */ - 'http_upstream_type': "cluster"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Locality.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Locality.ts deleted file mode 100644 index 49fb232a4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Locality.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - - -/** - * Identifies location of where either Envoy runs or where upstream hosts run. - */ -export interface Locality { - /** - * Region this :ref:`zone ` belongs to. - */ - 'region'?: (string); - /** - * Defines the local service zone where Envoy is running. Though optional, it - * should be set if discovery service routing is used and the discovery - * service exposes :ref:`zone data `, - * either in this message or via :option:`--service-zone`. The meaning of zone - * is context dependent, e.g. `Availability Zone (AZ) - * `_ - * on AWS, `Zone `_ on - * GCP, etc. - */ - 'zone'?: (string); - /** - * When used for locality of upstream hosts, this field further splits zone - * into smaller chunks of sub-zones so they can be load balanced - * independently. - */ - 'sub_zone'?: (string); -} - -/** - * Identifies location of where either Envoy runs or where upstream hosts run. - */ -export interface Locality__Output { - /** - * Region this :ref:`zone ` belongs to. - */ - 'region': (string); - /** - * Defines the local service zone where Envoy is running. Though optional, it - * should be set if discovery service routing is used and the discovery - * service exposes :ref:`zone data `, - * either in this message or via :option:`--service-zone`. The meaning of zone - * is context dependent, e.g. `Availability Zone (AZ) - * `_ - * on AWS, `Zone `_ on - * GCP, etc. - */ - 'zone': (string); - /** - * When used for locality of upstream hosts, this field further splits zone - * into smaller chunks of sub-zones so they can be load balanced - * independently. - */ - 'sub_zone': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts deleted file mode 100644 index 7a6871eeb..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Metadata.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; - -/** - * Metadata provides additional inputs to filters based on matched listeners, - * filter chains, routes and endpoints. It is structured as a map, usually from - * filter name (in reverse DNS format) to metadata specific to the filter. Metadata - * key-values for a filter are merged as connection and request handling occurs, - * with later values for the same key overriding earlier values. - * - * An example use of metadata is providing additional values to - * http_connection_manager in the envoy.http_connection_manager.access_log - * namespace. - * - * Another example use of metadata is to per service config info in cluster metadata, which may get - * consumed by multiple filters. - * - * For load balancing, Metadata provides a means to subset cluster endpoints. - * Endpoints have a Metadata object associated and routes contain a Metadata - * object to match against. There are some well defined metadata used today for - * this purpose: - * - * * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an - * endpoint and is also used during header processing - * (x-envoy-upstream-canary) and for stats purposes. - * [#next-major-version: move to type/metadata/v2] - */ -export interface Metadata { - /** - * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* - * namespace is reserved for Envoy's built-in filters. - */ - 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct}); -} - -/** - * Metadata provides additional inputs to filters based on matched listeners, - * filter chains, routes and endpoints. It is structured as a map, usually from - * filter name (in reverse DNS format) to metadata specific to the filter. Metadata - * key-values for a filter are merged as connection and request handling occurs, - * with later values for the same key overriding earlier values. - * - * An example use of metadata is providing additional values to - * http_connection_manager in the envoy.http_connection_manager.access_log - * namespace. - * - * Another example use of metadata is to per service config info in cluster metadata, which may get - * consumed by multiple filters. - * - * For load balancing, Metadata provides a means to subset cluster endpoints. - * Endpoints have a Metadata object associated and routes contain a Metadata - * object to match against. There are some well defined metadata used today for - * this purpose: - * - * * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an - * endpoint and is also used during header processing - * (x-envoy-upstream-canary) and for stats purposes. - * [#next-major-version: move to type/metadata/v2] - */ -export interface Metadata__Output { - /** - * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* - * namespace is reserved for Envoy's built-in filters. - */ - 'filter_metadata': ({[key: string]: _google_protobuf_Struct__Output}); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts deleted file mode 100644 index ddf10f08b..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Node.ts +++ /dev/null @@ -1,173 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { Locality as _envoy_api_v2_core_Locality, Locality__Output as _envoy_api_v2_core_Locality__Output } from '../../../../envoy/api/v2/core/Locality'; -import type { BuildVersion as _envoy_api_v2_core_BuildVersion, BuildVersion__Output as _envoy_api_v2_core_BuildVersion__Output } from '../../../../envoy/api/v2/core/BuildVersion'; -import type { Extension as _envoy_api_v2_core_Extension, Extension__Output as _envoy_api_v2_core_Extension__Output } from '../../../../envoy/api/v2/core/Extension'; -import type { Address as _envoy_api_v2_core_Address, Address__Output as _envoy_api_v2_core_Address__Output } from '../../../../envoy/api/v2/core/Address'; - -/** - * Identifies a specific Envoy instance. The node identifier is presented to the - * management server, which may use this identifier to distinguish per Envoy - * configuration for serving. - * [#next-free-field: 12] - */ -export interface Node { - /** - * An opaque node identifier for the Envoy node. This also provides the local - * service node name. It should be set if any of the following features are - * used: :ref:`statsd `, :ref:`CDS - * `, and :ref:`HTTP tracing - * `, either in this message or via - * :option:`--service-node`. - */ - 'id'?: (string); - /** - * Defines the local service cluster name where Envoy is running. Though - * optional, it should be set if any of the following features are used: - * :ref:`statsd `, :ref:`health check cluster - * verification - * `, - * :ref:`runtime override directory `, - * :ref:`user agent addition - * `, - * :ref:`HTTP global rate limiting `, - * :ref:`CDS `, and :ref:`HTTP tracing - * `, either in this message or via - * :option:`--service-cluster`. - */ - 'cluster'?: (string); - /** - * Opaque metadata extending the node identifier. Envoy will pass this - * directly to the management server. - */ - 'metadata'?: (_google_protobuf_Struct | null); - /** - * Locality specifying where the Envoy instance is running. - */ - 'locality'?: (_envoy_api_v2_core_Locality | null); - /** - * This is motivated by informing a management server during canary which - * version of Envoy is being tested in a heterogeneous fleet. This will be set - * by Envoy in management server RPCs. - * This field is deprecated in favor of the user_agent_name and user_agent_version values. - */ - 'build_version'?: (string); - /** - * Free-form string that identifies the entity requesting config. - * E.g. "envoy" or "grpc" - */ - 'user_agent_name'?: (string); - /** - * Free-form string that identifies the version of the entity requesting config. - * E.g. "1.12.2" or "abcd1234", or "SpecialEnvoyBuild" - */ - 'user_agent_version'?: (string); - /** - * Structured version of the entity requesting config. - */ - 'user_agent_build_version'?: (_envoy_api_v2_core_BuildVersion | null); - /** - * List of extensions and their versions supported by the node. - */ - 'extensions'?: (_envoy_api_v2_core_Extension)[]; - /** - * Client feature support list. These are well known features described - * in the Envoy API repository for a given major version of an API. Client features - * use reverse DNS naming scheme, for example `com.acme.feature`. - * See :ref:`the list of features ` that xDS client may - * support. - */ - 'client_features'?: (string)[]; - /** - * Known listening ports on the node as a generic hint to the management server - * for filtering :ref:`listeners ` to be returned. For example, - * if there is a listener bound to port 80, the list can optionally contain the - * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. - */ - 'listening_addresses'?: (_envoy_api_v2_core_Address)[]; - 'user_agent_version_type'?: "user_agent_version"|"user_agent_build_version"; -} - -/** - * Identifies a specific Envoy instance. The node identifier is presented to the - * management server, which may use this identifier to distinguish per Envoy - * configuration for serving. - * [#next-free-field: 12] - */ -export interface Node__Output { - /** - * An opaque node identifier for the Envoy node. This also provides the local - * service node name. It should be set if any of the following features are - * used: :ref:`statsd `, :ref:`CDS - * `, and :ref:`HTTP tracing - * `, either in this message or via - * :option:`--service-node`. - */ - 'id': (string); - /** - * Defines the local service cluster name where Envoy is running. Though - * optional, it should be set if any of the following features are used: - * :ref:`statsd `, :ref:`health check cluster - * verification - * `, - * :ref:`runtime override directory `, - * :ref:`user agent addition - * `, - * :ref:`HTTP global rate limiting `, - * :ref:`CDS `, and :ref:`HTTP tracing - * `, either in this message or via - * :option:`--service-cluster`. - */ - 'cluster': (string); - /** - * Opaque metadata extending the node identifier. Envoy will pass this - * directly to the management server. - */ - 'metadata': (_google_protobuf_Struct__Output | null); - /** - * Locality specifying where the Envoy instance is running. - */ - 'locality': (_envoy_api_v2_core_Locality__Output | null); - /** - * This is motivated by informing a management server during canary which - * version of Envoy is being tested in a heterogeneous fleet. This will be set - * by Envoy in management server RPCs. - * This field is deprecated in favor of the user_agent_name and user_agent_version values. - */ - 'build_version': (string); - /** - * Free-form string that identifies the entity requesting config. - * E.g. "envoy" or "grpc" - */ - 'user_agent_name': (string); - /** - * Free-form string that identifies the version of the entity requesting config. - * E.g. "1.12.2" or "abcd1234", or "SpecialEnvoyBuild" - */ - 'user_agent_version'?: (string); - /** - * Structured version of the entity requesting config. - */ - 'user_agent_build_version'?: (_envoy_api_v2_core_BuildVersion__Output | null); - /** - * List of extensions and their versions supported by the node. - */ - 'extensions': (_envoy_api_v2_core_Extension__Output)[]; - /** - * Client feature support list. These are well known features described - * in the Envoy API repository for a given major version of an API. Client features - * use reverse DNS naming scheme, for example `com.acme.feature`. - * See :ref:`the list of features ` that xDS client may - * support. - */ - 'client_features': (string)[]; - /** - * Known listening ports on the node as a generic hint to the management server - * for filtering :ref:`listeners ` to be returned. For example, - * if there is a listener bound to port 80, the list can optionally contain the - * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. - */ - 'listening_addresses': (_envoy_api_v2_core_Address__Output)[]; - 'user_agent_version_type': "user_agent_version"|"user_agent_build_version"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Pipe.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Pipe.ts deleted file mode 100644 index 9e6cbb82d..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/Pipe.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/address.proto - - -export interface Pipe { - /** - * Unix Domain Socket path. On Linux, paths starting with '@' will use the - * abstract namespace. The starting '@' is replaced by a null byte by Envoy. - * Paths starting with '@' will result in an error in environments other than - * Linux. - */ - 'path'?: (string); - /** - * The mode for the Pipe. Not applicable for abstract sockets. - */ - 'mode'?: (number); -} - -export interface Pipe__Output { - /** - * Unix Domain Socket path. On Linux, paths starting with '@' will use the - * abstract namespace. The starting '@' is replaced by a null byte by Envoy. - * Paths starting with '@' will result in an error in environments other than - * Linux. - */ - 'path': (string); - /** - * The mode for the Pipe. Not applicable for abstract sockets. - */ - 'mode': (number); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts deleted file mode 100644 index 516dbf864..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RemoteDataSource.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { HttpUri as _envoy_api_v2_core_HttpUri, HttpUri__Output as _envoy_api_v2_core_HttpUri__Output } from '../../../../envoy/api/v2/core/HttpUri'; -import type { RetryPolicy as _envoy_api_v2_core_RetryPolicy, RetryPolicy__Output as _envoy_api_v2_core_RetryPolicy__Output } from '../../../../envoy/api/v2/core/RetryPolicy'; - -/** - * The message specifies how to fetch data from remote and how to verify it. - */ -export interface RemoteDataSource { - /** - * The HTTP URI to fetch the remote data. - */ - 'http_uri'?: (_envoy_api_v2_core_HttpUri | null); - /** - * SHA256 string for verifying data. - */ - 'sha256'?: (string); - /** - * Retry policy for fetching remote data. - */ - 'retry_policy'?: (_envoy_api_v2_core_RetryPolicy | null); -} - -/** - * The message specifies how to fetch data from remote and how to verify it. - */ -export interface RemoteDataSource__Output { - /** - * The HTTP URI to fetch the remote data. - */ - 'http_uri': (_envoy_api_v2_core_HttpUri__Output | null); - /** - * SHA256 string for verifying data. - */ - 'sha256': (string); - /** - * Retry policy for fetching remote data. - */ - 'retry_policy': (_envoy_api_v2_core_RetryPolicy__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RequestMethod.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RequestMethod.ts deleted file mode 100644 index 029e9882d..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RequestMethod.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -/** - * HTTP request method. - */ -export enum RequestMethod { - METHOD_UNSPECIFIED = 0, - GET = 1, - HEAD = 2, - POST = 3, - PUT = 4, - DELETE = 5, - CONNECT = 6, - OPTIONS = 7, - TRACE = 8, - PATCH = 9, -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts deleted file mode 100644 index 7ff35e375..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RetryPolicy.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { BackoffStrategy as _envoy_api_v2_core_BackoffStrategy, BackoffStrategy__Output as _envoy_api_v2_core_BackoffStrategy__Output } from '../../../../envoy/api/v2/core/BackoffStrategy'; -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; - -/** - * The message specifies the retry policy of remote data source when fetching fails. - */ -export interface RetryPolicy { - /** - * Specifies parameters that control :ref:`retry backoff strategy `. - * This parameter is optional, in which case the default base interval is 1000 milliseconds. The - * default maximum interval is 10 times the base interval. - */ - 'retry_back_off'?: (_envoy_api_v2_core_BackoffStrategy | null); - /** - * Specifies the allowed number of retries. This parameter is optional and - * defaults to 1. - */ - 'num_retries'?: (_google_protobuf_UInt32Value | null); -} - -/** - * The message specifies the retry policy of remote data source when fetching fails. - */ -export interface RetryPolicy__Output { - /** - * Specifies parameters that control :ref:`retry backoff strategy `. - * This parameter is optional, in which case the default base interval is 1000 milliseconds. The - * default maximum interval is 10 times the base interval. - */ - 'retry_back_off': (_envoy_api_v2_core_BackoffStrategy__Output | null); - /** - * Specifies the allowed number of retries. This parameter is optional and - * defaults to 1. - */ - 'num_retries': (_google_protobuf_UInt32Value__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RoutingPriority.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RoutingPriority.ts deleted file mode 100644 index 5937fceb2..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RoutingPriority.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -/** - * Envoy supports :ref:`upstream priority routing - * ` both at the route and the virtual - * cluster level. The current priority implementation uses different connection - * pool and circuit breaking settings for each priority level. This means that - * even for HTTP/2 requests, two physical connections will be used to an - * upstream host. In the future Envoy will likely support true HTTP/2 priority - * over a single upstream connection. - */ -export enum RoutingPriority { - DEFAULT = 0, - HIGH = 1, -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts deleted file mode 100644 index 1ea2b8146..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeDouble.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - - -/** - * Runtime derived double with a default when not specified. - */ -export interface RuntimeDouble { - /** - * Default value if runtime value is not available. - */ - 'default_value'?: (number | string); - /** - * Runtime key to get value for comparison. This value is used if defined. - */ - 'runtime_key'?: (string); -} - -/** - * Runtime derived double with a default when not specified. - */ -export interface RuntimeDouble__Output { - /** - * Default value if runtime value is not available. - */ - 'default_value': (number); - /** - * Runtime key to get value for comparison. This value is used if defined. - */ - 'runtime_key': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts deleted file mode 100644 index 4ad57de46..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFeatureFlag.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; - -/** - * Runtime derived bool with a default when not specified. - */ -export interface RuntimeFeatureFlag { - /** - * Default value if runtime value is not available. - */ - 'default_value'?: (_google_protobuf_BoolValue | null); - /** - * Runtime key to get value for comparison. This value is used if defined. The boolean value must - * be represented via its - * `canonical JSON encoding `_. - */ - 'runtime_key'?: (string); -} - -/** - * Runtime derived bool with a default when not specified. - */ -export interface RuntimeFeatureFlag__Output { - /** - * Default value if runtime value is not available. - */ - 'default_value': (_google_protobuf_BoolValue__Output | null); - /** - * Runtime key to get value for comparison. This value is used if defined. The boolean value must - * be represented via its - * `canonical JSON encoding `_. - */ - 'runtime_key': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts deleted file mode 100644 index a168af60c..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeFractionalPercent.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { FractionalPercent as _envoy_type_FractionalPercent, FractionalPercent__Output as _envoy_type_FractionalPercent__Output } from '../../../../envoy/type/FractionalPercent'; - -/** - * Runtime derived FractionalPercent with defaults for when the numerator or denominator is not - * specified via a runtime key. - * - * .. note:: - * - * Parsing of the runtime key's data is implemented such that it may be represented as a - * :ref:`FractionalPercent ` proto represented as JSON/YAML - * and may also be represented as an integer with the assumption that the value is an integral - * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse - * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. - */ -export interface RuntimeFractionalPercent { - /** - * Default value if the runtime value's for the numerator/denominator keys are not available. - */ - 'default_value'?: (_envoy_type_FractionalPercent | null); - /** - * Runtime key for a YAML representation of a FractionalPercent. - */ - 'runtime_key'?: (string); -} - -/** - * Runtime derived FractionalPercent with defaults for when the numerator or denominator is not - * specified via a runtime key. - * - * .. note:: - * - * Parsing of the runtime key's data is implemented such that it may be represented as a - * :ref:`FractionalPercent ` proto represented as JSON/YAML - * and may also be represented as an integer with the assumption that the value is an integral - * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse - * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. - */ -export interface RuntimeFractionalPercent__Output { - /** - * Default value if the runtime value's for the numerator/denominator keys are not available. - */ - 'default_value': (_envoy_type_FractionalPercent__Output | null); - /** - * Runtime key for a YAML representation of a FractionalPercent. - */ - 'runtime_key': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeUInt32.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeUInt32.ts deleted file mode 100644 index 72e8972a4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/RuntimeUInt32.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - - -/** - * Runtime derived uint32 with a default when not specified. - */ -export interface RuntimeUInt32 { - /** - * Default value if runtime value is not available. - */ - 'default_value'?: (number); - /** - * Runtime key to get value for comparison. This value is used if defined. - */ - 'runtime_key'?: (string); -} - -/** - * Runtime derived uint32 with a default when not specified. - */ -export interface RuntimeUInt32__Output { - /** - * Default value if runtime value is not available. - */ - 'default_value': (number); - /** - * Runtime key to get value for comparison. This value is used if defined. - */ - 'runtime_key': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketAddress.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketAddress.ts deleted file mode 100644 index f81c981c1..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketAddress.ts +++ /dev/null @@ -1,97 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/address.proto - - -// Original file: deps/envoy-api/envoy/api/v2/core/address.proto - -export enum _envoy_api_v2_core_SocketAddress_Protocol { - TCP = 0, - UDP = 1, -} - -/** - * [#next-free-field: 7] - */ -export interface SocketAddress { - 'protocol'?: (_envoy_api_v2_core_SocketAddress_Protocol | keyof typeof _envoy_api_v2_core_SocketAddress_Protocol); - /** - * The address for this socket. :ref:`Listeners ` will bind - * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` - * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: - * It is possible to distinguish a Listener address via the prefix/suffix matching - * in :ref:`FilterChainMatch `.] When used - * within an upstream :ref:`BindConfig `, the address - * controls the source address of outbound connections. For :ref:`clusters - * `, the cluster type determines whether the - * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS - * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - * via :ref:`resolver_name `. - */ - 'address'?: (string); - 'port_value'?: (number); - /** - * This is only valid if :ref:`resolver_name - * ` is specified below and the - * named resolver is capable of named port resolution. - */ - 'named_port'?: (string); - /** - * The name of the custom resolver. This must have been registered with Envoy. If - * this is empty, a context dependent default applies. If the address is a concrete - * IP address, no resolution will occur. If address is a hostname this - * should be set for resolution other than DNS. Specifying a custom resolver with - * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. - */ - 'resolver_name'?: (string); - /** - * When binding to an IPv6 address above, this enables `IPv4 compatibility - * `_. Binding to ``::`` will - * allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into - * IPv6 space as ``::FFFF:``. - */ - 'ipv4_compat'?: (boolean); - 'port_specifier'?: "port_value"|"named_port"; -} - -/** - * [#next-free-field: 7] - */ -export interface SocketAddress__Output { - 'protocol': (keyof typeof _envoy_api_v2_core_SocketAddress_Protocol); - /** - * The address for this socket. :ref:`Listeners ` will bind - * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` - * to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: - * It is possible to distinguish a Listener address via the prefix/suffix matching - * in :ref:`FilterChainMatch `.] When used - * within an upstream :ref:`BindConfig `, the address - * controls the source address of outbound connections. For :ref:`clusters - * `, the cluster type determines whether the - * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS - * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized - * via :ref:`resolver_name `. - */ - 'address': (string); - 'port_value'?: (number); - /** - * This is only valid if :ref:`resolver_name - * ` is specified below and the - * named resolver is capable of named port resolution. - */ - 'named_port'?: (string); - /** - * The name of the custom resolver. This must have been registered with Envoy. If - * this is empty, a context dependent default applies. If the address is a concrete - * IP address, no resolution will occur. If address is a hostname this - * should be set for resolution other than DNS. Specifying a custom resolver with - * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. - */ - 'resolver_name': (string); - /** - * When binding to an IPv6 address above, this enables `IPv4 compatibility - * `_. Binding to ``::`` will - * allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into - * IPv6 space as ``::FFFF:``. - */ - 'ipv4_compat': (boolean); - 'port_specifier': "port_value"|"named_port"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketOption.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketOption.ts deleted file mode 100644 index 4a32e46b4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/SocketOption.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/socket_option.proto - -import type { Long } from '@grpc/proto-loader'; - -// Original file: deps/envoy-api/envoy/api/v2/core/socket_option.proto - -export enum _envoy_api_v2_core_SocketOption_SocketState { - /** - * Socket options are applied after socket creation but before binding the socket to a port - */ - STATE_PREBIND = 0, - /** - * Socket options are applied after binding the socket to a port but before calling listen() - */ - STATE_BOUND = 1, - /** - * Socket options are applied after calling listen() - */ - STATE_LISTENING = 2, -} - -/** - * Generic socket option message. This would be used to set socket options that - * might not exist in upstream kernels or precompiled Envoy binaries. - * [#next-free-field: 7] - */ -export interface SocketOption { - /** - * An optional name to give this socket option for debugging, etc. - * Uniqueness is not required and no special meaning is assumed. - */ - 'description'?: (string); - /** - * Corresponding to the level value passed to setsockopt, such as IPPROTO_TCP - */ - 'level'?: (number | string | Long); - /** - * The numeric name as passed to setsockopt - */ - 'name'?: (number | string | Long); - /** - * Because many sockopts take an int value. - */ - 'int_value'?: (number | string | Long); - /** - * Otherwise it's a byte buffer. - */ - 'buf_value'?: (Buffer | Uint8Array | string); - /** - * The state in which the option will be applied. When used in BindConfig - * STATE_PREBIND is currently the only valid value. - */ - 'state'?: (_envoy_api_v2_core_SocketOption_SocketState | keyof typeof _envoy_api_v2_core_SocketOption_SocketState); - 'value'?: "int_value"|"buf_value"; -} - -/** - * Generic socket option message. This would be used to set socket options that - * might not exist in upstream kernels or precompiled Envoy binaries. - * [#next-free-field: 7] - */ -export interface SocketOption__Output { - /** - * An optional name to give this socket option for debugging, etc. - * Uniqueness is not required and no special meaning is assumed. - */ - 'description': (string); - /** - * Corresponding to the level value passed to setsockopt, such as IPPROTO_TCP - */ - 'level': (string); - /** - * The numeric name as passed to setsockopt - */ - 'name': (string); - /** - * Because many sockopts take an int value. - */ - 'int_value'?: (string); - /** - * Otherwise it's a byte buffer. - */ - 'buf_value'?: (Buffer); - /** - * The state in which the option will be applied. When used in BindConfig - * STATE_PREBIND is currently the only valid value. - */ - 'state': (keyof typeof _envoy_api_v2_core_SocketOption_SocketState); - 'value': "int_value"|"buf_value"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts deleted file mode 100644 index e9d805c76..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TcpKeepalive.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/address.proto - -import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; - -export interface TcpKeepalive { - /** - * Maximum number of keepalive probes to send without response before deciding - * the connection is dead. Default is to use the OS level configuration (unless - * overridden, Linux defaults to 9.) - */ - 'keepalive_probes'?: (_google_protobuf_UInt32Value | null); - /** - * The number of seconds a connection needs to be idle before keep-alive probes - * start being sent. Default is to use the OS level configuration (unless - * overridden, Linux defaults to 7200s (i.e., 2 hours.) - */ - 'keepalive_time'?: (_google_protobuf_UInt32Value | null); - /** - * The number of seconds between keep-alive probes. Default is to use the OS - * level configuration (unless overridden, Linux defaults to 75s.) - */ - 'keepalive_interval'?: (_google_protobuf_UInt32Value | null); -} - -export interface TcpKeepalive__Output { - /** - * Maximum number of keepalive probes to send without response before deciding - * the connection is dead. Default is to use the OS level configuration (unless - * overridden, Linux defaults to 9.) - */ - 'keepalive_probes': (_google_protobuf_UInt32Value__Output | null); - /** - * The number of seconds a connection needs to be idle before keep-alive probes - * start being sent. Default is to use the OS level configuration (unless - * overridden, Linux defaults to 7200s (i.e., 2 hours.) - */ - 'keepalive_time': (_google_protobuf_UInt32Value__Output | null); - /** - * The number of seconds between keep-alive probes. Default is to use the OS - * level configuration (unless overridden, Linux defaults to 75s.) - */ - 'keepalive_interval': (_google_protobuf_UInt32Value__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TrafficDirection.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TrafficDirection.ts deleted file mode 100644 index 41cf36523..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TrafficDirection.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -/** - * Identifies the direction of the traffic relative to the local Envoy. - */ -export enum TrafficDirection { - /** - * Default option is unspecified. - */ - UNSPECIFIED = 0, - /** - * The transport is used for incoming traffic. - */ - INBOUND = 1, - /** - * The transport is used for outgoing traffic. - */ - OUTBOUND = 2, -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts deleted file mode 100644 index 87fbcaf38..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/core/TransportSocket.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/core/base.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; - -/** - * Configuration for transport socket in :ref:`listeners ` and - * :ref:`clusters `. If the configuration is - * empty, a default transport socket implementation and configuration will be - * chosen based on the platform and existence of tls_context. - */ -export interface TransportSocket { - /** - * The name of the transport socket to instantiate. The name must match a supported transport - * socket implementation. - */ - 'name'?: (string); - 'config'?: (_google_protobuf_Struct | null); - 'typed_config'?: (_google_protobuf_Any | null); - /** - * Implementation specific configuration which depends on the implementation being instantiated. - * See the supported transport socket implementations for further documentation. - */ - 'config_type'?: "config"|"typed_config"; -} - -/** - * Configuration for transport socket in :ref:`listeners ` and - * :ref:`clusters `. If the configuration is - * empty, a default transport socket implementation and configuration will be - * chosen based on the platform and existence of tls_context. - */ -export interface TransportSocket__Output { - /** - * The name of the transport socket to instantiate. The name must match a supported transport - * socket implementation. - */ - 'name': (string); - 'config'?: (_google_protobuf_Struct__Output | null); - 'typed_config'?: (_google_protobuf_Any__Output | null); - /** - * Implementation specific configuration which depends on the implementation being instantiated. - * See the supported transport socket implementations for further documentation. - */ - 'config_type': "config"|"typed_config"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts deleted file mode 100644 index 3609ea1e4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/ClusterStats.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint/load_report.proto - -import type { UpstreamLocalityStats as _envoy_api_v2_endpoint_UpstreamLocalityStats, UpstreamLocalityStats__Output as _envoy_api_v2_endpoint_UpstreamLocalityStats__Output } from '../../../../envoy/api/v2/endpoint/UpstreamLocalityStats'; -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { Long } from '@grpc/proto-loader'; - -export interface _envoy_api_v2_endpoint_ClusterStats_DroppedRequests { - /** - * Identifier for the policy specifying the drop. - */ - 'category'?: (string); - /** - * Total number of deliberately dropped requests for the category. - */ - 'dropped_count'?: (number | string | Long); -} - -export interface _envoy_api_v2_endpoint_ClusterStats_DroppedRequests__Output { - /** - * Identifier for the policy specifying the drop. - */ - 'category': (string); - /** - * Total number of deliberately dropped requests for the category. - */ - 'dropped_count': (string); -} - -/** - * Per cluster load stats. Envoy reports these stats a management server in a - * :ref:`LoadStatsRequest` - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - * Next ID: 7 - * [#next-free-field: 7] - */ -export interface ClusterStats { - /** - * The name of the cluster. - */ - 'cluster_name'?: (string); - /** - * Need at least one. - */ - 'upstream_locality_stats'?: (_envoy_api_v2_endpoint_UpstreamLocalityStats)[]; - /** - * Cluster-level stats such as total_successful_requests may be computed by - * summing upstream_locality_stats. In addition, below there are additional - * cluster-wide stats. - * - * The total number of dropped requests. This covers requests - * deliberately dropped by the drop_overload policy and circuit breaking. - */ - 'total_dropped_requests'?: (number | string | Long); - /** - * Period over which the actual load report occurred. This will be guaranteed to include every - * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy - * and the *LoadStatsResponse* message sent from the management server, this may be longer than - * the requested load reporting interval in the *LoadStatsResponse*. - */ - 'load_report_interval'?: (_google_protobuf_Duration | null); - /** - * Information about deliberately dropped requests for each category specified - * in the DropOverload policy. - */ - 'dropped_requests'?: (_envoy_api_v2_endpoint_ClusterStats_DroppedRequests)[]; - /** - * The eds_cluster_config service_name of the cluster. - * It's possible that two clusters send the same service_name to EDS, - * in that case, the management server is supposed to do aggregation on the load reports. - */ - 'cluster_service_name'?: (string); -} - -/** - * Per cluster load stats. Envoy reports these stats a management server in a - * :ref:`LoadStatsRequest` - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - * Next ID: 7 - * [#next-free-field: 7] - */ -export interface ClusterStats__Output { - /** - * The name of the cluster. - */ - 'cluster_name': (string); - /** - * Need at least one. - */ - 'upstream_locality_stats': (_envoy_api_v2_endpoint_UpstreamLocalityStats__Output)[]; - /** - * Cluster-level stats such as total_successful_requests may be computed by - * summing upstream_locality_stats. In addition, below there are additional - * cluster-wide stats. - * - * The total number of dropped requests. This covers requests - * deliberately dropped by the drop_overload policy and circuit breaking. - */ - 'total_dropped_requests': (string); - /** - * Period over which the actual load report occurred. This will be guaranteed to include every - * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy - * and the *LoadStatsResponse* message sent from the management server, this may be longer than - * the requested load reporting interval in the *LoadStatsResponse*. - */ - 'load_report_interval': (_google_protobuf_Duration__Output | null); - /** - * Information about deliberately dropped requests for each category specified - * in the DropOverload policy. - */ - 'dropped_requests': (_envoy_api_v2_endpoint_ClusterStats_DroppedRequests__Output)[]; - /** - * The eds_cluster_config service_name of the cluster. - * It's possible that two clusters send the same service_name to EDS, - * in that case, the management server is supposed to do aggregation on the load reports. - */ - 'cluster_service_name': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts deleted file mode 100644 index 335b759b6..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/EndpointLoadMetricStats.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint/load_report.proto - -import type { Long } from '@grpc/proto-loader'; - -/** - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - */ -export interface EndpointLoadMetricStats { - /** - * Name of the metric; may be empty. - */ - 'metric_name'?: (string); - /** - * Number of calls that finished and included this metric. - */ - 'num_requests_finished_with_metric'?: (number | string | Long); - /** - * Sum of metric values across all calls that finished with this metric for - * load_reporting_interval. - */ - 'total_metric_value'?: (number | string); -} - -/** - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - */ -export interface EndpointLoadMetricStats__Output { - /** - * Name of the metric; may be empty. - */ - 'metric_name': (string); - /** - * Number of calls that finished and included this metric. - */ - 'num_requests_finished_with_metric': (string); - /** - * Sum of metric values across all calls that finished with this metric for - * load_reporting_interval. - */ - 'total_metric_value': (number); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts deleted file mode 100644 index e4551bd37..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamEndpointStats.ts +++ /dev/null @@ -1,106 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint/load_report.proto - -import type { Address as _envoy_api_v2_core_Address, Address__Output as _envoy_api_v2_core_Address__Output } from '../../../../envoy/api/v2/core/Address'; -import type { EndpointLoadMetricStats as _envoy_api_v2_endpoint_EndpointLoadMetricStats, EndpointLoadMetricStats__Output as _envoy_api_v2_endpoint_EndpointLoadMetricStats__Output } from '../../../../envoy/api/v2/endpoint/EndpointLoadMetricStats'; -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { Long } from '@grpc/proto-loader'; - -/** - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - * [#next-free-field: 8] - */ -export interface UpstreamEndpointStats { - /** - * Upstream host address. - */ - 'address'?: (_envoy_api_v2_core_Address | null); - /** - * The total number of requests successfully completed by the endpoints in the - * locality. These include non-5xx responses for HTTP, where errors - * originate at the client and the endpoint responded successfully. For gRPC, - * the grpc-status values are those not covered by total_error_requests below. - */ - 'total_successful_requests'?: (number | string | Long); - /** - * The total number of unfinished requests for this endpoint. - */ - 'total_requests_in_progress'?: (number | string | Long); - /** - * The total number of requests that failed due to errors at the endpoint. - * For HTTP these are responses with 5xx status codes and for gRPC the - * grpc-status values: - * - * - DeadlineExceeded - * - Unimplemented - * - Internal - * - Unavailable - * - Unknown - * - DataLoss - */ - 'total_error_requests'?: (number | string | Long); - /** - * Stats for multi-dimensional load balancing. - */ - 'load_metric_stats'?: (_envoy_api_v2_endpoint_EndpointLoadMetricStats)[]; - /** - * Opaque and implementation dependent metadata of the - * endpoint. Envoy will pass this directly to the management server. - */ - 'metadata'?: (_google_protobuf_Struct | null); - /** - * The total number of requests that were issued to this endpoint - * since the last report. A single TCP connection, HTTP or gRPC - * request or stream is counted as one request. - */ - 'total_issued_requests'?: (number | string | Long); -} - -/** - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - * [#next-free-field: 8] - */ -export interface UpstreamEndpointStats__Output { - /** - * Upstream host address. - */ - 'address': (_envoy_api_v2_core_Address__Output | null); - /** - * The total number of requests successfully completed by the endpoints in the - * locality. These include non-5xx responses for HTTP, where errors - * originate at the client and the endpoint responded successfully. For gRPC, - * the grpc-status values are those not covered by total_error_requests below. - */ - 'total_successful_requests': (string); - /** - * The total number of unfinished requests for this endpoint. - */ - 'total_requests_in_progress': (string); - /** - * The total number of requests that failed due to errors at the endpoint. - * For HTTP these are responses with 5xx status codes and for gRPC the - * grpc-status values: - * - * - DeadlineExceeded - * - Unimplemented - * - Internal - * - Unavailable - * - Unknown - * - DataLoss - */ - 'total_error_requests': (string); - /** - * Stats for multi-dimensional load balancing. - */ - 'load_metric_stats': (_envoy_api_v2_endpoint_EndpointLoadMetricStats__Output)[]; - /** - * Opaque and implementation dependent metadata of the - * endpoint. Envoy will pass this directly to the management server. - */ - 'metadata': (_google_protobuf_Struct__Output | null); - /** - * The total number of requests that were issued to this endpoint - * since the last report. A single TCP connection, HTTP or gRPC - * request or stream is counted as one request. - */ - 'total_issued_requests': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts b/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts deleted file mode 100644 index df4d4eb89..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/api/v2/endpoint/UpstreamLocalityStats.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Original file: deps/envoy-api/envoy/api/v2/endpoint/load_report.proto - -import type { Locality as _envoy_api_v2_core_Locality, Locality__Output as _envoy_api_v2_core_Locality__Output } from '../../../../envoy/api/v2/core/Locality'; -import type { EndpointLoadMetricStats as _envoy_api_v2_endpoint_EndpointLoadMetricStats, EndpointLoadMetricStats__Output as _envoy_api_v2_endpoint_EndpointLoadMetricStats__Output } from '../../../../envoy/api/v2/endpoint/EndpointLoadMetricStats'; -import type { UpstreamEndpointStats as _envoy_api_v2_endpoint_UpstreamEndpointStats, UpstreamEndpointStats__Output as _envoy_api_v2_endpoint_UpstreamEndpointStats__Output } from '../../../../envoy/api/v2/endpoint/UpstreamEndpointStats'; -import type { Long } from '@grpc/proto-loader'; - -/** - * These are stats Envoy reports to GLB every so often. Report frequency is - * defined by - * :ref:`LoadStatsResponse.load_reporting_interval`. - * Stats per upstream region/zone and optionally per subzone. - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - * [#next-free-field: 9] - */ -export interface UpstreamLocalityStats { - /** - * Name of zone, region and optionally endpoint group these metrics were - * collected from. Zone and region names could be empty if unknown. - */ - 'locality'?: (_envoy_api_v2_core_Locality | null); - /** - * The total number of requests successfully completed by the endpoints in the - * locality. - */ - 'total_successful_requests'?: (number | string | Long); - /** - * The total number of unfinished requests - */ - 'total_requests_in_progress'?: (number | string | Long); - /** - * The total number of requests that failed due to errors at the endpoint, - * aggregated over all endpoints in the locality. - */ - 'total_error_requests'?: (number | string | Long); - /** - * Stats for multi-dimensional load balancing. - */ - 'load_metric_stats'?: (_envoy_api_v2_endpoint_EndpointLoadMetricStats)[]; - /** - * [#not-implemented-hide:] The priority of the endpoint group these metrics - * were collected from. - */ - 'priority'?: (number); - /** - * Endpoint granularity stats information for this locality. This information - * is populated if the Server requests it by setting - * :ref:`LoadStatsResponse.report_endpoint_granularity`. - */ - 'upstream_endpoint_stats'?: (_envoy_api_v2_endpoint_UpstreamEndpointStats)[]; - /** - * The total number of requests that were issued by this Envoy since - * the last report. This information is aggregated over all the - * upstream endpoints in the locality. - */ - 'total_issued_requests'?: (number | string | Long); -} - -/** - * These are stats Envoy reports to GLB every so often. Report frequency is - * defined by - * :ref:`LoadStatsResponse.load_reporting_interval`. - * Stats per upstream region/zone and optionally per subzone. - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - * [#next-free-field: 9] - */ -export interface UpstreamLocalityStats__Output { - /** - * Name of zone, region and optionally endpoint group these metrics were - * collected from. Zone and region names could be empty if unknown. - */ - 'locality': (_envoy_api_v2_core_Locality__Output | null); - /** - * The total number of requests successfully completed by the endpoints in the - * locality. - */ - 'total_successful_requests': (string); - /** - * The total number of unfinished requests - */ - 'total_requests_in_progress': (string); - /** - * The total number of requests that failed due to errors at the endpoint, - * aggregated over all endpoints in the locality. - */ - 'total_error_requests': (string); - /** - * Stats for multi-dimensional load balancing. - */ - 'load_metric_stats': (_envoy_api_v2_endpoint_EndpointLoadMetricStats__Output)[]; - /** - * [#not-implemented-hide:] The priority of the endpoint group these metrics - * were collected from. - */ - 'priority': (number); - /** - * Endpoint granularity stats information for this locality. This information - * is populated if the Server requests it by setting - * :ref:`LoadStatsResponse.report_endpoint_granularity`. - */ - 'upstream_endpoint_stats': (_envoy_api_v2_endpoint_UpstreamEndpointStats__Output)[]; - /** - * The total number of requests that were issued by this Envoy since - * the last report. This information is aggregated over all the - * upstream endpoints in the locality. - */ - 'total_issued_requests': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AdsDummy.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AdsDummy.ts deleted file mode 100644 index eeb6aa6af..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AdsDummy.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Original file: deps/envoy-api/envoy/service/discovery/v2/ads.proto - - -/** - * [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing - * services: https://github.com/google/protobuf/issues/4221 - */ -export interface AdsDummy { -} - -/** - * [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing - * services: https://github.com/google/protobuf/issues/4221 - */ -export interface AdsDummy__Output { -} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts deleted file mode 100644 index 6044118bc..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v2/AggregatedDiscoveryService.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Original file: deps/envoy-api/envoy/service/discovery/v2/ads.proto - -import type * as grpc from '@grpc/grpc-js' -import type { MethodDefinition } from '@grpc/proto-loader' -import type { DeltaDiscoveryRequest as _envoy_api_v2_DeltaDiscoveryRequest, DeltaDiscoveryRequest__Output as _envoy_api_v2_DeltaDiscoveryRequest__Output } from '../../../../envoy/api/v2/DeltaDiscoveryRequest'; -import type { DeltaDiscoveryResponse as _envoy_api_v2_DeltaDiscoveryResponse, DeltaDiscoveryResponse__Output as _envoy_api_v2_DeltaDiscoveryResponse__Output } from '../../../../envoy/api/v2/DeltaDiscoveryResponse'; -import type { DiscoveryRequest as _envoy_api_v2_DiscoveryRequest, DiscoveryRequest__Output as _envoy_api_v2_DiscoveryRequest__Output } from '../../../../envoy/api/v2/DiscoveryRequest'; -import type { DiscoveryResponse as _envoy_api_v2_DiscoveryResponse, DiscoveryResponse__Output as _envoy_api_v2_DiscoveryResponse__Output } from '../../../../envoy/api/v2/DiscoveryResponse'; - -/** - * See https://github.com/lyft/envoy-api#apis for a description of the role of - * ADS and how it is intended to be used by a management server. ADS requests - * have the same structure as their singleton xDS counterparts, but can - * multiplex many resource types on a single stream. The type_url in the - * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover - * the multiplexed singleton APIs at the Envoy instance and management server. - */ -export interface AggregatedDiscoveryServiceClient extends grpc.Client { - DeltaAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DeltaDiscoveryRequest, _envoy_api_v2_DeltaDiscoveryResponse__Output>; - DeltaAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DeltaDiscoveryRequest, _envoy_api_v2_DeltaDiscoveryResponse__Output>; - deltaAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DeltaDiscoveryRequest, _envoy_api_v2_DeltaDiscoveryResponse__Output>; - deltaAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DeltaDiscoveryRequest, _envoy_api_v2_DeltaDiscoveryResponse__Output>; - - /** - * This is a gRPC-only API. - */ - StreamAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DiscoveryRequest, _envoy_api_v2_DiscoveryResponse__Output>; - StreamAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DiscoveryRequest, _envoy_api_v2_DiscoveryResponse__Output>; - /** - * This is a gRPC-only API. - */ - streamAggregatedResources(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DiscoveryRequest, _envoy_api_v2_DiscoveryResponse__Output>; - streamAggregatedResources(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_api_v2_DiscoveryRequest, _envoy_api_v2_DiscoveryResponse__Output>; - -} - -/** - * See https://github.com/lyft/envoy-api#apis for a description of the role of - * ADS and how it is intended to be used by a management server. ADS requests - * have the same structure as their singleton xDS counterparts, but can - * multiplex many resource types on a single stream. The type_url in the - * DiscoveryRequest/DiscoveryResponse provides sufficient information to recover - * the multiplexed singleton APIs at the Envoy instance and management server. - */ -export interface AggregatedDiscoveryServiceHandlers extends grpc.UntypedServiceImplementation { - DeltaAggregatedResources: grpc.handleBidiStreamingCall<_envoy_api_v2_DeltaDiscoveryRequest__Output, _envoy_api_v2_DeltaDiscoveryResponse>; - - /** - * This is a gRPC-only API. - */ - StreamAggregatedResources: grpc.handleBidiStreamingCall<_envoy_api_v2_DiscoveryRequest__Output, _envoy_api_v2_DiscoveryResponse>; - -} - -export interface AggregatedDiscoveryServiceDefinition extends grpc.ServiceDefinition { - DeltaAggregatedResources: MethodDefinition<_envoy_api_v2_DeltaDiscoveryRequest, _envoy_api_v2_DeltaDiscoveryResponse, _envoy_api_v2_DeltaDiscoveryRequest__Output, _envoy_api_v2_DeltaDiscoveryResponse__Output> - StreamAggregatedResources: MethodDefinition<_envoy_api_v2_DiscoveryRequest, _envoy_api_v2_DiscoveryResponse, _envoy_api_v2_DiscoveryRequest__Output, _envoy_api_v2_DiscoveryResponse__Output> -} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts deleted file mode 100644 index 2a95752de..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadReportingService.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Original file: deps/envoy-api/envoy/service/load_stats/v2/lrs.proto - -import type * as grpc from '@grpc/grpc-js' -import type { MethodDefinition } from '@grpc/proto-loader' -import type { LoadStatsRequest as _envoy_service_load_stats_v2_LoadStatsRequest, LoadStatsRequest__Output as _envoy_service_load_stats_v2_LoadStatsRequest__Output } from '../../../../envoy/service/load_stats/v2/LoadStatsRequest'; -import type { LoadStatsResponse as _envoy_service_load_stats_v2_LoadStatsResponse, LoadStatsResponse__Output as _envoy_service_load_stats_v2_LoadStatsResponse__Output } from '../../../../envoy/service/load_stats/v2/LoadStatsResponse'; - -export interface LoadReportingServiceClient extends grpc.Client { - /** - * Advanced API to allow for multi-dimensional load balancing by remote - * server. For receiving LB assignments, the steps are: - * 1, The management server is configured with per cluster/zone/load metric - * capacity configuration. The capacity configuration definition is - * outside of the scope of this document. - * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters - * to balance. - * - * Independently, Envoy will initiate a StreamLoadStats bidi stream with a - * management server: - * 1. Once a connection establishes, the management server publishes a - * LoadStatsResponse for all clusters it is interested in learning load - * stats about. - * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts - * based on per-zone weights and/or per-instance weights (if specified) - * based on intra-zone LbPolicy. This information comes from the above - * {Stream,Fetch}Endpoints. - * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. - * 4. Envoy aggregates load reports over the period of time given to it in - * LoadStatsResponse.load_reporting_interval. This includes aggregation - * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as - * well as load metrics from upstream hosts. - * 5. When the timer of load_reporting_interval expires, Envoy sends new - * LoadStatsRequest filled with load reports for each cluster. - * 6. The management server uses the load reports from all reported Envoys - * from around the world, computes global assignment and prepares traffic - * assignment destined for each zone Envoys are located in. Goto 2. - */ - StreamLoadStats(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v2_LoadStatsRequest, _envoy_service_load_stats_v2_LoadStatsResponse__Output>; - StreamLoadStats(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v2_LoadStatsRequest, _envoy_service_load_stats_v2_LoadStatsResponse__Output>; - /** - * Advanced API to allow for multi-dimensional load balancing by remote - * server. For receiving LB assignments, the steps are: - * 1, The management server is configured with per cluster/zone/load metric - * capacity configuration. The capacity configuration definition is - * outside of the scope of this document. - * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters - * to balance. - * - * Independently, Envoy will initiate a StreamLoadStats bidi stream with a - * management server: - * 1. Once a connection establishes, the management server publishes a - * LoadStatsResponse for all clusters it is interested in learning load - * stats about. - * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts - * based on per-zone weights and/or per-instance weights (if specified) - * based on intra-zone LbPolicy. This information comes from the above - * {Stream,Fetch}Endpoints. - * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. - * 4. Envoy aggregates load reports over the period of time given to it in - * LoadStatsResponse.load_reporting_interval. This includes aggregation - * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as - * well as load metrics from upstream hosts. - * 5. When the timer of load_reporting_interval expires, Envoy sends new - * LoadStatsRequest filled with load reports for each cluster. - * 6. The management server uses the load reports from all reported Envoys - * from around the world, computes global assignment and prepares traffic - * assignment destined for each zone Envoys are located in. Goto 2. - */ - streamLoadStats(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v2_LoadStatsRequest, _envoy_service_load_stats_v2_LoadStatsResponse__Output>; - streamLoadStats(options?: grpc.CallOptions): grpc.ClientDuplexStream<_envoy_service_load_stats_v2_LoadStatsRequest, _envoy_service_load_stats_v2_LoadStatsResponse__Output>; - -} - -export interface LoadReportingServiceHandlers extends grpc.UntypedServiceImplementation { - /** - * Advanced API to allow for multi-dimensional load balancing by remote - * server. For receiving LB assignments, the steps are: - * 1, The management server is configured with per cluster/zone/load metric - * capacity configuration. The capacity configuration definition is - * outside of the scope of this document. - * 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters - * to balance. - * - * Independently, Envoy will initiate a StreamLoadStats bidi stream with a - * management server: - * 1. Once a connection establishes, the management server publishes a - * LoadStatsResponse for all clusters it is interested in learning load - * stats about. - * 2. For each cluster, Envoy load balances incoming traffic to upstream hosts - * based on per-zone weights and/or per-instance weights (if specified) - * based on intra-zone LbPolicy. This information comes from the above - * {Stream,Fetch}Endpoints. - * 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. - * 4. Envoy aggregates load reports over the period of time given to it in - * LoadStatsResponse.load_reporting_interval. This includes aggregation - * stats Envoy maintains by itself (total_requests, rpc_errors etc.) as - * well as load metrics from upstream hosts. - * 5. When the timer of load_reporting_interval expires, Envoy sends new - * LoadStatsRequest filled with load reports for each cluster. - * 6. The management server uses the load reports from all reported Envoys - * from around the world, computes global assignment and prepares traffic - * assignment destined for each zone Envoys are located in. Goto 2. - */ - StreamLoadStats: grpc.handleBidiStreamingCall<_envoy_service_load_stats_v2_LoadStatsRequest__Output, _envoy_service_load_stats_v2_LoadStatsResponse>; - -} - -export interface LoadReportingServiceDefinition extends grpc.ServiceDefinition { - StreamLoadStats: MethodDefinition<_envoy_service_load_stats_v2_LoadStatsRequest, _envoy_service_load_stats_v2_LoadStatsResponse, _envoy_service_load_stats_v2_LoadStatsRequest__Output, _envoy_service_load_stats_v2_LoadStatsResponse__Output> -} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts deleted file mode 100644 index 3cd5ebc2f..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsRequest.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Original file: deps/envoy-api/envoy/service/load_stats/v2/lrs.proto - -import type { Node as _envoy_api_v2_core_Node, Node__Output as _envoy_api_v2_core_Node__Output } from '../../../../envoy/api/v2/core/Node'; -import type { ClusterStats as _envoy_api_v2_endpoint_ClusterStats, ClusterStats__Output as _envoy_api_v2_endpoint_ClusterStats__Output } from '../../../../envoy/api/v2/endpoint/ClusterStats'; - -/** - * A load report Envoy sends to the management server. - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - */ -export interface LoadStatsRequest { - /** - * Node identifier for Envoy instance. - */ - 'node'?: (_envoy_api_v2_core_Node | null); - /** - * A list of load stats to report. - */ - 'cluster_stats'?: (_envoy_api_v2_endpoint_ClusterStats)[]; -} - -/** - * A load report Envoy sends to the management server. - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - */ -export interface LoadStatsRequest__Output { - /** - * Node identifier for Envoy instance. - */ - 'node': (_envoy_api_v2_core_Node__Output | null); - /** - * A list of load stats to report. - */ - 'cluster_stats': (_envoy_api_v2_endpoint_ClusterStats__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts deleted file mode 100644 index 1065fe22f..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v2/LoadStatsResponse.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Original file: deps/envoy-api/envoy/service/load_stats/v2/lrs.proto - -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; - -/** - * The management server sends envoy a LoadStatsResponse with all clusters it - * is interested in learning load stats about. - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - */ -export interface LoadStatsResponse { - /** - * Clusters to report stats for. - * Not populated if *send_all_clusters* is true. - */ - 'clusters'?: (string)[]; - /** - * The minimum interval of time to collect stats over. This is only a minimum for two reasons: - * 1. There may be some delay from when the timer fires until stats sampling occurs. - * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic - * that is observed in between the corresponding previous *LoadStatsRequest* and this - * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period - * of inobservability that might otherwise exists between the messages. New clusters are not - * subject to this consideration. - */ - 'load_reporting_interval'?: (_google_protobuf_Duration | null); - /** - * Set to *true* if the management server supports endpoint granularity - * report. - */ - 'report_endpoint_granularity'?: (boolean); - /** - * If true, the client should send all clusters it knows about. - * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - * :ref:`client_features` field will honor this field. - */ - 'send_all_clusters'?: (boolean); -} - -/** - * The management server sends envoy a LoadStatsResponse with all clusters it - * is interested in learning load stats about. - * [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. - */ -export interface LoadStatsResponse__Output { - /** - * Clusters to report stats for. - * Not populated if *send_all_clusters* is true. - */ - 'clusters': (string)[]; - /** - * The minimum interval of time to collect stats over. This is only a minimum for two reasons: - * 1. There may be some delay from when the timer fires until stats sampling occurs. - * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic - * that is observed in between the corresponding previous *LoadStatsRequest* and this - * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period - * of inobservability that might otherwise exists between the messages. New clusters are not - * subject to this consideration. - */ - 'load_reporting_interval': (_google_protobuf_Duration__Output | null); - /** - * Set to *true* if the management server supports endpoint granularity - * report. - */ - 'report_endpoint_granularity': (boolean); - /** - * If true, the client should send all clusters it knows about. - * Only clients that advertise the "envoy.lrs.supports_send_all_clusters" capability in their - * :ref:`client_features` field will honor this field. - */ - 'send_all_clusters': (boolean); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/FractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/type/FractionalPercent.ts deleted file mode 100644 index e450f0bfa..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/type/FractionalPercent.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Original file: deps/envoy-api/envoy/type/percent.proto - - -// Original file: deps/envoy-api/envoy/type/percent.proto - -/** - * Fraction percentages support several fixed denominator values. - */ -export enum _envoy_type_FractionalPercent_DenominatorType { - /** - * 100. - * - * **Example**: 1/100 = 1%. - */ - HUNDRED = 0, - /** - * 10,000. - * - * **Example**: 1/10000 = 0.01%. - */ - TEN_THOUSAND = 1, - /** - * 1,000,000. - * - * **Example**: 1/1000000 = 0.0001%. - */ - MILLION = 2, -} - -/** - * A fractional percentage is used in cases in which for performance reasons performing floating - * point to integer conversions during randomness calculations is undesirable. The message includes - * both a numerator and denominator that together determine the final fractional value. - * - * * **Example**: 1/100 = 1%. - * * **Example**: 3/10000 = 0.03%. - */ -export interface FractionalPercent { - /** - * Specifies the numerator. Defaults to 0. - */ - 'numerator'?: (number); - /** - * Specifies the denominator. If the denominator specified is less than the numerator, the final - * fractional percentage is capped at 1 (100%). - */ - 'denominator'?: (_envoy_type_FractionalPercent_DenominatorType | keyof typeof _envoy_type_FractionalPercent_DenominatorType); -} - -/** - * A fractional percentage is used in cases in which for performance reasons performing floating - * point to integer conversions during randomness calculations is undesirable. The message includes - * both a numerator and denominator that together determine the final fractional value. - * - * * **Example**: 1/100 = 1%. - * * **Example**: 3/10000 = 0.03%. - */ -export interface FractionalPercent__Output { - /** - * Specifies the numerator. Defaults to 0. - */ - 'numerator': (number); - /** - * Specifies the denominator. If the denominator specified is less than the numerator, the final - * fractional percentage is capped at 1 (100%). - */ - 'denominator': (keyof typeof _envoy_type_FractionalPercent_DenominatorType); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/Percent.ts b/packages/grpc-js-xds/src/generated/envoy/type/Percent.ts deleted file mode 100644 index 364419994..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/type/Percent.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Original file: deps/envoy-api/envoy/type/percent.proto - - -/** - * Identifies a percentage, in the range [0.0, 100.0]. - */ -export interface Percent { - 'value'?: (number | string); -} - -/** - * Identifies a percentage, in the range [0.0, 100.0]. - */ -export interface Percent__Output { - 'value': (number); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/type/SemanticVersion.ts b/packages/grpc-js-xds/src/generated/envoy/type/SemanticVersion.ts deleted file mode 100644 index f99431703..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/type/SemanticVersion.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Original file: deps/envoy-api/envoy/type/semantic_version.proto - - -/** - * Envoy uses SemVer (https://semver.org/). Major/minor versions indicate - * expected behaviors and APIs, the patch version field is used only - * for security fixes and can be generally ignored. - */ -export interface SemanticVersion { - 'major_number'?: (number); - 'minor_number'?: (number); - 'patch'?: (number); -} - -/** - * Envoy uses SemVer (https://semver.org/). Major/minor versions indicate - * expected behaviors and APIs, the patch version field is used only - * for security fixes and can be generally ignored. - */ -export interface SemanticVersion__Output { - 'major_number': (number); - 'minor_number': (number); - 'patch': (number); -} diff --git a/packages/grpc-js-xds/src/generated/lrs.ts b/packages/grpc-js-xds/src/generated/lrs.ts index e92f80800..e57d6c249 100644 --- a/packages/grpc-js-xds/src/generated/lrs.ts +++ b/packages/grpc-js-xds/src/generated/lrs.ts @@ -1,7 +1,6 @@ import type * as grpc from '@grpc/grpc-js'; import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; -import type { LoadReportingServiceClient as _envoy_service_load_stats_v2_LoadReportingServiceClient, LoadReportingServiceDefinition as _envoy_service_load_stats_v2_LoadReportingServiceDefinition } from './envoy/service/load_stats/v2/LoadReportingService'; import type { LoadReportingServiceClient as _envoy_service_load_stats_v3_LoadReportingServiceClient, LoadReportingServiceDefinition as _envoy_service_load_stats_v3_LoadReportingServiceDefinition } from './envoy/service/load_stats/v3/LoadReportingService'; type SubtypeConstructor any, Subtype> = { @@ -12,48 +11,6 @@ export interface ProtoGrpcType { envoy: { annotations: { } - api: { - v2: { - core: { - Address: MessageTypeDefinition - AsyncDataSource: MessageTypeDefinition - BackoffStrategy: MessageTypeDefinition - BindConfig: MessageTypeDefinition - BuildVersion: MessageTypeDefinition - CidrRange: MessageTypeDefinition - ControlPlane: MessageTypeDefinition - DataSource: MessageTypeDefinition - Extension: MessageTypeDefinition - HeaderMap: MessageTypeDefinition - HeaderValue: MessageTypeDefinition - HeaderValueOption: MessageTypeDefinition - HttpUri: MessageTypeDefinition - Locality: MessageTypeDefinition - Metadata: MessageTypeDefinition - Node: MessageTypeDefinition - Pipe: MessageTypeDefinition - RemoteDataSource: MessageTypeDefinition - RequestMethod: EnumTypeDefinition - RetryPolicy: MessageTypeDefinition - RoutingPriority: EnumTypeDefinition - RuntimeDouble: MessageTypeDefinition - RuntimeFeatureFlag: MessageTypeDefinition - RuntimeFractionalPercent: MessageTypeDefinition - RuntimeUInt32: MessageTypeDefinition - SocketAddress: MessageTypeDefinition - SocketOption: MessageTypeDefinition - TcpKeepalive: MessageTypeDefinition - TrafficDirection: EnumTypeDefinition - TransportSocket: MessageTypeDefinition - } - endpoint: { - ClusterStats: MessageTypeDefinition - EndpointLoadMetricStats: MessageTypeDefinition - UpstreamEndpointStats: MessageTypeDefinition - UpstreamLocalityStats: MessageTypeDefinition - } - } - } config: { core: { v3: { @@ -104,11 +61,6 @@ export interface ProtoGrpcType { } service: { load_stats: { - v2: { - LoadReportingService: SubtypeConstructor & { service: _envoy_service_load_stats_v2_LoadReportingServiceDefinition } - LoadStatsRequest: MessageTypeDefinition - LoadStatsResponse: MessageTypeDefinition - } v3: { LoadReportingService: SubtypeConstructor & { service: _envoy_service_load_stats_v3_LoadReportingServiceDefinition } LoadStatsRequest: MessageTypeDefinition @@ -117,9 +69,6 @@ export interface ProtoGrpcType { } } type: { - FractionalPercent: MessageTypeDefinition - Percent: MessageTypeDefinition - SemanticVersion: MessageTypeDefinition v3: { FractionalPercent: MessageTypeDefinition Percent: MessageTypeDefinition From 4ac8d6dab36b494cd49bca0a83068b067dd562e0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Oct 2022 13:48:39 -0700 Subject: [PATCH 342/694] grpc-js-xds: Remove all code for handling xDS v2 --- packages/grpc-js-xds/src/csds.ts | 14 +- packages/grpc-js-xds/src/load-balancer-eds.ts | 2 +- packages/grpc-js-xds/src/load-balancer-lrs.ts | 2 +- packages/grpc-js-xds/src/resolver-xds.ts | 28 +- packages/grpc-js-xds/src/resources.ts | 27 +- packages/grpc-js-xds/src/xds-client.ts | 432 +++++------------- .../src/xds-stream-state/lds-state.ts | 13 +- .../src/xds-stream-state/rds-state.ts | 8 +- .../src/xds-stream-state/xds-stream-state.ts | 15 +- 9 files changed, 173 insertions(+), 368 deletions(-) diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts index 6454e1ad1..114c18042 100644 --- a/packages/grpc-js-xds/src/csds.ts +++ b/packages/grpc-js-xds/src/csds.ts @@ -21,7 +21,7 @@ import { ClientStatusDiscoveryServiceHandlers } from "./generated/envoy/service/ import { ClientStatusRequest__Output } from "./generated/envoy/service/status/v3/ClientStatusRequest"; import { ClientStatusResponse } from "./generated/envoy/service/status/v3/ClientStatusResponse"; import { Timestamp } from "./generated/google/protobuf/Timestamp"; -import { AdsTypeUrl, CDS_TYPE_URL_V2, CDS_TYPE_URL_V3, EDS_TYPE_URL_V2, EDS_TYPE_URL_V3, LDS_TYPE_URL_V2, LDS_TYPE_URL_V3, RDS_TYPE_URL_V2, RDS_TYPE_URL_V3 } from "./resources"; +import { AdsTypeUrl, CDS_TYPE_URL, EDS_TYPE_URL, LDS_TYPE_URL, RDS_TYPE_URL } from "./resources"; import { HandleResponseResult } from "./xds-stream-state/xds-stream-state"; import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, status, experimental, loadPackageDefinition, logVerbosity } from '@grpc/grpc-js'; import { loadSync } from "@grpc/proto-loader"; @@ -50,14 +50,10 @@ function dateToProtoTimestamp(date?: Date | null): Timestamp | null { let clientNode: Node | null = null; const configStatus = { - [EDS_TYPE_URL_V2]: new Map(), - [EDS_TYPE_URL_V3]: new Map(), - [CDS_TYPE_URL_V2]: new Map(), - [CDS_TYPE_URL_V3]: new Map(), - [RDS_TYPE_URL_V2]: new Map(), - [RDS_TYPE_URL_V3]: new Map(), - [LDS_TYPE_URL_V2]: new Map(), - [LDS_TYPE_URL_V3]: new Map() + [EDS_TYPE_URL]: new Map(), + [CDS_TYPE_URL]: new Map(), + [RDS_TYPE_URL]: new Map(), + [LDS_TYPE_URL]: new Map() }; /** diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index e7aac0571..6bec1fd19 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -18,7 +18,7 @@ import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, StatusObject } from '@grpc/grpc-js'; import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from './xds-client'; import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; -import { Locality__Output } from './generated/envoy/api/v2/core/Locality'; +import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from './load-balancer-priority'; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 145501fe9..27c62c19b 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -16,7 +16,7 @@ */ import { connectivityState as ConnectivityState, StatusObject, status as Status, experimental } from '@grpc/grpc-js'; -import { Locality__Output } from './generated/envoy/api/v2/core/Locality'; +import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; import { XdsClusterLocalityStats, XdsClient, getSingletonXdsClient } from './xds-client'; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 8c447931d..496c37094 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -40,7 +40,7 @@ import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluste import { ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; import { envoyFractionToFraction, Fraction } from "./fraction"; import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; -import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from './resources'; +import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resources'; import Duration = experimental.Duration; import { Duration__Output } from './generated/google/protobuf/Duration'; import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter'; @@ -212,7 +212,6 @@ class XdsResolver implements Resolver { private latestRouteConfigName: string | null = null; private latestRouteConfig: RouteConfiguration__Output | null = null; - private latestRouteConfigIsV2 = false; private clusterRefcounts = new Map(); @@ -226,15 +225,15 @@ class XdsResolver implements Resolver { private channelOptions: ChannelOptions ) { this.ldsWatcher = { - onValidUpdate: (update: Listener__Output, isV2: boolean) => { - const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, update.api_listener!.api_listener!.value); + onValidUpdate: (update: Listener__Output) => { + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, update.api_listener!.api_listener!.value); const defaultTimeout = httpConnectionManager.common_http_protocol_options?.idle_timeout; if (defaultTimeout === null || defaultTimeout === undefined) { this.latestDefaultTimeout = undefined; } else { this.latestDefaultTimeout = protoDurationToDuration(defaultTimeout); } - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { this.ldsHttpFilterConfigs = []; for (const filter of httpConnectionManager.http_filters) { // typed_config must be set here, or validation would have failed @@ -260,7 +259,7 @@ class XdsResolver implements Resolver { if (this.latestRouteConfigName) { getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } - this.handleRouteConfig(httpConnectionManager.route_config!, isV2); + this.handleRouteConfig(httpConnectionManager.route_config!); break; default: // This is prevented by the validation rules @@ -280,8 +279,8 @@ class XdsResolver implements Resolver { } }; this.rdsWatcher = { - onValidUpdate: (update: RouteConfiguration__Output, isV2: boolean) => { - this.handleRouteConfig(update, isV2); + onValidUpdate: (update: RouteConfiguration__Output) => { + this.handleRouteConfig(update); }, onTransientError: (error: StatusObject) => { /* A transient error only needs to bubble up as a failure if we have @@ -311,14 +310,13 @@ class XdsResolver implements Resolver { refCount.refCount -= 1; if (!refCount.inLastConfig && refCount.refCount === 0) { this.clusterRefcounts.delete(clusterName); - this.handleRouteConfig(this.latestRouteConfig!, this.latestRouteConfigIsV2); + this.handleRouteConfig(this.latestRouteConfig!); } } } - private handleRouteConfig(routeConfig: RouteConfiguration__Output, isV2: boolean) { + private handleRouteConfig(routeConfig: RouteConfiguration__Output) { this.latestRouteConfig = routeConfig; - this.latestRouteConfigIsV2 = isV2; /* Select the virtual host using the default authority override if it * exists, and the channel target otherwise. */ const hostDomain = this.channelOptions['grpc.default_authority'] ?? this.target.path; @@ -328,7 +326,7 @@ class XdsResolver implements Resolver { return; } const virtualHostHttpFilterOverrides = new Map(); - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filter] of Object.entries(virtualHost.typed_per_filter_config ?? {})) { const parsedConfig = parseOverrideFilterConfig(filter); if (parsedConfig) { @@ -357,7 +355,7 @@ class XdsResolver implements Resolver { timeout = undefined; } const routeHttpFilterOverrides = new Map(); - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filter] of Object.entries(route.typed_per_filter_config ?? {})) { const parsedConfig = parseOverrideFilterConfig(filter); if (parsedConfig) { @@ -372,7 +370,7 @@ class XdsResolver implements Resolver { const cluster = route.route!.cluster!; allConfigClusters.add(cluster); const extraFilterFactories: FilterFactory[] = []; - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { for (const filterConfig of this.ldsHttpFilterConfigs) { if (routeHttpFilterOverrides.has(filterConfig.name)) { const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!); @@ -401,7 +399,7 @@ class XdsResolver implements Resolver { allConfigClusters.add(clusterWeight.name); const extraFilterFactories: FilterFactory[] = []; const clusterHttpFilterOverrides = new Map(); - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filter] of Object.entries(clusterWeight.typed_per_filter_config ?? {})) { const parsedConfig = parseOverrideFilterConfig(filter); if (parsedConfig) { diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts index 4a7e22763..0972ce97d 100644 --- a/packages/grpc-js-xds/src/resources.ts +++ b/packages/grpc-js-xds/src/resources.ts @@ -23,29 +23,22 @@ import { Listener__Output } from './generated/envoy/config/listener/v3/Listener' import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; -export const EDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.ClusterLoadAssignment'; -export const CDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.Cluster'; -export const LDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.Listener'; -export const RDS_TYPE_URL_V2 = 'type.googleapis.com/envoy.api.v2.RouteConfiguration'; +export const EDS_TYPE_URL = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; +export const CDS_TYPE_URL = 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; +export const LDS_TYPE_URL = 'type.googleapis.com/envoy.config.listener.v3.Listener'; +export const RDS_TYPE_URL = 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; -export const EDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; -export const CDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; -export const LDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.listener.v3.Listener'; -export const RDS_TYPE_URL_V3 = 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; - -export type EdsTypeUrl = 'type.googleapis.com/envoy.api.v2.ClusterLoadAssignment' | 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; -export type CdsTypeUrl = 'type.googleapis.com/envoy.api.v2.Cluster' | 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; -export type LdsTypeUrl = 'type.googleapis.com/envoy.api.v2.Listener' | 'type.googleapis.com/envoy.config.listener.v3.Listener'; -export type RdsTypeUrl = 'type.googleapis.com/envoy.api.v2.RouteConfiguration' | 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; +export type EdsTypeUrl = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; +export type CdsTypeUrl = 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; +export type LdsTypeUrl = 'type.googleapis.com/envoy.config.listener.v3.Listener'; +export type RdsTypeUrl = 'type.googleapis.com/envoy.config.route.v3.RouteConfiguration'; export type AdsTypeUrl = EdsTypeUrl | CdsTypeUrl | RdsTypeUrl | LdsTypeUrl; -export const HTTP_CONNECTION_MANGER_TYPE_URL_V2 = - 'type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager'; -export const HTTP_CONNECTION_MANGER_TYPE_URL_V3 = +export const HTTP_CONNECTION_MANGER_TYPE_URL = 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; -export type HttpConnectionManagerTypeUrl = 'type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager' | 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; +export type HttpConnectionManagerTypeUrl = 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; /** * Map type URLs to their corresponding message types diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 0c8126cbe..439ed80ea 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -22,17 +22,12 @@ import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, ex import * as adsTypes from './generated/ads'; import * as lrsTypes from './generated/lrs'; import { loadBootstrapInfo } from './xds-bootstrap'; -import { Node as NodeV2 } from './generated/envoy/api/v2/core/Node'; -import { Node as NodeV3 } from './generated/envoy/config/core/v3/Node'; -import { AggregatedDiscoveryServiceClient as AggregatedDiscoveryServiceClientV2 } from './generated/envoy/service/discovery/v2/AggregatedDiscoveryService'; -import { AggregatedDiscoveryServiceClient as AggregatedDiscoveryServiceClientV3 } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; -import { DiscoveryRequest as DiscoveryRequestV2 } from './generated/envoy/api/v2/DiscoveryRequest'; -import { DiscoveryRequest as DiscoveryRequestV3 } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; +import { Node } from './generated/envoy/config/core/v3/Node'; +import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; +import { DiscoveryRequest } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; import { DiscoveryResponse__Output } from './generated/envoy/service/discovery/v3/DiscoveryResponse'; -import { LoadReportingServiceClient as LoadReportingServiceClientV2 } from './generated/envoy/service/load_stats/v2/LoadReportingService'; -import { LoadReportingServiceClient as LoadReportingServiceClientV3 } from './generated/envoy/service/load_stats/v3/LoadReportingService'; -import { LoadStatsRequest as LoadStatsRequestV2 } from './generated/envoy/service/load_stats/v2/LoadStatsRequest'; -import { LoadStatsRequest as LoadStatsRequestV3 } from './generated/envoy/service/load_stats/v3/LoadStatsRequest'; +import { LoadReportingServiceClient } from './generated/envoy/service/load_stats/v3/LoadReportingService'; +import { LoadStatsRequest } from './generated/envoy/service/load_stats/v3/LoadStatsRequest'; import { LoadStatsResponse__Output } from './generated/envoy/service/load_stats/v3/LoadStatsResponse'; import { Locality, Locality__Output } from './generated/envoy/config/core/v3/Locality'; import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; @@ -50,7 +45,7 @@ import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; import { Duration } from './generated/google/protobuf/Duration'; -import { AdsOutputType, AdsTypeUrl, CDS_TYPE_URL_V2, CDS_TYPE_URL_V3, decodeSingleResource, EDS_TYPE_URL_V2, EDS_TYPE_URL_V3, LDS_TYPE_URL_V2, LDS_TYPE_URL_V3, RDS_TYPE_URL_V2, RDS_TYPE_URL_V3 } from './resources'; +import { AdsOutputType, AdsTypeUrl, CDS_TYPE_URL, decodeSingleResource, EDS_TYPE_URL, LDS_TYPE_URL, RDS_TYPE_URL } from './resources'; import { setCsdsClientNode, updateCsdsRequestedNameList, updateCsdsResourceResponse } from './csds'; const TRACER_NAME = 'xds_client'; @@ -74,8 +69,6 @@ function loadAdsProtos(): Promise< loadedProtos = protoLoader .load( [ - 'envoy/service/discovery/v2/ads.proto', - 'envoy/service/load_stats/v2/lrs.proto', 'envoy/service/discovery/v3/ads.proto', 'envoy/service/load_stats/v3/lrs.proto', ], @@ -85,6 +78,7 @@ function loadAdsProtos(): Promise< enums: String, defaults: true, oneofs: true, + json: true, includeDirs: [ // Paths are relative to src/build __dirname + '/../../deps/envoy-api/', @@ -234,62 +228,38 @@ interface AdsState { lds: LdsState; } -enum XdsApiVersion { - V2, - V3 -} - function getResponseMessages( targetTypeUrl: T, - allowedTypeUrls: string[], resources: Any__Output[] ): ResourcePair>[] { const result: ResourcePair>[] = []; for (const resource of resources) { - if (allowedTypeUrls.includes(resource.type_url)) { - result.push({ - resource: decodeSingleResource(targetTypeUrl, resource.value), - raw: resource - }); - } else { + if (resource.type_url !== targetTypeUrl) { throw new Error( - `ADS Error: Invalid resource type ${resource.type_url}, expected ${allowedTypeUrls}` + `ADS Error: Invalid resource type ${resource.type_url}, expected ${targetTypeUrl}` ); } + result.push({ + resource: decodeSingleResource(targetTypeUrl, resource.value), + raw: resource + }); } return result; } export class XdsClient { - private apiVersion: XdsApiVersion = XdsApiVersion.V2; - - private adsNodeV2: NodeV2 | null = null; - private adsNodeV3: NodeV3 | null = null; - /* A client initiates connections lazily, so the client we don't use won't - * use significant extra resources. */ - private adsClientV2: AggregatedDiscoveryServiceClientV2 | null = null; - private adsClientV3: AggregatedDiscoveryServiceClientV3 | null = null; - /* TypeScript typing is structural, so we can take advantage of the fact that - * the output structures for the two call types are identical. */ - private adsCallV2: ClientDuplexStream< - DiscoveryRequestV2, - DiscoveryResponse__Output - > | null = null; - private adsCallV3: ClientDuplexStream< - DiscoveryRequestV3, + + private adsNode: Node | null = null; + private adsClient: AggregatedDiscoveryServiceClient | null = null; + private adsCall: ClientDuplexStream< + DiscoveryRequest, DiscoveryResponse__Output > | null = null; - private lrsNodeV2: NodeV2 | null = null; - private lrsNodeV3: NodeV3 | null = null; - private lrsClientV2: LoadReportingServiceClientV2 | null = null; - private lrsClientV3: LoadReportingServiceClientV3 | null = null; - private lrsCallV2: ClientDuplexStream< - LoadStatsRequestV2, - LoadStatsResponse__Output - > | null = null; - private lrsCallV3: ClientDuplexStream< - LoadStatsRequestV3, + private lrsNode: Node | null = null; + private lrsClient: LoadReportingServiceClient | null = null; + private lrsCall: ClientDuplexStream< + LoadStatsRequest, LoadStatsResponse__Output > | null = null; private latestLrsSettings: LoadStatsResponse__Output | null = null; @@ -355,48 +325,24 @@ export class XdsClient { }); return; } - if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('xds_v3') >= 0) { - this.apiVersion = XdsApiVersion.V3; - } else { - this.apiVersion = XdsApiVersion.V2; - } if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('ignore_resource_deletion') >= 0) { this.adsState.lds.enableIgnoreResourceDeletion(); this.adsState.cds.enableIgnoreResourceDeletion(); } - const nodeV2: NodeV2 = { - ...bootstrapInfo.node, - build_version: `gRPC Node Pure JS ${clientVersion}`, - user_agent_name: 'gRPC Node Pure JS', - }; - const nodeV3: NodeV3 = { + const userAgentName = 'gRPC Node Pure JS'; + this.adsNode = { ...bootstrapInfo.node, - user_agent_name: 'gRPC Node Pure JS', - }; - this.adsNodeV2 = { - ...nodeV2, - client_features: ['envoy.lb.does_not_support_overprovisioning'], - }; - this.adsNodeV3 = { - ...nodeV3, + user_agent_name: userAgentName, client_features: ['envoy.lb.does_not_support_overprovisioning'], }; - this.lrsNodeV2 = { - ...nodeV2, - client_features: ['envoy.lrs.supports_send_all_clusters'], - }; - this.lrsNodeV3 = { - ...nodeV3, + this.lrsNode = { + ...bootstrapInfo.node, + user_agent_name: userAgentName, client_features: ['envoy.lrs.supports_send_all_clusters'], }; - setCsdsClientNode(this.adsNodeV3); - if (this.apiVersion === XdsApiVersion.V2) { - trace('ADS Node: ' + JSON.stringify(this.adsNodeV2, undefined, 2)); - trace('LRS Node: ' + JSON.stringify(this.lrsNodeV2, undefined, 2)); - } else { - trace('ADS Node: ' + JSON.stringify(this.adsNodeV3, undefined, 2)); - trace('LRS Node: ' + JSON.stringify(this.lrsNodeV3, undefined, 2)); - } + setCsdsClientNode(this.adsNode); + trace('ADS Node: ' + JSON.stringify(this.adsNode, undefined, 2)); + trace('LRS Node: ' + JSON.stringify(this.lrsNode, undefined, 2)); const credentialsConfigs = bootstrapInfo.xdsServers[0].channelCreds; let channelCreds: ChannelCredentials | null = null; for (const config of credentialsConfigs) { @@ -421,24 +367,14 @@ export class XdsClient { const serverUri = bootstrapInfo.xdsServers[0].serverUri trace('Starting xDS client connected to server URI ' + bootstrapInfo.xdsServers[0].serverUri); const channel = new Channel(serverUri, channelCreds, channelArgs); - this.adsClientV2 = new protoDefinitions.envoy.service.discovery.v2.AggregatedDiscoveryService( - serverUri, - channelCreds, - {channelOverride: channel} - ); - this.adsClientV3 = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( + this.adsClient = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( serverUri, channelCreds, {channelOverride: channel} ); this.maybeStartAdsStream(); - this.lrsClientV2 = new protoDefinitions.envoy.service.load_stats.v2.LoadReportingService( - serverUri, - channelCreds, - {channelOverride: channel} - ); - this.lrsClientV3 = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( + this.lrsClient = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( serverUri, channelCreds, {channelOverride: channel} @@ -463,55 +399,36 @@ export class XdsClient { result: HandleResponseResult; serviceKind: AdsServiceKind; } | null = null; - let isV2: boolean; - switch (message.type_url) { - case EDS_TYPE_URL_V2: - case CDS_TYPE_URL_V2: - case RDS_TYPE_URL_V2: - case LDS_TYPE_URL_V2: - isV2 = true; - break; - default: - isV2 = false; - } try { switch (message.type_url) { - case EDS_TYPE_URL_V2: - case EDS_TYPE_URL_V3: + case EDS_TYPE_URL: handleResponseResult = { result: this.adsState.eds.handleResponses( - getResponseMessages(EDS_TYPE_URL_V3, [EDS_TYPE_URL_V2, EDS_TYPE_URL_V3], message.resources), - isV2 + getResponseMessages(EDS_TYPE_URL, message.resources) ), serviceKind: 'eds' }; break; - case CDS_TYPE_URL_V2: - case CDS_TYPE_URL_V3: + case CDS_TYPE_URL: handleResponseResult = { result: this.adsState.cds.handleResponses( - getResponseMessages(CDS_TYPE_URL_V3, [CDS_TYPE_URL_V2, CDS_TYPE_URL_V3], message.resources), - isV2 + getResponseMessages(CDS_TYPE_URL, message.resources) ), serviceKind: 'cds' }; break; - case RDS_TYPE_URL_V2: - case RDS_TYPE_URL_V3: + case RDS_TYPE_URL: handleResponseResult = { result: this.adsState.rds.handleResponses( - getResponseMessages(RDS_TYPE_URL_V3, [RDS_TYPE_URL_V2, RDS_TYPE_URL_V3], message.resources), - isV2 + getResponseMessages(RDS_TYPE_URL, message.resources) ), serviceKind: 'rds' }; break; - case LDS_TYPE_URL_V2: - case LDS_TYPE_URL_V3: + case LDS_TYPE_URL: handleResponseResult = { result: this.adsState.lds.handleResponses( - getResponseMessages(LDS_TYPE_URL_V3, [LDS_TYPE_URL_V2, LDS_TYPE_URL_V3], message.resources), - isV2 + getResponseMessages(LDS_TYPE_URL, message.resources) ), serviceKind: 'lds' } @@ -548,8 +465,7 @@ export class XdsClient { trace( 'ADS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details ); - this.adsCallV2 = null; - this.adsCallV3 = null; + this.adsCall = null; if (streamStatus.code !== status.OK) { this.reportStreamError(streamStatus); } @@ -560,48 +476,6 @@ export class XdsClient { } } - private maybeStartAdsStreamV2(): boolean { - if (this.apiVersion !== XdsApiVersion.V2) { - return false; - } - if (this.adsClientV2 === null) { - return false; - } - if (this.adsCallV2 !== null) { - return false; - } - this.adsCallV2 = this.adsClientV2.StreamAggregatedResources(); - this.adsCallV2.on('data', (message: DiscoveryResponse__Output) => { - this.handleAdsResponse(message); - }); - this.adsCallV2.on('status', (status: StatusObject) => { - this.handleAdsCallStatus(status); - }); - this.adsCallV2.on('error', () => {}); - return true; - } - - private maybeStartAdsStreamV3(): boolean { - if (this.apiVersion !== XdsApiVersion.V3) { - return false; - } - if (this.adsClientV3 === null) { - return false; - } - if (this.adsCallV3 !== null) { - return false; - } - this.adsCallV3 = this.adsClientV3.StreamAggregatedResources(); - this.adsCallV3.on('data', (message: DiscoveryResponse__Output) => { - this.handleAdsResponse(message); - }); - this.adsCallV3.on('status', (status: StatusObject) => { - this.handleAdsCallStatus(status); - }); - this.adsCallV3.on('error', () => {}); - return true; - } - /** * Start the ADS stream if the client exists and there is not already an * existing stream, and there are resources to request. @@ -616,73 +490,55 @@ export class XdsClient { this.adsState.lds.getResourceNames().length === 0) { return; } - let streamStarted: boolean; - if (this.apiVersion === XdsApiVersion.V2) { - streamStarted = this.maybeStartAdsStreamV2(); - } else { - streamStarted = this.maybeStartAdsStreamV3(); + if (this.adsClient === null) { + return; } - if (streamStarted) { - trace('Started ADS stream'); - // Backoff relative to when we start the request - this.adsBackoff.runOnce(); - - const allServiceKinds: AdsServiceKind[] = ['eds', 'cds', 'rds', 'lds']; - for (const service of allServiceKinds) { - const state = this.adsState[service]; - if (state.getResourceNames().length > 0) { - this.updateNames(service); - } + if (this.adsCall !== null) { + return; + } + this.adsCall = this.adsClient.StreamAggregatedResources(); + this.adsCall.on('data', (message: DiscoveryResponse__Output) => { + this.handleAdsResponse(message); + }); + this.adsCall.on('status', (status: StatusObject) => { + this.handleAdsCallStatus(status); + }); + this.adsCall.on('error', () => {}); + trace('Started ADS stream'); + // Backoff relative to when we start the request + this.adsBackoff.runOnce(); + + const allServiceKinds: AdsServiceKind[] = ['eds', 'cds', 'rds', 'lds']; + for (const service of allServiceKinds) { + const state = this.adsState[service]; + if (state.getResourceNames().length > 0) { + this.updateNames(service); } - this.reportAdsStreamStarted(); } + this.reportAdsStreamStarted(); } private maybeSendAdsMessage(typeUrl: string, resourceNames: string[], responseNonce: string, versionInfo: string, errorMessage?: string) { - if (this.apiVersion === XdsApiVersion.V2) { - this.adsCallV2?.write({ - node: this.adsNodeV2!, - type_url: typeUrl, - resource_names: resourceNames, - response_nonce: responseNonce, - version_info: versionInfo, - error_detail: errorMessage ? { message: errorMessage } : undefined - }); - } else { - this.adsCallV3?.write({ - node: this.adsNodeV3!, - type_url: typeUrl, - resource_names: resourceNames, - response_nonce: responseNonce, - version_info: versionInfo, - error_detail: errorMessage ? { message: errorMessage } : undefined - }); - } + this.adsCall?.write({ + node: this.adsNode!, + type_url: typeUrl, + resource_names: resourceNames, + response_nonce: responseNonce, + version_info: versionInfo, + error_detail: errorMessage ? { message: errorMessage } : undefined + }); } private getTypeUrl(serviceKind: AdsServiceKind): AdsTypeUrl { - if (this.apiVersion === XdsApiVersion.V2) { - switch (serviceKind) { - case 'eds': - return EDS_TYPE_URL_V2; - case 'cds': - return CDS_TYPE_URL_V2; - case 'rds': - return RDS_TYPE_URL_V2; - case 'lds': - return LDS_TYPE_URL_V2; - } - } else { - switch (serviceKind) { - case 'eds': - return EDS_TYPE_URL_V3; - case 'cds': - return CDS_TYPE_URL_V3; - case 'rds': - return RDS_TYPE_URL_V3; - case 'lds': - return LDS_TYPE_URL_V3; - } + switch (serviceKind) { + case 'eds': + return EDS_TYPE_URL; + case 'cds': + return CDS_TYPE_URL; + case 'rds': + return RDS_TYPE_URL; + case 'lds': + return LDS_TYPE_URL; } } @@ -708,20 +564,16 @@ export class XdsClient { let versionInfo: string; let serviceKind: AdsServiceKind | null; switch (typeUrl) { - case EDS_TYPE_URL_V2: - case EDS_TYPE_URL_V3: + case EDS_TYPE_URL: serviceKind = 'eds'; break; - case CDS_TYPE_URL_V2: - case CDS_TYPE_URL_V3: + case CDS_TYPE_URL: serviceKind = 'cds'; break; - case RDS_TYPE_URL_V2: - case RDS_TYPE_URL_V3: + case RDS_TYPE_URL: serviceKind = 'rds'; break; - case LDS_TYPE_URL_V2: - case LDS_TYPE_URL_V3: + case LDS_TYPE_URL: serviceKind = 'lds'; break; default: @@ -731,7 +583,7 @@ export class XdsClient { if (serviceKind) { this.adsState[serviceKind].reportStreamError({ code: status.UNAVAILABLE, - details: message + ' Node ID=' + this.adsNodeV3!.id, + details: message + ' Node ID=' + this.adsNode!.id, metadata: new Metadata() }); resourceNames = this.adsState[serviceKind].getResourceNames(); @@ -750,19 +602,15 @@ export class XdsClient { this.adsState.cds.getResourceNames().length === 0 && this.adsState.rds.getResourceNames().length === 0 && this.adsState.lds.getResourceNames().length === 0) { - this.adsCallV2?.end(); - this.adsCallV2 = null; - this.adsCallV3?.end(); - this.adsCallV3 = null; - this.lrsCallV2?.end(); - this.lrsCallV2 = null; - this.lrsCallV3?.end(); - this.lrsCallV3 = null; + this.adsCall?.end(); + this.adsCall = null; + this.lrsCall?.end(); + this.lrsCall = null; return; } this.maybeStartAdsStream(); this.maybeStartLrsStream(); - if (!this.adsCallV2 && !this.adsCallV3) { + if (!this.adsCall) { /* If the stream is not set up yet at this point, shortcut the rest * becuase nothing will actually be sent. This would mainly happen if * the bootstrap file has not been read yet. In that case, the output @@ -776,7 +624,7 @@ export class XdsClient { } private reportStreamError(status: StatusObject) { - status = {...status, details: status.details + ' Node ID=' + this.adsNodeV3!.id}; + status = {...status, details: status.details + ' Node ID=' + this.adsNode!.id}; this.adsState.eds.reportStreamError(status); this.adsState.cds.reportStreamError(status); this.adsState.rds.reportStreamError(status); @@ -823,8 +671,7 @@ export class XdsClient { trace( 'LRS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details ); - this.lrsCallV2 = null; - this.lrsCallV3 = null; + this.lrsCall = null; clearInterval(this.statsTimer); /* If the backoff timer is no longer running, we do not need to wait any * more to start the new call. */ @@ -833,41 +680,22 @@ export class XdsClient { } } - private maybeStartLrsStreamV2(): boolean { - if (!this.lrsClientV2) { - return false; - } - if (this.lrsCallV2) { - return false; - } - this.lrsCallV2 = this.lrsClientV2.streamLoadStats(); - this.receivedLrsSettingsForCurrentStream = false; - this.lrsCallV2.on('data', (message: LoadStatsResponse__Output) => { - this.handleLrsResponse(message); - }); - this.lrsCallV2.on('status', (status: StatusObject) => { - this.handleLrsCallStatus(status); - }); - this.lrsCallV2.on('error', () => {}); - return true; - } - private maybeStartLrsStreamV3(): boolean { - if (!this.lrsClientV3) { + if (!this.lrsClient) { return false; } - if (this.lrsCallV3) { + if (this.lrsCall) { return false; } - this.lrsCallV3 = this.lrsClientV3.streamLoadStats(); + this.lrsCall = this.lrsClient.streamLoadStats(); this.receivedLrsSettingsForCurrentStream = false; - this.lrsCallV3.on('data', (message: LoadStatsResponse__Output) => { + this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { this.handleLrsResponse(message); }); - this.lrsCallV3.on('status', (status: StatusObject) => { + this.lrsCall.on('status', (status: StatusObject) => { this.handleLrsCallStatus(status); }); - this.lrsCallV3.on('error', () => {}); + this.lrsCall.on('error', () => {}); return true; } @@ -881,39 +709,37 @@ export class XdsClient { this.adsState.lds.getResourceNames().length === 0) { return; } - - let streamStarted: boolean; - if (this.apiVersion === XdsApiVersion.V2) { - streamStarted = this.maybeStartLrsStreamV2(); - } else { - streamStarted = this.maybeStartLrsStreamV3(); + if (!this.lrsClient) { + return; } - - if (streamStarted) { - trace('Starting LRS stream'); - this.lrsBackoff.runOnce(); - /* Send buffered stats information when starting LRS stream. If there is no - * buffered stats information, it will still send the node field. */ - this.sendStats(); + if (this.lrsCall) { + return; } + this.lrsCall = this.lrsClient.streamLoadStats(); + this.receivedLrsSettingsForCurrentStream = false; + this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { + this.handleLrsResponse(message); + }); + this.lrsCall.on('status', (status: StatusObject) => { + this.handleLrsCallStatus(status); + }); + this.lrsCall.on('error', () => {}); + trace('Starting LRS stream'); + this.lrsBackoff.runOnce(); + /* Send buffered stats information when starting LRS stream. If there is no + * buffered stats information, it will still send the node field. */ + this.sendStats(); } private maybeSendLrsMessage(clusterStats: ClusterStats[]) { - if (this.apiVersion === XdsApiVersion.V2) { - this.lrsCallV2?.write({ - node: this.lrsNodeV2!, - cluster_stats: clusterStats - }); - } else { - this.lrsCallV3?.write({ - node: this.lrsNodeV3!, - cluster_stats: clusterStats - }); - } + this.lrsCall?.write({ + node: this.lrsNode!, + cluster_stats: clusterStats + }); } private sendStats() { - if (this.lrsCallV2 === null && this.lrsCallV3 === null) { + if (this.lrsCall === null) { return; } if (!this.latestLrsSettings) { @@ -1121,14 +947,10 @@ export class XdsClient { } private shutdown(): void { - this.adsCallV2?.cancel(); - this.adsCallV3?.cancel(); - this.adsClientV2?.close(); - this.adsClientV3?.close(); - this.lrsCallV2?.cancel(); - this.lrsCallV3?.cancel(); - this.lrsClientV2?.close(); - this.lrsClientV3?.close(); + this.adsCall?.cancel(); + this.adsClient?.close(); + this.lrsCall?.cancel(); + this.lrsClient?.close(); this.hasShutdown = true; } } diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index bd5b6423f..c215076db 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -19,7 +19,7 @@ import { experimental, logVerbosity } from "@grpc/grpc-js"; import { Listener__Output } from '../generated/envoy/config/listener/v3/Listener'; import { RdsState } from "./rds-state"; import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; -import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V2, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from '../resources'; +import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from '../resources'; import { getTopLevelFilterUrl, validateTopLevelFilter } from '../http-filter'; import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; @@ -46,18 +46,17 @@ export class LdsState extends BaseXdsStreamState implements Xd super(updateResourceNames); } - public validateResponse(message: Listener__Output, isV2: boolean): boolean { + public validateResponse(message: Listener__Output): boolean { if ( !( message.api_listener?.api_listener && - (message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL_V2 || - message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL_V3) + message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL ) ) { return false; } - const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener.value); - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, message.api_listener!.api_listener.value); + if (EXPERIMENTAL_FAULT_INJECTION) { const filterNames = new Set(); for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { if (filterNames.has(httpFilter.name)) { @@ -89,7 +88,7 @@ export class LdsState extends BaseXdsStreamState implements Xd case 'rds': return !!httpConnectionManager.rds?.config_source?.ads; case 'route_config': - return this.rdsState.validateResponse(httpConnectionManager.route_config!, isV2); + return this.rdsState.validateResponse(httpConnectionManager.route_config!); } return false; } diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index a5d3c47cf..119ac6b92 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -40,7 +40,7 @@ export class RdsState extends BaseXdsStreamState imp protected getProtocolName(): string { return 'RDS'; } - validateResponse(message: RouteConfiguration__Output, isV2: boolean): boolean { + validateResponse(message: RouteConfiguration__Output): boolean { // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation for (const virtualHost of message.virtual_hosts) { for (const domainPattern of virtualHost.domains) { @@ -55,7 +55,7 @@ export class RdsState extends BaseXdsStreamState imp return false; } } - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { return false; @@ -81,7 +81,7 @@ export class RdsState extends BaseXdsStreamState imp if ((route.route === undefined) || (route.route === null) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { return false; } - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { return false; @@ -99,7 +99,7 @@ export class RdsState extends BaseXdsStreamState imp if (weightSum !== route.route.weighted_clusters!.total_weight?.value ?? 100) { return false; } - if (!isV2 && EXPERIMENTAL_FAULT_INJECTION) { + if (EXPERIMENTAL_FAULT_INJECTION) { for (const weightedCluster of route.route!.weighted_clusters!.clusters) { for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 7b3bc0189..86c2cea4b 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -26,7 +26,7 @@ export interface Watcher { * message type into a library-specific configuration object type, to * remove a lot of duplicate logic, including logic for handling that * flag. */ - onValidUpdate(update: UpdateType, isV2: boolean): void; + onValidUpdate(update: UpdateType): void; onTransientError(error: StatusObject): void; onResourceDoesNotExist(): void; } @@ -85,7 +85,6 @@ export abstract class BaseXdsStreamState implements XdsStreamState nonce = ''; private subscriptions: Map> = new Map>(); - private latestIsV2 = false; private isAdsStreamRunning = false; private ignoreResourceDeletion = false; @@ -128,7 +127,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState * the same happens here */ process.nextTick(() => { this.trace('Reporting existing update for new watcher for name ' + name); - watcher.onValidUpdate(cachedResponse, this.latestIsV2); + watcher.onValidUpdate(cachedResponse); }); } if (addedName) { @@ -157,7 +156,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState getResourceNames(): string[] { return Array.from(this.subscriptions.keys()); } - handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult { + handleResponses(responses: ResourcePair[]): HandleResponseResult { const validResponses: ResponseType[] = []; let result: HandleResponseResult = { accepted: [], @@ -166,7 +165,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState } for (const {resource, raw} of responses) { const resourceName = this.getResourceName(resource); - if (this.validateResponse(resource, isV2)) { + if (this.validateResponse(resource)) { validResponses.push(resource); result.accepted.push({ name: resourceName, @@ -180,7 +179,6 @@ export abstract class BaseXdsStreamState implements XdsStreamState }); } } - this.latestIsV2 = isV2; const allResourceNames = new Set(); for (const resource of validResponses) { const resourceName = this.getResourceName(resource); @@ -189,7 +187,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState if (subscriptionEntry) { const watchers = subscriptionEntry.watchers; for (const watcher of watchers) { - watcher.onValidUpdate(resource, isV2); + watcher.onValidUpdate(resource); } clearTimeout(subscriptionEntry.resourceTimer); subscriptionEntry.cachedResponse = resource; @@ -259,9 +257,8 @@ export abstract class BaseXdsStreamState implements XdsStreamState * This function is public so that the LDS validateResponse can call into * the RDS validateResponse. * @param resource The resource object sent by the xDS server - * @param isV2 If true, the resource is an xDS V2 resource instead of xDS V3 */ - public abstract validateResponse(resource: ResponseType, isV2: boolean): boolean; + public abstract validateResponse(resource: ResponseType): boolean; /** * Get the name of a resource object. The name is some field of the object, so * getting it depends on the specific type. From 75a6d0a24b01d18448f72dc6df3a4e1de497d333 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Oct 2022 09:45:57 -0700 Subject: [PATCH 343/694] grpc-js: Handle the grpc-node.max_session_memory option consistently on the client and server --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/server.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index c0db93ae0..087060154 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.7.1", + "version": "1.7.2", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 974c3dfc6..499fde4a3 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -346,6 +346,12 @@ export class Server { serverOptions.maxSessionMemory = this.options[ 'grpc-node.max_session_memory' ]; + } else { + /* By default, set a very large max session memory limit, to effectively + * disable enforcement of the limit. Some testing indicates that Node's + * behavior degrades badly when this limit is reached, so we solve that + * by disabling the check entirely. */ + serverOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; } if ('grpc.max_concurrent_streams' in this.options) { serverOptions.settings = { From 59a2cbceeb288b0967dca7daafee254292bc0e1a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Oct 2022 10:27:38 -0700 Subject: [PATCH 344/694] grpc-js: Remove redundant calls to setCredentials --- packages/grpc-js/src/client-interceptors.ts | 8 -------- packages/grpc-js/src/client.ts | 12 ------------ 2 files changed, 20 deletions(-) diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index 52320b0b3..d9c88f448 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -198,8 +198,6 @@ export interface InterceptingCallInterface { sendMessage(message: any): void; startRead(): void; halfClose(): void; - - setCredentials(credentials: CallCredentials): void; } export class InterceptingCall implements InterceptingCallInterface { @@ -337,9 +335,6 @@ export class InterceptingCall implements InterceptingCallInterface { } }); } - setCredentials(credentials: CallCredentials): void { - this.nextCall.setCredentials(credentials); - } } function getCall(channel: Channel, path: string, options: CallOptions): Call { @@ -371,9 +366,6 @@ class BaseInterceptingCall implements InterceptingCallInterface { getPeer(): string { return this.call.getPeer(); } - setCredentials(credentials: CallCredentials): void { - this.call.setCredentials(credentials); - } // eslint-disable-next-line @typescript-eslint/no-explicit-any sendMessageWithContext(context: MessageContext, message: any): void { let serialized: Buffer; diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index d97c9fa3f..f96f8fdf0 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -321,9 +321,6 @@ export class Client { * before calling the CallInvocationTransformer, and we need to create the * call after that. */ emitter.call = call; - if (callProperties.callOptions.credentials) { - call.setCredentials(callProperties.callOptions.credentials); - } let responseMessage: ResponseType | null = null; let receivedStatus = false; const callerStackError = new Error(); @@ -449,9 +446,6 @@ export class Client { * before calling the CallInvocationTransformer, and we need to create the * call after that. */ emitter.call = call; - if (callProperties.callOptions.credentials) { - call.setCredentials(callProperties.callOptions.credentials); - } let responseMessage: ResponseType | null = null; let receivedStatus = false; const callerStackError = new Error(); @@ -582,9 +576,6 @@ export class Client { * before calling the CallInvocationTransformer, and we need to create the * call after that. */ stream.call = call; - if (callProperties.callOptions.credentials) { - call.setCredentials(callProperties.callOptions.credentials); - } let receivedStatus = false; const callerStackError = new Error(); call.start(callProperties.metadata, { @@ -681,9 +672,6 @@ export class Client { * before calling the CallInvocationTransformer, and we need to create the * call after that. */ stream.call = call; - if (callProperties.callOptions.credentials) { - call.setCredentials(callProperties.callOptions.credentials); - } let receivedStatus = false; const callerStackError = new Error(); call.start(callProperties.metadata, { From 63d9f6a6d68ceef57e72f02e37c91d140ae32196 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Oct 2022 11:18:26 -0700 Subject: [PATCH 345/694] Ensure ordering between received messages and status --- packages/grpc-js/src/load-balancing-call.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 36af8b683..73c1fa9fd 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -44,6 +44,8 @@ export class LoadBalancingCall implements Call { private writeFilterPending = false; private pendingMessage: {context: MessageContext, message: Buffer} | null = null; private pendingHalfClose = false; + private readFilterPending = false; + private pendingChildStatus: StatusObject | null = null; private ended = false; private serviceUrl: string; private filterStack: FilterStack; @@ -153,14 +155,23 @@ export class LoadBalancingCall implements Call { this.listener!.onReceiveMetadata(this.filterStack.receiveMetadata(metadata)); }, onReceiveMessage: message => { + this.readFilterPending = true; this.filterStack.receiveMessage(message).then(filteredMesssage => { + this.readFilterPending = false; this.listener!.onReceiveMessage(filteredMesssage); + if (this.pendingChildStatus) { + this.outputStatus(this.pendingChildStatus, 'PROCESSED'); + } }, (status: StatusObject) => { this.cancelWithStatus(status.code, status.details); }); }, onReceiveStatus: status => { - this.outputStatus(status, 'PROCESSED'); + if (this.readFilterPending) { + this.pendingChildStatus = status; + } else { + this.outputStatus(status, 'PROCESSED'); + } } }); } catch (error) { From 955f088379829cc362496b10e992ab0d1f8c591c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Oct 2022 11:46:32 -0700 Subject: [PATCH 346/694] grpc-js: Test against actively maintained Node versions --- run-tests.bat | 6 +++--- run-tests.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/run-tests.bat b/run-tests.bat index 3b0aa07d0..4262f2844 100644 --- a/run-tests.bat +++ b/run-tests.bat @@ -28,8 +28,8 @@ SET JOBS=8 call nvm version -call nvm install 10 -call nvm use 10 +call nvm install 18 +call nvm use 18 git submodule update --init --recursive @@ -40,7 +40,7 @@ call npm install || goto :error SET JUNIT_REPORT_STACK=1 SET FAILED=0 -for %%v in (10 12) do ( +for %%v in (14 16 18) do ( call nvm install %%v call nvm use %%v if "%%v"=="4" ( diff --git a/run-tests.sh b/run-tests.sh index 4bcb388b4..b2e8f6a4e 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -28,7 +28,7 @@ cd $ROOT git submodule update --init --recursive if [ ! -n "$node_versions" ] ; then - node_versions="10 12 14 16" + node_versions="14 16 18" fi set +ex From 276b7b66d020314562000f9fa905e860b7e360c3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Oct 2022 13:29:57 -0700 Subject: [PATCH 347/694] grpc-js-xds: Fix limit representation for priority weight validation --- packages/grpc-js-xds/src/xds-stream-state/eds-state.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index 9b7b9cd5f..cec6a4764 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -25,6 +25,8 @@ import { BaseXdsStreamState, HandleResponseResult, RejectedResourceEntry, Resour const TRACER_NAME = 'xds_client'; +const UINT32_MAX = 0xFFFFFFFF; + function trace(text: string): void { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); } @@ -88,7 +90,7 @@ export class EdsState extends BaseXdsStreamState priorityTotalWeights.set(endpoint.priority, (priorityTotalWeights.get(endpoint.priority) ?? 0) + (endpoint.load_balancing_weight?.value ?? 0)); } for (const totalWeight of priorityTotalWeights.values()) { - if (totalWeight >= 1<<32) { + if (totalWeight > UINT32_MAX) { return false; } } From 223bf8f4b04bdb412b55bc14146f86c228a682f7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 17 Oct 2022 10:11:38 -0700 Subject: [PATCH 348/694] Don't test on Node 18 yet --- run-tests.bat | 6 +++--- run-tests.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/run-tests.bat b/run-tests.bat index 4262f2844..6b94b78de 100644 --- a/run-tests.bat +++ b/run-tests.bat @@ -28,8 +28,8 @@ SET JOBS=8 call nvm version -call nvm install 18 -call nvm use 18 +call nvm install 16 +call nvm use 16 git submodule update --init --recursive @@ -40,7 +40,7 @@ call npm install || goto :error SET JUNIT_REPORT_STACK=1 SET FAILED=0 -for %%v in (14 16 18) do ( +for %%v in (14 16) do ( call nvm install %%v call nvm use %%v if "%%v"=="4" ( diff --git a/run-tests.sh b/run-tests.sh index b2e8f6a4e..0adcc0f17 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -28,7 +28,7 @@ cd $ROOT git submodule update --init --recursive if [ ! -n "$node_versions" ] ; then - node_versions="14 16 18" + node_versions="14 16" fi set +ex From c4c321d37dfca78a5b97f433652147628fa43186 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 17 Oct 2022 11:32:22 -0700 Subject: [PATCH 349/694] grpc-js: Handle filters in ResolvingCall instead of LoadBalancingCall --- packages/grpc-js/src/internal-channel.ts | 2 +- packages/grpc-js/src/load-balancing-call.ts | 59 +++---------- packages/grpc-js/src/resolving-call.ts | 96 +++++++++++++++------ 3 files changed, 83 insertions(+), 74 deletions(-) diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 2a775e7d8..16f4b9832 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -402,7 +402,7 @@ export class InternalChannel { method + '"' ); - return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, this.filterStackFactory, callNumber); + return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, callNumber); } createInnerCall( diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 73c1fa9fd..6faa15592 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -41,14 +41,11 @@ export interface StatusObjectWithProgress extends StatusObject { export class LoadBalancingCall implements Call { private child: SubchannelCall | null = null; private readPending = false; - private writeFilterPending = false; private pendingMessage: {context: MessageContext, message: Buffer} | null = null; private pendingHalfClose = false; - private readFilterPending = false; private pendingChildStatus: StatusObject | null = null; private ended = false; private serviceUrl: string; - private filterStack: FilterStack; private metadata: Metadata | null = null; private listener: InterceptingListener | null = null; private onCallEnded: ((statusCode: Status) => void) | null = null; @@ -59,11 +56,8 @@ export class LoadBalancingCall implements Call { private readonly host : string, private readonly credentials: CallCredentials, private readonly deadline: Deadline, - filterStackFactory: FilterStackFactory, private readonly callNumber: number ) { - this.filterStack = filterStackFactory.createFilter(); - const splitPath: string[] = this.methodName.split('/'); let serviceName = ''; /* The standard path format is "/{serviceName}/{methodName}", so if we split @@ -90,8 +84,7 @@ export class LoadBalancingCall implements Call { if (!this.ended) { this.ended = true; this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); - const filteredStatus = this.filterStack.receiveTrailers(status); - const finalStatus = {...filteredStatus, progress}; + const finalStatus = {...status, progress}; this.listener?.onReceiveStatus(finalStatus); this.onCallEnded?.(finalStatus.code); } @@ -152,26 +145,13 @@ export class LoadBalancingCall implements Call { try { this.child = pickResult.subchannel!.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, { onReceiveMetadata: metadata => { - this.listener!.onReceiveMetadata(this.filterStack.receiveMetadata(metadata)); + this.listener!.onReceiveMetadata(metadata); }, onReceiveMessage: message => { - this.readFilterPending = true; - this.filterStack.receiveMessage(message).then(filteredMesssage => { - this.readFilterPending = false; - this.listener!.onReceiveMessage(filteredMesssage); - if (this.pendingChildStatus) { - this.outputStatus(this.pendingChildStatus, 'PROCESSED'); - } - }, (status: StatusObject) => { - this.cancelWithStatus(status.code, status.details); - }); + this.listener!.onReceiveMessage(message); }, onReceiveStatus: status => { - if (this.readFilterPending) { - this.pendingChildStatus = status; - } else { - this.outputStatus(status, 'PROCESSED'); - } + this.outputStatus(status, 'PROCESSED'); } }); } catch (error) { @@ -201,7 +181,7 @@ export class LoadBalancingCall implements Call { if (this.pendingMessage) { this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); } - if (this.pendingHalfClose && !this.writeFilterPending) { + if (this.pendingHalfClose) { this.child.halfClose(); } }, (error: Error & { code: number }) => { @@ -249,29 +229,16 @@ export class LoadBalancingCall implements Call { start(metadata: Metadata, listener: InterceptingListener): void { this.trace('start called'); this.listener = listener; - this.filterStack.sendMetadata(Promise.resolve(metadata)).then(filteredMetadata => { - this.metadata = filteredMetadata; - this.doPick(); - }, (status: StatusObject) => { - this.outputStatus(status, 'PROCESSED'); - }); + this.metadata = metadata; + this.doPick(); } sendMessageWithContext(context: MessageContext, message: Buffer): void { this.trace('write() called with message of length ' + message.length); - this.writeFilterPending = true; - this.filterStack.sendMessage(Promise.resolve({message: message, flags: context.flags})).then((filteredMessage) => { - this.writeFilterPending = false; - if (this.child) { - this.child.sendMessageWithContext(context, filteredMessage.message); - if (this.pendingHalfClose) { - this.child.halfClose(); - } - } else { - this.pendingMessage = {context, message: filteredMessage.message}; - } - }, (status: StatusObject) => { - this.cancelWithStatus(status.code, status.details); - }) + if (this.child) { + this.child.sendMessageWithContext(context, message); + } else { + this.pendingMessage = {context, message}; + } } startRead(): void { this.trace('startRead called'); @@ -283,7 +250,7 @@ export class LoadBalancingCall implements Call { } halfClose(): void { this.trace('halfClose called'); - if (this.child && !this.writeFilterPending) { + if (this.child) { this.child.halfClose(); } else { this.pendingHalfClose = true; diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index 76a7bd208..f2e5741fa 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -19,7 +19,7 @@ import { CallCredentials } from "./call-credentials"; import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; import { LogVerbosity, Propagate, Status } from "./constants"; import { Deadline, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; -import { FilterStackFactory } from "./filter-stack"; +import { FilterStack, FilterStackFactory } from "./filter-stack"; import { InternalChannel } from "./internal-channel"; import { Metadata } from "./metadata"; import * as logging from './logging'; @@ -33,12 +33,16 @@ export class ResolvingCall implements Call { private pendingMessage: {context: MessageContext, message: Buffer} | null = null; private pendingHalfClose = false; private ended = false; + private readFilterPending = false; + private writeFilterPending = false; + private pendingChildStatus: StatusObject | null = null; private metadata: Metadata | null = null; private listener: InterceptingListener | null = null; private deadline: Deadline; private host: string; private statusWatchers: ((status: StatusObject) => void)[] = []; private deadlineTimer: NodeJS.Timer = setTimeout(() => {}, 0); + private filterStack: FilterStack | null = null; constructor( private readonly channel: InternalChannel, @@ -96,14 +100,35 @@ export class ResolvingCall implements Call { private outputStatus(status: StatusObject) { if (!this.ended) { this.ended = true; - this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); - this.statusWatchers.forEach(watcher => watcher(status)); + if (!this.filterStack) { + this.filterStack = this.filterStackFactory.createFilter(); + } + const filteredStatus = this.filterStack.receiveTrailers(status); + this.trace('ended with status: code=' + filteredStatus.code + ' details="' + filteredStatus.details + '"'); + this.statusWatchers.forEach(watcher => watcher(filteredStatus)); process.nextTick(() => { - this.listener?.onReceiveStatus(status); + this.listener?.onReceiveStatus(filteredStatus); }); } } + private sendMessageOnChild(context: MessageContext, message: Buffer): void { + if (!this.child) { + throw new Error('sendMessageonChild called with child not populated'); + } + const child = this.child; + this.writeFilterPending = true; + this.filterStack!.sendMessage(Promise.resolve({message: message, flags: context.flags})).then((filteredMessage) => { + this.writeFilterPending = false; + child.sendMessageWithContext(context, filteredMessage.message); + if (this.pendingHalfClose) { + child.halfClose(); + } + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }); + } + getConfig(): void { if (this.ended) { return; @@ -148,29 +173,46 @@ export class ResolvingCall implements Call { } this.filterStackFactory.push(config.dynamicFilterFactories); - - this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); - this.child.start(this.metadata, { - onReceiveMetadata: metadata => { - this.listener!.onReceiveMetadata(metadata); - }, - onReceiveMessage: message => { - this.listener!.onReceiveMessage(message); - }, - onReceiveStatus: status => { - this.outputStatus(status); + this.filterStack = this.filterStackFactory.createFilter(); + this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then(filteredMetadata => { + this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); + this.child.start(filteredMetadata, { + onReceiveMetadata: metadata => { + this.listener!.onReceiveMetadata(this.filterStack!.receiveMetadata(metadata)); + }, + onReceiveMessage: message => { + this.readFilterPending = true; + this.filterStack!.receiveMessage(message).then(filteredMesssage => { + this.readFilterPending = false; + this.listener!.onReceiveMessage(filteredMesssage); + if (this.pendingChildStatus) { + this.outputStatus(this.pendingChildStatus); + } + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }); + }, + onReceiveStatus: status => { + if (this.readFilterPending) { + this.pendingChildStatus = status; + } else { + this.outputStatus(status); + } + } + }); + if (this.readPending) { + this.child.startRead(); } - }); - if (this.readPending) { - this.child.startRead(); - } - if (this.pendingMessage) { - this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); - } - if (this.pendingHalfClose) { - this.child.halfClose(); - } + if (this.pendingMessage) { + this.sendMessageOnChild(this.pendingMessage.context, this.pendingMessage.message); + } else if (this.pendingHalfClose) { + this.child.halfClose(); + } + }, (status: StatusObject) => { + this.outputStatus(status); + }) } + reportResolverError(status: StatusObject) { if (this.metadata?.getOptions().waitForReady) { this.channel.queueCallForConfig(this); @@ -195,7 +237,7 @@ export class ResolvingCall implements Call { sendMessageWithContext(context: MessageContext, message: Buffer): void { this.trace('write() called with message of length ' + message.length); if (this.child) { - this.child.sendMessageWithContext(context, message); + this.sendMessageOnChild(context, message); } else { this.pendingMessage = {context, message}; } @@ -210,7 +252,7 @@ export class ResolvingCall implements Call { } halfClose(): void { this.trace('halfClose called'); - if (this.child) { + if (this.child && !this.writeFilterPending) { this.child.halfClose(); } else { this.pendingHalfClose = true; From 24c4cd7bb81c983fb9bde4e62d66493a02733cdc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 18 Oct 2022 16:29:22 -0700 Subject: [PATCH 350/694] grpc-js: Add more outlier detection tests and tracing --- .../src/load-balancer-outlier-detection.ts | 4 +- .../grpc-js/test/test-outlier-detection.ts | 169 ++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 1f1710e75..cdf580523 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -423,9 +423,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const targetRequestVolume = successRateConfig.request_volume; let addresesWithTargetVolume = 0; const successRates: number[] = [] - for (const mapEntry of this.addressMap.values()) { + for (const [address, mapEntry] of this.addressMap) { const successes = mapEntry.counter.getLastSuccesses(); const failures = mapEntry.counter.getLastFailures(); + trace('Stats for ' + address + ': successes=' + successes + ' failures=' + failures + ' targetRequestVolume=' + targetRequestVolume); if (successes + failures >= targetRequestVolume) { addresesWithTargetVolume += 1; successRates.push(successes/(successes + failures)); @@ -545,6 +546,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private startTimer(delayMs: number) { this.ejectionTimer = setTimeout(() => this.runChecks(), delayMs); + this.ejectionTimer.unref?.(); } private runChecks() { diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts index 74e536767..c9021e605 100644 --- a/packages/grpc-js/test/test-outlier-detection.ts +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -20,6 +20,7 @@ import * as path from 'path'; import * as grpc from '../src'; import { loadProtoFile } from './common'; import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection' +import { ServiceClient } from '../src/make-client'; function multiDone(done: Mocha.Done, target: number) { let count = 0; @@ -49,6 +50,54 @@ const defaultOutlierDetectionServiceConfig = { const defaultOutlierDetectionServiceConfigString = JSON.stringify(defaultOutlierDetectionServiceConfig); +const successRateOutlierDetectionServiceConfig = { + methodConfig: [], + loadBalancingConfig: [ + { + outlier_detection: { + interval: { + seconds: 1, + nanos: 0 + }, + base_ejection_time: { + seconds: 3, + nanos: 0 + }, + success_rate_ejection: { + request_volume: 5 + }, + child_policy: [{round_robin: {}}] + } + } + ] +}; + +const successRateOutlierDetectionServiceConfigString = JSON.stringify(successRateOutlierDetectionServiceConfig); + +const failurePercentageOutlierDetectionServiceConfig = { + methodConfig: [], + loadBalancingConfig: [ + { + outlier_detection: { + interval: { + seconds: 1, + nanos: 0 + }, + base_ejection_time: { + seconds: 3, + nanos: 0 + }, + failure_percentage_ejection: { + request_volume: 5 + }, + child_policy: [{round_robin: {}}] + } + } + ] +}; + +const falurePercentageOutlierDetectionServiceConfigString = JSON.stringify(failurePercentageOutlierDetectionServiceConfig); + const goodService = { echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { callback(null, call.request) @@ -353,6 +402,20 @@ describe('Outlier detection', () => { badServer.forceShutdown(); }); + function makeManyRequests(makeOneRequest: (callback: (error?: Error) => void) => void, total: number, callback: (error?: Error) => void) { + if (total === 0) { + callback(); + return; + } + makeOneRequest(error => { + if (error) { + callback(error); + return; + } + makeManyRequests(makeOneRequest, total - 1, callback); + }); + } + it('Should allow normal operation with one server', done => { const client = new EchoService(`localhost:${goodPorts[0]}`, grpc.credentials.createInsecure(), {'grpc.service_config': defaultOutlierDetectionServiceConfigString}); client.echo( @@ -364,4 +427,110 @@ describe('Outlier detection', () => { } ); }); + describe('Success rate', () => { + let makeCheckedRequest: (callback: () => void) => void; + let makeUncheckedRequest:(callback: (error?: Error) => void) => void; + before(() => { + const target = 'ipv4:///' + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + `,127.0.0.1:${badPort}`; + const client = new EchoService(target, grpc.credentials.createInsecure(), {'grpc.service_config': successRateOutlierDetectionServiceConfigString}); + makeUncheckedRequest = (callback: () => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(); + } + ); + }; + makeCheckedRequest = (callback: (error?: Error) => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(error); + } + ); + }; + }); + it('Should eject a server if it is failing requests', done => { + // Make a large volume of requests + makeManyRequests(makeUncheckedRequest, 50, () => { + // Give outlier detection time to run ejection checks + setTimeout(() => { + // Make enough requests to go around all servers + makeManyRequests(makeCheckedRequest, 10, done); + }, 1000); + }); + }); + it('Should uneject a server after the ejection period', function(done) { + this.timeout(5000); + makeManyRequests(makeUncheckedRequest, 50, () => { + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + if (error) { + done(error); + return; + } + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + assert(error); + done(); + }); + }, 3000); + }); + }, 1000); + }) + }); + }); + describe('Failure percentage', () => { + let makeCheckedRequest: (callback: () => void) => void; + let makeUncheckedRequest:(callback: (error?: Error) => void) => void; + before(() => { + const target = 'ipv4:///' + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + `,127.0.0.1:${badPort}`; + const client = new EchoService(target, grpc.credentials.createInsecure(), {'grpc.service_config': falurePercentageOutlierDetectionServiceConfigString}); + makeUncheckedRequest = (callback: () => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(); + } + ); + }; + makeCheckedRequest = (callback: (error?: Error) => void) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + callback(error); + } + ); + }; + }); + it('Should eject a server if it is failing requests', done => { + // Make a large volume of requests + makeManyRequests(makeUncheckedRequest, 50, () => { + // Give outlier detection time to run ejection checks + setTimeout(() => { + // Make enough requests to go around all servers + makeManyRequests(makeCheckedRequest, 10, done); + }, 1000); + }); + }); + it('Should uneject a server after the ejection period', function(done) { + this.timeout(5000); + makeManyRequests(makeUncheckedRequest, 50, () => { + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + if (error) { + done(error); + return; + } + setTimeout(() => { + makeManyRequests(makeCheckedRequest, 10, error => { + assert(error); + done(); + }); + }, 3000); + }); + }, 1000); + }) + }); + }); }); \ No newline at end of file From 2f124ad68bfe5d9df1130d7c25ea340a50b87a58 Mon Sep 17 00:00:00 2001 From: AVVS Date: Wed, 19 Oct 2022 14:48:11 -0700 Subject: [PATCH 351/694] fix: perf issues in hot paths 1. no unused timers, wrap tracing calls to avoid stringifying 2. track graceful end of the call and avoid emitting 'cancelled' in such cases 3. remove validate calls in metadata on operations where it's not needed 4. refactor server session stream handlers into separate channelz enabled/disabled handlers 5. refactor message request logic - reduce amount of microtasks generated 6. improve sendStatus a little when there is no metadata involved --- packages/grpc-js/src/call-stream.ts | 4 + .../generated/grpc/channelz/v1/Channelz.ts | 104 ++--- packages/grpc-js/src/metadata.ts | 64 ++- packages/grpc-js/src/server-call.ts | 305 +++++++------ packages/grpc-js/src/server.ts | 413 +++++++++++------- 5 files changed, 502 insertions(+), 388 deletions(-) diff --git a/packages/grpc-js/src/call-stream.ts b/packages/grpc-js/src/call-stream.ts index d7151eb6f..488e35fc8 100644 --- a/packages/grpc-js/src/call-stream.ts +++ b/packages/grpc-js/src/call-stream.ts @@ -97,6 +97,10 @@ export interface StatusObject { metadata: Metadata; } +export type PartialStatusObject = Pick & { + metadata: Metadata | null; +} + export const enum WriteFlags { BufferHint = 1, NoCompress = 2, diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts index ace712454..4c8c18aa7 100644 --- a/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts @@ -25,102 +25,102 @@ export interface ChannelzClient extends grpc.Client { /** * Returns a single Channel, or else a NOT_FOUND code. */ - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Server, or else a NOT_FOUND code. */ - GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - GetServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - GetServer(argument: _grpc_channelz_v1_GetServerRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Server, or else a NOT_FOUND code. */ - getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - getServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - getServer(argument: _grpc_channelz_v1_GetServerRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; /** * Gets all server sockets that exist in the process. */ - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; /** * Gets all server sockets that exist in the process. */ - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; /** * Gets all servers that exist in the process. */ - GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - GetServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - GetServers(argument: _grpc_channelz_v1_GetServersRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; /** * Gets all servers that exist in the process. */ - getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - getServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - getServers(argument: _grpc_channelz_v1_GetServersRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Socket or else a NOT_FOUND code. */ - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Socket or else a NOT_FOUND code. */ - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Subchannel, or else a NOT_FOUND code. */ - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Subchannel, or else a NOT_FOUND code. */ - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; /** * Gets all root channels (i.e. channels the application has directly * created). This does not include subchannels nor non-top level channels. */ - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; /** * Gets all root channels (i.e. channels the application has directly * created). This does not include subchannels nor non-top level channels. */ - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; } diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts index 04db642ef..376cb491a 100644 --- a/packages/grpc-js/src/metadata.ts +++ b/packages/grpc-js/src/metadata.ts @@ -48,13 +48,14 @@ function validate(key: string, value?: MetadataValue): void { if (!isLegalKey(key)) { throw new Error('Metadata key "' + key + '" contains illegal characters'); } + if (value !== null && value !== undefined) { if (isBinaryKey(key)) { - if (!(value instanceof Buffer)) { + if (!Buffer.isBuffer(value)) { throw new Error("keys that end with '-bin' must have Buffer values"); } } else { - if (value instanceof Buffer) { + if (Buffer.isBuffer(value)) { throw new Error( "keys that don't end with '-bin' must have String values" ); @@ -88,12 +89,8 @@ export class Metadata { protected internalRepr: MetadataObject = new Map(); private options: MetadataOptions; - constructor(options?: MetadataOptions) { - if (options === undefined) { - this.options = {}; - } else { - this.options = options; - } + constructor(options: MetadataOptions = {}) { + this.options = options; } /** @@ -120,9 +117,7 @@ export class Metadata { key = normalizeKey(key); validate(key, value); - const existingValue: MetadataValue[] | undefined = this.internalRepr.get( - key - ); + const existingValue: MetadataValue[] | undefined = this.internalRepr.get(key); if (existingValue === undefined) { this.internalRepr.set(key, [value]); @@ -137,7 +132,7 @@ export class Metadata { */ remove(key: string): void { key = normalizeKey(key); - validate(key); + // validate(key); this.internalRepr.delete(key); } @@ -148,7 +143,7 @@ export class Metadata { */ get(key: string): MetadataValue[] { key = normalizeKey(key); - validate(key); + // validate(key); return this.internalRepr.get(key) || []; } @@ -160,12 +155,12 @@ export class Metadata { getMap(): { [key: string]: MetadataValue } { const result: { [key: string]: MetadataValue } = {}; - this.internalRepr.forEach((values, key) => { + for (const [key, values] of this.internalRepr) { if (values.length > 0) { const v = values[0]; - result[key] = v instanceof Buffer ? v.slice() : v; + result[key] = Buffer.isBuffer(v) ? Buffer.from(v) : v; } - }); + } return result; } @@ -177,9 +172,9 @@ export class Metadata { const newMetadata = new Metadata(this.options); const newInternalRepr = newMetadata.internalRepr; - this.internalRepr.forEach((value, key) => { + for (const [key, value] of this.internalRepr) { const clonedValue: MetadataValue[] = value.map((v) => { - if (v instanceof Buffer) { + if (Buffer.isBuffer(v)) { return Buffer.from(v); } else { return v; @@ -187,7 +182,7 @@ export class Metadata { }); newInternalRepr.set(key, clonedValue); - }); + } return newMetadata; } @@ -200,13 +195,13 @@ export class Metadata { * @param other A Metadata object. */ merge(other: Metadata): void { - other.internalRepr.forEach((values, key) => { + for (const [key, values] of other.internalRepr) { const mergedValue: MetadataValue[] = ( this.internalRepr.get(key) || [] ).concat(values); this.internalRepr.set(key, mergedValue); - }); + } } setOptions(options: MetadataOptions) { @@ -223,17 +218,13 @@ export class Metadata { toHttp2Headers(): http2.OutgoingHttpHeaders { // NOTE: Node <8.9 formats http2 headers incorrectly. const result: http2.OutgoingHttpHeaders = {}; - this.internalRepr.forEach((values, key) => { + + for (const [key, values] of this.internalRepr) { // We assume that the user's interaction with this object is limited to // through its public API (i.e. keys and values are already validated). - result[key] = values.map((value) => { - if (value instanceof Buffer) { - return value.toString('base64'); - } else { - return value; - } - }); - }); + result[key] = values.map(bufToString); + } + return result; } @@ -248,7 +239,7 @@ export class Metadata { */ toJSON() { const result: { [key: string]: MetadataValue[] } = {}; - for (const [key, values] of this.internalRepr.entries()) { + for (const [key, values] of this.internalRepr) { result[key] = values; } return result; @@ -261,10 +252,10 @@ export class Metadata { */ static fromHttp2Headers(headers: http2.IncomingHttpHeaders): Metadata { const result = new Metadata(); - Object.keys(headers).forEach((key) => { + for (const key of Object.keys(headers)) { // Reserved headers (beginning with `:`) are not valid keys. if (key.charAt(0) === ':') { - return; + continue; } const values = headers[key]; @@ -297,7 +288,12 @@ export class Metadata { const message = `Failed to add metadata entry ${key}: ${values}. ${error.message}. For more information see https://github.com/grpc/grpc-node/issues/1173`; log(LogVerbosity.ERROR, message); } - }); + } + return result; } } + +const bufToString = (val: string | Buffer): string => { + return Buffer.isBuffer(val) ? val.toString('base64') : val +}; diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 1f7f3eb04..84ff7c8ee 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -19,8 +19,9 @@ import { EventEmitter } from 'events'; import * as http2 from 'http2'; import { Duplex, Readable, Writable } from 'stream'; import * as zlib from 'zlib'; +import { promisify } from 'util'; -import { Deadline, StatusObject } from './call-stream'; +import { Deadline, StatusObject, PartialStatusObject } from './call-stream'; import { Status, DEFAULT_MAX_SEND_MESSAGE_LENGTH, @@ -35,6 +36,8 @@ import { ChannelOptions } from './channel-options'; import * as logging from './logging'; const TRACER_NAME = 'server_call'; +const unzip = promisify(zlib.unzip); +const inflate = promisify(zlib.inflate); function trace(text: string): void { logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); @@ -86,25 +89,22 @@ export type ServerSurfaceCall = { export type ServerUnaryCall = ServerSurfaceCall & { request: RequestType; }; -export type ServerReadableStream< - RequestType, - ResponseType -> = ServerSurfaceCall & ObjectReadable; -export type ServerWritableStream< - RequestType, - ResponseType -> = ServerSurfaceCall & - ObjectWritable & { - request: RequestType; - end: (metadata?: Metadata) => void; - }; +export type ServerReadableStream = + ServerSurfaceCall & ObjectReadable; +export type ServerWritableStream = + ServerSurfaceCall & + ObjectWritable & { + request: RequestType; + end: (metadata?: Metadata) => void; + }; export type ServerDuplexStream = ServerSurfaceCall & ObjectReadable & ObjectWritable & { end: (metadata?: Metadata) => void }; export class ServerUnaryCallImpl extends EventEmitter - implements ServerUnaryCall { + implements ServerUnaryCall +{ cancelled: boolean; constructor( @@ -136,7 +136,8 @@ export class ServerUnaryCallImpl export class ServerReadableStreamImpl extends Readable - implements ServerReadableStream { + implements ServerReadableStream +{ cancelled: boolean; constructor( @@ -178,7 +179,8 @@ export class ServerReadableStreamImpl export class ServerWritableStreamImpl extends Writable - implements ServerWritableStream { + implements ServerWritableStream +{ cancelled: boolean; private trailingMetadata: Metadata; @@ -257,7 +259,8 @@ export class ServerWritableStreamImpl export class ServerDuplexStreamImpl extends Duplex - implements ServerDuplexStream { + implements ServerDuplexStream +{ cancelled: boolean; private trailingMetadata: Metadata; @@ -395,7 +398,8 @@ export class Http2ServerCallStream< ResponseType > extends EventEmitter { cancelled = false; - deadlineTimer: NodeJS.Timer = setTimeout(() => {}, 0); + deadlineTimer: NodeJS.Timer | null = null; + private statusSent = false; private deadline: Deadline = Infinity; private wantTrailers = false; private metadataSent = false; @@ -428,10 +432,20 @@ export class Http2ServerCallStream< ' stream closed with rstCode ' + this.stream.rstCode ); - this.cancelled = true; - this.emit('cancelled', 'cancelled'); - this.emit('streamEnd', false); - this.sendStatus({code: Status.CANCELLED, details: 'Cancelled by client', metadata: new Metadata()}); + + if (!this.statusSent) { + this.cancelled = true; + this.emit('cancelled', 'cancelled'); + this.emit('streamEnd', false); + this.sendStatus({ + code: Status.CANCELLED, + details: 'Cancelled by client', + metadata: null, + }); + } + + // to compensate for a fact that cancelled is not always called + this.emit('close'); }); this.stream.on('drain', () => { @@ -444,9 +458,6 @@ export class Http2ServerCallStream< if ('grpc.max_receive_message_length' in options) { this.maxReceiveMessageSize = options['grpc.max_receive_message_length']!; } - - // Clear noop timer - clearTimeout(this.deadlineTimer); } private checkCancelled(): boolean { @@ -458,52 +469,22 @@ export class Http2ServerCallStream< return this.cancelled; } - private getDecompressedMessage(message: Buffer, encoding: string) { - switch (encoding) { - case 'deflate': { - return new Promise((resolve, reject) => { - zlib.inflate(message.slice(5), (err, output) => { - if (err) { - this.sendError({ - code: Status.INTERNAL, - details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, - }); - resolve(); - } else { - resolve(output); - } - }); - }); - } - - case 'gzip': { - return new Promise((resolve, reject) => { - zlib.unzip(message.slice(5), (err, output) => { - if (err) { - this.sendError({ - code: Status.INTERNAL, - details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, - }); - resolve(); - } else { - resolve(output); - } - }); - }); - } - - case 'identity': { - return Promise.resolve(message.slice(5)); - } - - default: { - this.sendError({ - code: Status.UNIMPLEMENTED, - details: `Received message compressed with unsupported encoding "${encoding}"`, - }); - return Promise.resolve(); - } + private getDecompressedMessage( + message: Buffer, + encoding: string + ): Buffer | Promise { + if (encoding === 'deflate') { + return inflate(message.subarray(5)); + } else if (encoding === 'gzip') { + return unzip(message.subarray(5)); + } else if (encoding === 'identity') { + return message.subarray(5); } + + return Promise.reject({ + code: Status.UNIMPLEMENTED, + details: `Received message compressed with unsupported encoding "${encoding}"`, + }); } sendMetadata(customMetadata?: Metadata) { @@ -518,13 +499,22 @@ export class Http2ServerCallStream< this.metadataSent = true; const custom = customMetadata ? customMetadata.toHttp2Headers() : null; // TODO(cjihrig): Include compression headers. - const headers = Object.assign({}, defaultResponseHeaders, custom); + const headers = { ...defaultResponseHeaders, ...custom }; this.stream.respond(headers, defaultResponseOptions); } receiveMetadata(headers: http2.IncomingHttpHeaders) { const metadata = Metadata.fromHttp2Headers(headers); + if (logging.isTracerEnabled(TRACER_NAME)) { + trace( + 'Request to ' + + this.handler.path + + ' received headers ' + + JSON.stringify(metadata.toJSON()) + ); + } + // TODO(cjihrig): Receive compression metadata. const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER); @@ -556,52 +546,95 @@ export class Http2ServerCallStream< return metadata; } - receiveUnaryMessage(encoding: string): Promise { - return new Promise((resolve, reject) => { - const stream = this.stream; - const chunks: Buffer[] = []; - let totalLength = 0; + receiveUnaryMessage( + encoding: string, + next: ( + err: Partial | null, + request?: RequestType + ) => void + ): void { + const { stream } = this; + + let receivedLength = 0; + const call = this; + const body: Buffer[] = []; + const limit = this.maxReceiveMessageSize; + + stream.on('data', onData); + stream.on('end', onEnd); + stream.on('error', onEnd); + + function onData(chunk: Buffer) { + receivedLength += chunk.byteLength; + + if (limit !== -1 && receivedLength > limit) { + stream.removeListener('data', onData); + stream.removeListener('end', onEnd); + stream.removeListener('error', onEnd); + next({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message larger than max (${receivedLength} vs. ${limit})`, + }); + return; + } - stream.on('data', (data: Buffer) => { - chunks.push(data); - totalLength += data.byteLength; - }); + body.push(chunk); + } - stream.once('end', async () => { - try { - const requestBytes = Buffer.concat(chunks, totalLength); - if ( - this.maxReceiveMessageSize !== -1 && - requestBytes.length > this.maxReceiveMessageSize - ) { - this.sendError({ - code: Status.RESOURCE_EXHAUSTED, - details: `Received message larger than max (${requestBytes.length} vs. ${this.maxReceiveMessageSize})`, - }); - resolve(); - } - - this.emit('receiveMessage'); - - const compressed = requestBytes.readUInt8(0) === 1; - const compressedMessageEncoding = compressed ? encoding : 'identity'; - const decompressedMessage = await this.getDecompressedMessage(requestBytes, compressedMessageEncoding); - - // Encountered an error with decompression; it'll already have been propogated back - // Just return early - if (!decompressedMessage) { - resolve(); - } - else { - resolve(this.deserializeMessage(decompressedMessage)); - } - } catch (err) { - err.code = Status.INTERNAL; - this.sendError(err); - resolve(); - } - }); - }); + function onEnd(err?: Error) { + stream.removeListener('data', onData); + stream.removeListener('end', onEnd); + stream.removeListener('error', onEnd); + + if (err !== undefined) { + next({ code: Status.INTERNAL, details: err.message }); + return; + } + + if (receivedLength === 0) { + next({ code: Status.INTERNAL, details: 'received empty unary message' }) + return; + } + + call.emit('receiveMessage'); + + const requestBytes = Buffer.concat(body, receivedLength); + const compressed = requestBytes.readUInt8(0) === 1; + const compressedMessageEncoding = compressed ? encoding : 'identity'; + const decompressedMessage = call.getDecompressedMessage( + requestBytes, + compressedMessageEncoding + ); + + if (Buffer.isBuffer(decompressedMessage)) { + call.safeDeserializeMessage(decompressedMessage, next); + return; + } + + decompressedMessage.then( + (decompressed) => call.safeDeserializeMessage(decompressed, next), + (err: any) => next( + err.code + ? err + : { + code: Status.INTERNAL, + details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, + } + ) + ) + } + } + + private safeDeserializeMessage( + buffer: Buffer, + next: (err: Partial | null, request?: RequestType) => void + ) { + try { + next(null, this.deserializeMessage(buffer)); + } catch (err) { + err.code = Status.INTERNAL; + next(err); + } } serializeMessage(value: ResponseType) { @@ -623,18 +656,19 @@ export class Http2ServerCallStream< async sendUnaryMessage( err: ServerErrorResponse | ServerStatusResponse | null, value?: ResponseType | null, - metadata?: Metadata, + metadata?: Metadata | null, flags?: number ) { if (this.checkCancelled()) { return; } - if (!metadata) { - metadata = new Metadata(); + + if (metadata === undefined) { + metadata = null; } if (err) { - if (!Object.prototype.hasOwnProperty.call(err, 'metadata')) { + if (!Object.prototype.hasOwnProperty.call(err, 'metadata') && metadata) { err.metadata = metadata; } this.sendError(err); @@ -652,7 +686,7 @@ export class Http2ServerCallStream< } } - sendStatus(statusObj: StatusObject) { + sendStatus(statusObj: PartialStatusObject) { this.emit('callEnd', statusObj.code); this.emit('streamEnd', statusObj.code === Status.OK); if (this.checkCancelled()) { @@ -668,20 +702,19 @@ export class Http2ServerCallStream< statusObj.details ); - clearTimeout(this.deadlineTimer); + if (this.deadlineTimer) clearTimeout(this.deadlineTimer); if (!this.wantTrailers) { this.wantTrailers = true; this.stream.once('wantTrailers', () => { - const trailersToSend = Object.assign( - { - [GRPC_STATUS_HEADER]: statusObj.code, - [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details as string), - }, - statusObj.metadata.toHttp2Headers() - ); + const trailersToSend = { + [GRPC_STATUS_HEADER]: statusObj.code, + [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), + ...statusObj.metadata?.toHttp2Headers(), + }; this.stream.sendTrailers(trailersToSend); + this.statusSent = true; }); this.sendMetadata(); this.stream.end(); @@ -689,13 +722,13 @@ export class Http2ServerCallStream< } sendError(error: ServerErrorResponse | ServerStatusResponse) { - const status: StatusObject = { + const status: PartialStatusObject = { code: Status.UNKNOWN, details: 'message' in error ? error.message : 'Unknown Error', metadata: 'metadata' in error && error.metadata !== undefined ? error.metadata - : new Metadata(), + : null, }; if ( @@ -744,6 +777,9 @@ export class Http2ServerCallStream< call.emit('cancelled', reason); }); + // to compensate for the fact that cancelled is no longer always called + this.once('close', () => call.emit('close')) + this.once('callEnd', (status) => call.emit('callEnd', status)); } @@ -766,7 +802,7 @@ export class Http2ServerCallStream< pushedEnd = true; this.pushOrBufferMessage(readable, null); } - } + }; this.stream.on('data', async (data: Buffer) => { const messages = decoder.write(data); @@ -788,12 +824,15 @@ export class Http2ServerCallStream< const compressed = message.readUInt8(0) === 1; const compressedMessageEncoding = compressed ? encoding : 'identity'; - const decompressedMessage = await this.getDecompressedMessage(message, compressedMessageEncoding); + const decompressedMessage = await this.getDecompressedMessage( + message, + compressedMessageEncoding + ); // Encountered an error with decompression; it'll already have been propogated back // Just return early if (!decompressedMessage) return; - + this.pushOrBufferMessage(readable, decompressedMessage); } pendingMessageProcessing = false; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 499fde4a3..2e89f459b 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -62,6 +62,10 @@ import { parseUri } from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; +const { + HTTP2_HEADER_PATH +} = http2.constants + const TRACER_NAME = 'server'; interface BindResult { @@ -77,7 +81,6 @@ function getUnimplementedStatusResponse( return { code: Status.UNIMPLEMENTED, details: `The server does not implement the method ${methodName}`, - metadata: new Metadata(), }; } @@ -147,6 +150,7 @@ export class Server { private sessions = new Map(); private started = false; private options: ChannelOptions; + private serverAddressString: string = 'null' // Channelz Info private readonly channelzEnabled: boolean = true; @@ -165,6 +169,7 @@ export class Server { if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Server created'); } + this.trace('Server constructed'); } @@ -730,150 +735,210 @@ export class Server { return this.channelzRef; } - private _setupHandlers( - http2Server: http2.Http2Server | http2.Http2SecureServer - ): void { - if (http2Server === null) { - return; + private _verifyContentType(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders): boolean { + const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE]; + + if ( + typeof contentType !== 'string' || + !contentType.startsWith('application/grpc') + ) { + stream.respond( + { + [http2.constants.HTTP2_HEADER_STATUS]: + http2.constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + }, + { endStream: true } + ); + return false } - http2Server.on( - 'stream', - (stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) => { - const channelzSessionInfo = this.sessions.get(stream.session as http2.ServerHttp2Session); - if (this.channelzEnabled) { - this.callTracker.addCallStarted(); - channelzSessionInfo?.streamTracker.addCallStarted(); - } - const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE]; - - if ( - typeof contentType !== 'string' || - !contentType.startsWith('application/grpc') - ) { - stream.respond( - { - [http2.constants.HTTP2_HEADER_STATUS]: - http2.constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, - }, - { endStream: true } - ); - this.callTracker.addCallFailed(); - if (this.channelzEnabled) { - channelzSessionInfo?.streamTracker.addCallFailed(); - } - return; - } + return true + } - let call: Http2ServerCallStream | null = null; + private _retrieveHandler(headers: http2.IncomingHttpHeaders): Handler { + const path = headers[HTTP2_HEADER_PATH] as string - try { - const path = headers[http2.constants.HTTP2_HEADER_PATH] as string; - const serverAddress = http2Server.address(); - let serverAddressString = 'null'; - if (serverAddress) { - if (typeof serverAddress === 'string') { - serverAddressString = serverAddress; - } else { - serverAddressString = - serverAddress.address + ':' + serverAddress.port; - } - } - this.trace( - 'Received call to method ' + - path + - ' at address ' + - serverAddressString - ); - const handler = this.handlers.get(path); + this.trace( + 'Received call to method ' + + path + + ' at address ' + + this.serverAddressString + ); - if (handler === undefined) { - this.trace( - 'No handler registered for method ' + - path + - '. Sending UNIMPLEMENTED status.' - ); - throw getUnimplementedStatusResponse(path); - } + const handler = this.handlers.get(path); - call = new Http2ServerCallStream(stream, handler, this.options); - call.once('callEnd', (code: Status) => { - if (code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); - if (this.channelzEnabled && channelzSessionInfo) { - call.once('streamEnd', (success: boolean) => { - if (success) { - channelzSessionInfo.streamTracker.addCallSucceeded(); - } else { - channelzSessionInfo.streamTracker.addCallFailed(); - } - }); - call.on('sendMessage', () => { - channelzSessionInfo.messagesSent += 1; - channelzSessionInfo.lastMessageSentTimestamp = new Date(); - }); - call.on('receiveMessage', () => { - channelzSessionInfo.messagesReceived += 1; - channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); - }); - } - const metadata = call.receiveMetadata(headers); - const encoding = (metadata.get('grpc-encoding')[0] as string | undefined) ?? 'identity'; - metadata.remove('grpc-encoding'); - - switch (handler.type) { - case 'unary': - handleUnary(call, handler as UntypedUnaryHandler, metadata, encoding); - break; - case 'clientStream': - handleClientStreaming( - call, - handler as UntypedClientStreamingHandler, - metadata, - encoding - ); - break; - case 'serverStream': - handleServerStreaming( - call, - handler as UntypedServerStreamingHandler, - metadata, - encoding - ); - break; - case 'bidi': - handleBidiStreaming( - call, - handler as UntypedBidiStreamingHandler, - metadata, - encoding - ); - break; - default: - throw new Error(`Unknown handler type: ${handler.type}`); - } - } catch (err) { - if (!call) { - call = new Http2ServerCallStream(stream, null!, this.options); - if (this.channelzEnabled) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed() - } - } + if (handler === undefined) { + this.trace( + 'No handler registered for method ' + + path + + '. Sending UNIMPLEMENTED status.' + ); + throw getUnimplementedStatusResponse(path); + } - if (err.code === undefined) { - err.code = Status.INTERNAL; - } + return handler + } + + private _respondWithError>( + err: T, + stream: http2.ServerHttp2Stream, + channelzSessionInfo: ChannelzSessionInfo | null = null + ) { + const call = new Http2ServerCallStream(stream, null!, this.options); + + if (err.code === undefined) { + err.code = Status.INTERNAL; + } - call.sendError(err); + if (this.channelzEnabled) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed() + } + + call.sendError(err); + } + + private _channelzHandler(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) { + const channelzSessionInfo = this.sessions.get(stream.session as http2.ServerHttp2Session); + + this.callTracker.addCallStarted(); + channelzSessionInfo?.streamTracker.addCallStarted(); + + if (!this._verifyContentType(stream, headers)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); + return + } + + let handler: Handler + try { + handler = this._retrieveHandler(headers) + } catch (err) { + this._respondWithError(err, stream, channelzSessionInfo) + return + } + + const call = new Http2ServerCallStream(stream, handler, this.options); + + call.once('callEnd', (code: Status) => { + if (code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }); + + if (channelzSessionInfo) { + call.once('streamEnd', (success: boolean) => { + if (success) { + channelzSessionInfo.streamTracker.addCallSucceeded(); + } else { + channelzSessionInfo.streamTracker.addCallFailed(); } + }); + call.on('sendMessage', () => { + channelzSessionInfo.messagesSent += 1; + channelzSessionInfo.lastMessageSentTimestamp = new Date(); + }); + call.on('receiveMessage', () => { + channelzSessionInfo.messagesReceived += 1; + channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); + }); + } + + if (!this._runHandlerForCall(call, handler, headers)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed() + + call.sendError({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}` + }); + } + } + + private _streamHandler(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) { + if (this._verifyContentType(stream, headers) !== true) { + return + } + + let handler: Handler + try { + handler = this._retrieveHandler(headers) + } catch (err) { + this._respondWithError(err, stream, null) + return + } + + const call = new Http2ServerCallStream(stream, handler, this.options) + if (!this._runHandlerForCall(call, handler, headers)) { + call.sendError({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}` + }); + } + } + + private _runHandlerForCall(call: Http2ServerCallStream, handler: Handler, headers: http2.IncomingHttpHeaders): boolean { + const metadata = call.receiveMetadata(headers); + const encoding = (metadata.get('grpc-encoding')[0] as string | undefined) ?? 'identity'; + metadata.remove('grpc-encoding'); + + const { type } = handler + if (type === 'unary') { + handleUnary(call, handler as UntypedUnaryHandler, metadata, encoding); + } else if (type === 'clientStream') { + handleClientStreaming( + call, + handler as UntypedClientStreamingHandler, + metadata, + encoding + ); + } else if (type === 'serverStream') { + handleServerStreaming( + call, + handler as UntypedServerStreamingHandler, + metadata, + encoding + ); + } else if (type === 'bidi') { + handleBidiStreaming( + call, + handler as UntypedBidiStreamingHandler, + metadata, + encoding + ); + } else { + return false + } + + return true + } + + private _setupHandlers( + http2Server: http2.Http2Server | http2.Http2SecureServer + ): void { + if (http2Server === null) { + return; + } + + const serverAddress = http2Server.address(); + let serverAddressString = 'null' + if (serverAddress) { + if (typeof serverAddress === 'string') { + serverAddressString = serverAddress + } else { + serverAddressString = + serverAddress.address + ':' + serverAddress.port } - ); + } + this.serverAddressString = serverAddressString + + const handler = this.channelzEnabled + ? this._channelzHandler + : this._streamHandler + http2Server.on('stream', handler.bind(this)) http2Server.on('session', (session) => { if (!this.started) { session.destroy(); @@ -910,35 +975,40 @@ export class Server { } } -async function handleUnary( +function handleUnary( call: Http2ServerCallStream, handler: UnaryHandler, metadata: Metadata, encoding: string -): Promise { - const request = await call.receiveUnaryMessage(encoding); +): void { + call.receiveUnaryMessage(encoding, (err, request) => { + if (err) { + call.sendError(err) + return + } - if (request === undefined || call.cancelled) { - return; - } + if (request === undefined || call.cancelled) { + return; + } - const emitter = new ServerUnaryCallImpl( - call, - metadata, - request - ); + const emitter = new ServerUnaryCallImpl( + call, + metadata, + request + ); - handler.func( - emitter, - ( - err: ServerErrorResponse | ServerStatusResponse | null, - value?: ResponseType | null, - trailer?: Metadata, - flags?: number - ) => { - call.sendUnaryMessage(err, value, trailer, flags); - } - ); + handler.func( + emitter, + ( + err: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number + ) => { + call.sendUnaryMessage(err, value, trailer, flags); + } + ); + }); } function handleClientStreaming( @@ -972,26 +1042,31 @@ function handleClientStreaming( handler.func(stream, respond); } -async function handleServerStreaming( +function handleServerStreaming( call: Http2ServerCallStream, handler: ServerStreamingHandler, metadata: Metadata, encoding: string -): Promise { - const request = await call.receiveUnaryMessage(encoding); +): void { + call.receiveUnaryMessage(encoding, (err, request) => { + if (err) { + call.sendError(err) + return + } - if (request === undefined || call.cancelled) { - return; - } + if (request === undefined || call.cancelled) { + return; + } - const stream = new ServerWritableStreamImpl( - call, - metadata, - handler.serialize, - request - ); + const stream = new ServerWritableStreamImpl( + call, + metadata, + handler.serialize, + request + ); - handler.func(stream); + handler.func(stream); + }); } function handleBidiStreaming( From 93de96f490d5683315532ee0bcdbcccff04c5ed3 Mon Sep 17 00:00:00 2001 From: AVVS Date: Wed, 19 Oct 2022 15:25:42 -0700 Subject: [PATCH 352/694] revert: extra close event on stream --- packages/grpc-js/src/server-call.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 84ff7c8ee..6ddfe13b9 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -443,9 +443,6 @@ export class Http2ServerCallStream< metadata: null, }); } - - // to compensate for a fact that cancelled is not always called - this.emit('close'); }); this.stream.on('drain', () => { @@ -777,9 +774,6 @@ export class Http2ServerCallStream< call.emit('cancelled', reason); }); - // to compensate for the fact that cancelled is no longer always called - this.once('close', () => call.emit('close')) - this.once('callEnd', (status) => call.emit('callEnd', status)); } From 035c260e3665ebcd67b2be3477d3fe8f08f787d7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 18 Oct 2022 13:25:58 -0700 Subject: [PATCH 353/694] grpc-js: Implement retries --- packages/grpc-js/src/channel-options.ts | 11 + packages/grpc-js/src/internal-channel.ts | 45 +- packages/grpc-js/src/load-balancing-call.ts | 13 +- .../grpc-js/src/resolving-load-balancer.ts | 3 +- packages/grpc-js/src/retrying-call.ts | 590 ++++++++++++++++++ packages/grpc-js/src/service-config.ts | 95 ++- packages/grpc-js/src/subchannel-call.ts | 14 +- packages/grpc-js/src/subchannel.ts | 4 +- 8 files changed, 762 insertions(+), 13 deletions(-) create mode 100644 packages/grpc-js/src/retrying-call.ts diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index b7fc92fa9..0842f4e1c 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -44,6 +44,14 @@ export interface ChannelOptions { 'grpc.default_compression_algorithm'?: CompressionAlgorithms; 'grpc.enable_channelz'?: number; 'grpc.dns_min_time_between_resolutions_ms'?: number; + 'grpc.enable_retries'?: number; + 'grpc.per_rpc_retry_buffer_size'?: number; + /* This option is pattered like a core option, but the core does not have + * this option. It is closely related to the option + * grpc.per_rpc_retry_buffer_size, which is in the core. The core will likely + * implement this functionality using the ResourceQuota mechanism, so there + * will probably not be any collision or other inconsistency. */ + 'grpc.retry_buffer_size'?: number; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; @@ -71,6 +79,9 @@ export const recognizedOptions = { 'grpc.enable_http_proxy': true, 'grpc.enable_channelz': true, 'grpc.dns_min_time_between_resolutions_ms': true, + 'grpc.enable_retries': true, + 'grpc.per_rpc_retry_buffer_size': true, + 'grpc.retry_buffer_size': true, 'grpc-node.max_session_memory': true, }; diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 16f4b9832..d0ae88c5d 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -50,6 +50,7 @@ import { Deadline, getDeadlineTimeoutString } from './deadline'; import { ResolvingCall } from './resolving-call'; import { getNextCallNumber } from './call-number'; import { restrictControlPlaneStatusCode } from './control-plane-status'; +import { MessageBufferTracker, RetryingCall, RetryThrottler } from './retrying-call'; /** * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args @@ -78,6 +79,11 @@ interface ErrorConfigResult { type GetConfigResult = NoneConfigResult | SuccessConfigResult | ErrorConfigResult; +const RETRY_THROTTLER_MAP: Map = new Map(); + +const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1<<24; // 16 MB +const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1<<20; // 1 MB + export class InternalChannel { private resolvingLoadBalancer: ResolvingLoadBalancer; @@ -111,6 +117,7 @@ export class InternalChannel { * than TRANSIENT_FAILURE. */ private currentResolutionError: StatusObject | null = null; + private retryBufferTracker: MessageBufferTracker; // Channelz info private readonly channelzEnabled: boolean = true; @@ -179,6 +186,10 @@ export class InternalChannel { this.subchannelPool = getSubchannelPool( (options['grpc.use_local_subchannel_pool'] ?? 0) === 0 ); + this.retryBufferTracker = new MessageBufferTracker( + options['grpc.retry_buffer_size'] ?? DEFAULT_RETRY_BUFFER_SIZE_BYTES, + options['grpc.per_rpc_retry_buffer_size'] ?? DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES + ); const channelControlHelper: ChannelControlHelper = { createSubchannel: ( subchannelAddress: SubchannelAddress, @@ -226,7 +237,12 @@ export class InternalChannel { this.target, channelControlHelper, options, - (configSelector) => { + (serviceConfig, configSelector) => { + if (serviceConfig.retryThrottling) { + RETRY_THROTTLER_MAP.set(this.getTarget(), new RetryThrottler(serviceConfig.retryThrottling.maxTokens, serviceConfig.retryThrottling.tokenRatio, RETRY_THROTTLER_MAP.get(this.getTarget()))); + } else { + RETRY_THROTTLER_MAP.delete(this.getTarget()); + } if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); } @@ -243,6 +259,7 @@ export class InternalChannel { } this.configSelectionQueue = []; }); + }, (status) => { if (this.channelzEnabled) { @@ -405,6 +422,24 @@ export class InternalChannel { return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, callNumber); } + createRetryingCall( + callConfig: CallConfig, + method: string, + host: string, + credentials: CallCredentials, + deadline: Deadline + ): RetryingCall { + const callNumber = getNextCallNumber(); + this.trace( + 'createRetryingCall [' + + callNumber + + '] method="' + + method + + '"' + ); + return new RetryingCall(this, callConfig, method, host, credentials, deadline, callNumber, this.retryBufferTracker, RETRY_THROTTLER_MAP.get(this.getTarget())) + } + createInnerCall( callConfig: CallConfig, method: string, @@ -413,7 +448,11 @@ export class InternalChannel { deadline: Deadline ): Call { // Create a RetryingCall if retries are enabled - return this.createLoadBalancingCall(callConfig, method, host, credentials, deadline); + if (this.options['grpc.enable_retries'] === 0) { + return this.createLoadBalancingCall(callConfig, method, host, credentials, deadline); + } else { + return this.createRetryingCall(callConfig, method, host, credentials, deadline); + } } createResolvingCall( @@ -439,7 +478,7 @@ export class InternalChannel { parentCall: parentCall, }; - const call = new ResolvingCall(this, method, finalOptions, this.filterStackFactory, this.credentials._getCallCredentials(), getNextCallNumber()); + const call = new ResolvingCall(this, method, finalOptions, this.filterStackFactory, this.credentials._getCallCredentials(), callNumber); if (this.channelzEnabled) { this.callTracker.addCallStarted(); diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 6faa15592..23b9a9174 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -29,6 +29,7 @@ import { CallConfig } from "./resolver"; import { splitHostPort } from "./uri-parser"; import * as logging from './logging'; import { restrictControlPlaneStatusCode } from "./control-plane-status"; +import * as http2 from 'http2'; const TRACER_NAME = 'load_balancing_call'; @@ -38,6 +39,10 @@ export interface StatusObjectWithProgress extends StatusObject { progress: RpcProgress; } +export interface LoadBalancingCallInterceptingListener extends InterceptingListener { + onReceiveStatus(status: StatusObjectWithProgress): void; +} + export class LoadBalancingCall implements Call { private child: SubchannelCall | null = null; private readPending = false; @@ -151,7 +156,11 @@ export class LoadBalancingCall implements Call { this.listener!.onReceiveMessage(message); }, onReceiveStatus: status => { - this.outputStatus(status, 'PROCESSED'); + if (status.code === http2.constants.NGHTTP2_REFUSED_STREAM) { + this.outputStatus(status, 'REFUSED'); + } else { + this.outputStatus(status, 'PROCESSED'); + } } }); } catch (error) { @@ -226,7 +235,7 @@ export class LoadBalancingCall implements Call { getPeer(): string { return this.child?.getPeer() ?? this.channel.getTarget(); } - start(metadata: Metadata, listener: InterceptingListener): void { + start(metadata: Metadata, listener: LoadBalancingCallInterceptingListener): void { this.trace('start called'); this.listener = listener; this.metadata = metadata; diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index da097cc42..a39606f2c 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -83,7 +83,7 @@ function getDefaultConfigSelector( } export interface ResolutionCallback { - (configSelector: ConfigSelector): void; + (serviceConfig: ServiceConfig, configSelector: ConfigSelector): void; } export interface ResolutionFailureCallback { @@ -239,6 +239,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { const finalServiceConfig = workingServiceConfig ?? this.defaultServiceConfig; this.onSuccessfulResolution( + finalServiceConfig, configSelector ?? getDefaultConfigSelector(finalServiceConfig) ); }, diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts new file mode 100644 index 000000000..7c15023d5 --- /dev/null +++ b/packages/grpc-js/src/retrying-call.ts @@ -0,0 +1,590 @@ +/* + * Copyright 2022 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 { CallCredentials } from "./call-credentials"; +import { LogVerbosity, Status } from "./constants"; +import { Deadline } from "./deadline"; +import { Metadata } from "./metadata"; +import { CallConfig } from "./resolver"; +import * as logging from './logging'; +import { Call, InterceptingListener, MessageContext, StatusObject, WriteCallback, WriteObject } from "./call-interface"; +import { LoadBalancingCall, StatusObjectWithProgress } from "./load-balancing-call"; +import { InternalChannel } from "./internal-channel"; + +const TRACER_NAME = 'retrying_call'; + +export class RetryThrottler { + private tokens: number; + constructor(private readonly maxTokens: number, private readonly tokenRatio: number, previousRetryThrottler?: RetryThrottler) { + if (previousRetryThrottler) { + /* When carrying over tokens from a previous config, rescale them to the + * new max value */ + this.tokens = previousRetryThrottler.tokens * (maxTokens / previousRetryThrottler.maxTokens); + } else { + this.tokens = maxTokens; + } + } + + addCallSucceeded() { + this.tokens = Math.max(this.tokens + this.tokenRatio, this.maxTokens); + } + + addCallFailed() { + this.tokens = Math.min(this.tokens - 1, 0); + } + + canRetryCall() { + return this.tokens > this.maxTokens / 2; + } +} + +export class MessageBufferTracker { + private totalAllocated: number = 0; + private allocatedPerCall: Map = new Map(); + + constructor(private totalLimit: number, private limitPerCall: number) {} + + allocate(size: number, callId: number): boolean { + const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; + if (this.limitPerCall - currentPerCall < size || this.totalLimit - this.totalAllocated < size) { + return false; + } + this.allocatedPerCall.set(callId, currentPerCall + size); + this.totalAllocated += size; + return true; + } + + free(size: number, callId: number) { + if (this.totalAllocated < size) { + throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > total allocated ${this.totalAllocated}`); + } + this.totalAllocated -= size; + const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; + if (currentPerCall < size) { + throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > allocated for call ${currentPerCall}`); + } + this.allocatedPerCall.set(callId, currentPerCall - size); + } + + freeAll(callId: number) { + const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; + if (this.totalAllocated < currentPerCall) { + throw new Error(`Invalid buffer allocation state: call ${callId} allocated ${currentPerCall} > total allocated ${this.totalAllocated}`); + } + this.totalAllocated -= currentPerCall; + this.allocatedPerCall.delete(callId); + } +} + +type UnderlyingCallState = 'ACTIVE' | 'COMPLETED'; + +interface UnderlyingCall { + state: UnderlyingCallState; + call: LoadBalancingCall; + nextMessageToSend: number; +} + +/** + * A retrying call can be in one of these states: + * RETRY: Retries are configured and new attempts may be sent + * HEDGING: Hedging is configured and new attempts may be sent + * TRANSPARENT_ONLY: Neither retries nor hedging are configured, and + * transparent retry attempts may still be sent + * COMMITTED: One attempt is committed, and no new attempts will be + * sent + */ +type RetryingCallState = 'RETRY' | 'HEDGING' | 'TRANSPARENT_ONLY' | 'COMMITTED'; + +/** + * The different types of objects that can be stored in the write buffer, with + * the following meanings: + * MESSAGE: This is a message to be sent. + * HALF_CLOSE: When this entry is reached, the calls should send a half-close. + * FREED: This slot previously contained a message that has been sent on all + * child calls and is no longer needed. + */ +type WriteBufferEntryType = 'MESSAGE' | 'HALF_CLOSE' | 'FREED'; + +/** + * Entry in the buffer of messages to send to the remote end. + */ +interface WriteBufferEntry { + entryType: WriteBufferEntryType; + /** + * Message to send. + * Only populated if entryType is MESSAGE. + */ + message?: WriteObject; + /** + * Callback to call after sending the message. + * Only populated if entryType is MESSAGE and the call is in the COMMITTED + * state. + */ + callback?: WriteCallback; +} + +const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts'; + +export class RetryingCall implements Call { + private state: RetryingCallState; + private listener: InterceptingListener | null = null; + private initialMetadata: Metadata | null = null; + private underlyingCalls: UnderlyingCall[] = []; + private writeBuffer: WriteBufferEntry[] = []; + private transparentRetryUsed: boolean = false; + /** + * Number of attempts so far + */ + private attempts: number = 0; + private hedgingTimer: NodeJS.Timer | null = null; + private committedCallIndex: number | null = null; + private initialRetryBackoffSec = 0; + private nextRetryBackoffSec = 0; + constructor( + private readonly channel: InternalChannel, + private readonly callConfig: CallConfig, + private readonly methodName: string, + private readonly host: string, + private readonly credentials: CallCredentials, + private readonly deadline: Deadline, + private readonly callNumber: number, + private readonly bufferTracker: MessageBufferTracker, + private readonly retryThrottler?: RetryThrottler + ) { + if (callConfig.methodConfig.retryPolicy) { + this.state = 'RETRY'; + const retryPolicy = callConfig.methodConfig.retryPolicy; + this.nextRetryBackoffSec = this.initialRetryBackoffSec = Number(retryPolicy.initialBackoff.substring(0, retryPolicy.initialBackoff.length - 1)); + } else if (callConfig.methodConfig.hedgingPolicy) { + this.state = 'HEDGING'; + } else { + this.state = 'TRANSPARENT_ONLY'; + } + } + getCallNumber(): number { + return this.callNumber; + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callNumber + '] ' + text + ); + } + + private reportStatus(statusObject: StatusObject) { + this.trace('ended with status: code=' + statusObject.code + ' details="' + statusObject.details + '"'); + process.nextTick(() => { + this.listener?.onReceiveStatus(statusObject); + }); + } + + cancelWithStatus(status: Status, details: string): void { + this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.reportStatus({code: status, details, metadata: new Metadata()}); + for (const {call} of this.underlyingCalls) { + call.cancelWithStatus(status, details); + } + } + getPeer(): string { + if (this.committedCallIndex !== null) { + return this.underlyingCalls[this.committedCallIndex].call.getPeer(); + } else { + return 'unknown'; + } + } + + private commitCall(index: number) { + if (this.state === 'COMMITTED') { + return; + } + if (this.underlyingCalls[index].state === 'COMPLETED') { + return; + } + this.trace('Committing call [' + this.underlyingCalls[index].call.getCallNumber() + '] at index ' + index); + this.state = 'COMMITTED'; + this.committedCallIndex = index; + for (let i = 0; i < this.underlyingCalls.length; i++) { + if (i === index) { + continue; + } + if (this.underlyingCalls[i].state === 'COMPLETED') { + continue; + } + this.underlyingCalls[i].state = 'COMPLETED'; + this.underlyingCalls[i].call.cancelWithStatus(Status.CANCELLED, 'Discarded in favor of other hedged attempt'); + } + for (let messageIndex = 0; messageIndex < this.underlyingCalls[index].nextMessageToSend - 1; messageIndex += 1) { + const bufferEntry = this.writeBuffer[messageIndex]; + if (bufferEntry.entryType === 'MESSAGE') { + this.bufferTracker.free(bufferEntry.message!.message.length, this.callNumber); + this.writeBuffer[messageIndex] = { + entryType: 'FREED' + }; + } + } + } + + private commitCallWithMostMessages() { + let mostMessages = -1; + let callWithMostMessages = -1; + for (const [index, childCall] of this.underlyingCalls.entries()) { + if (childCall.nextMessageToSend > mostMessages) { + mostMessages = childCall.nextMessageToSend; + callWithMostMessages = index; + } + } + this.commitCall(callWithMostMessages); + } + + private isStatusCodeInList(list: (Status | string)[], code: Status) { + return list.some((value => value === code || value.toString().toLowerCase() === Status[code].toLowerCase())); + } + + private getNextRetryBackoffMs() { + const retryPolicy = this.callConfig?.methodConfig.retryPolicy; + if (!retryPolicy) { + return 0; + } + const nextBackoffMs = Math.random() * this.nextRetryBackoffSec * 1000; + const maxBackoffSec = Number(retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1)); + this.nextRetryBackoffSec = Math.min(this.nextRetryBackoffSec * retryPolicy.backoffMultiplier, maxBackoffSec); + return nextBackoffMs + } + + private maybeRetryCall(pushback: number | null, callback: (retried: boolean) => void) { + if (this.state !== 'RETRY') { + callback(false); + return; + } + const retryPolicy = this.callConfig!.methodConfig.retryPolicy!; + if (this.attempts >= retryPolicy.maxAttempts) { + callback(false); + return; + } + let retryDelayMs: number; + if (pushback === null) { + retryDelayMs = this.getNextRetryBackoffMs(); + } else if (pushback < 0) { + this.state = 'TRANSPARENT_ONLY'; + callback(false); + return; + } else { + retryDelayMs = pushback; + this.nextRetryBackoffSec = this.initialRetryBackoffSec; + } + setTimeout(() => { + if (this.state !== 'RETRY') { + callback(false); + return; + } + if (this.retryThrottler?.canRetryCall() ?? true) { + callback(true); + this.attempts += 1; + this.startNewAttempt(); + } + }, retryDelayMs); + } + + private countActiveCalls(): number { + let count = 0; + for (const call of this.underlyingCalls) { + if (call?.state === 'ACTIVE') { + count += 1; + } + } + return count; + } + + private handleProcessedStatus(status: StatusObject, callIndex: number, pushback: number | null) { + switch (this.state) { + case 'COMMITTED': + case 'TRANSPARENT_ONLY': + this.commitCall(callIndex); + this.reportStatus(status); + break; + case 'HEDGING': + if (this.isStatusCodeInList(this.callConfig!.methodConfig.hedgingPolicy!.nonFatalStatusCodes, status.code)) { + this.retryThrottler?.addCallFailed(); + let delayMs: number; + if (pushback === null) { + delayMs = 0; + } else if (pushback < 0) { + this.state = 'TRANSPARENT_ONLY'; + this.commitCall(callIndex); + this.reportStatus(status); + return; + } else { + delayMs = pushback; + } + setTimeout(() => { + this.maybeStartHedgingAttempt(); + // If after trying to start a call there are no active calls, this was the last one + if (this.countActiveCalls() === 0) { + this.commitCall(callIndex); + this.reportStatus(status); + } + }, delayMs); + } else { + this.commitCall(callIndex); + this.reportStatus(status); + } + break; + case 'RETRY': + if (this.isStatusCodeInList(this.callConfig!.methodConfig.retryPolicy!.retryableStatusCodes, status.code)) { + this.retryThrottler?.addCallFailed(); + this.maybeRetryCall(pushback, (retried) => { + if (!retried) { + this.commitCall(callIndex); + this.reportStatus(status); + } + }); + } else { + this.commitCall(callIndex); + this.reportStatus(status); + } + break; + } + } + + private getPushback(metadata: Metadata): number | null { + const mdValue = metadata.get('grpc-retry-pushback-ms'); + if (mdValue.length === 0) { + return null; + } + try { + return parseInt(mdValue[0] as string); + } catch (e) { + return -1; + } + } + + private handleChildStatus(status: StatusObjectWithProgress, callIndex: number) { + if (this.underlyingCalls[callIndex].state === 'COMPLETED') { + return; + } + this.underlyingCalls[callIndex].state = 'COMPLETED'; + if (status.code === Status.OK) { + this.retryThrottler?.addCallSucceeded(); + this.commitCall(callIndex); + this.reportStatus(status); + return; + } + if (this.state === 'COMMITTED') { + this.reportStatus(status); + return; + } + const pushback = this.getPushback(status.metadata); + switch (status.progress) { + case 'NOT_STARTED': + // RPC never leaves the client, always safe to retry + this.startNewAttempt(); + break; + case 'REFUSED': + // RPC reaches the server library, but not the server application logic + if (this.transparentRetryUsed) { + this.handleProcessedStatus(status, callIndex, pushback); + } else { + this.transparentRetryUsed = true; + this.startNewAttempt(); + }; + break; + case 'DROP': + this.commitCall(callIndex); + this.reportStatus(status); + break; + case 'PROCESSED': + this.handleProcessedStatus(status, callIndex, pushback); + break; + } + } + + private maybeStartHedgingAttempt() { + if (this.state !== 'HEDGING') { + return; + } + if (!this.callConfig.methodConfig.hedgingPolicy) { + return; + } + const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy; + if (this.attempts >= hedgingPolicy.maxAttempts) { + return; + } + this.attempts += 1; + this.startNewAttempt(); + this.maybeStartHedgingTimer(); + } + + private maybeStartHedgingTimer() { + if (this.hedgingTimer) { + clearTimeout(this.hedgingTimer); + } + if (this.state !== 'HEDGING') { + return; + } + if (!this.callConfig.methodConfig.hedgingPolicy) { + return; + } + const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy; + if (this.attempts >= hedgingPolicy.maxAttempts) { + return; + } + const hedgingDelayString = hedgingPolicy.hedgingDelay ?? '0s'; + const hedgingDelaySec = Number(hedgingDelayString.substring(0, hedgingDelayString.length - 1)); + this.hedgingTimer = setTimeout(() => { + this.maybeStartHedgingAttempt(); + }, hedgingDelaySec * 1000); + this.hedgingTimer.unref?.(); + } + + private startNewAttempt() { + const child = this.channel.createLoadBalancingCall(this.callConfig, this.methodName, this.host, this.credentials, this.deadline); + this.trace('Created child call [' + child.getCallNumber() + '] for attempt ' + this.attempts); + const index = this.underlyingCalls.length; + this.underlyingCalls.push({state: 'ACTIVE', call: child, nextMessageToSend: 0}); + const previousAttempts = this.attempts - 1; + const initialMetadata = this.initialMetadata!.clone(); + if (previousAttempts > 0) { + initialMetadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + } + let receivedMetadata = false; + child.start(initialMetadata, { + onReceiveMetadata: metadata => { + this.commitCall(index); + receivedMetadata = true; + if (previousAttempts > 0) { + metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + } + if (this.underlyingCalls[index].state === 'ACTIVE') { + this.listener!.onReceiveMetadata(metadata); + } + }, + onReceiveMessage: message => { + this.commitCall(index); + if (this.underlyingCalls[index].state === 'ACTIVE') { + this.listener!.onReceiveMessage(message); + } + }, + onReceiveStatus: status => { + if (!receivedMetadata && previousAttempts > 0) { + status.metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + } + this.commitCall(index); + this.handleChildStatus(status, index); + } + }) + } + + start(metadata: Metadata, listener: InterceptingListener): void { + this.trace('start called'); + this.listener = listener; + this.initialMetadata = metadata; + this.attempts += 1; + this.startNewAttempt(); + this.maybeStartHedgingTimer(); + } + + private sendNextChildMessage(childIndex: number) { + const childCall = this.underlyingCalls[childIndex]; + if (childCall.state === 'COMPLETED') { + return; + } + if (this.writeBuffer[childCall.nextMessageToSend]) { + const bufferEntry = this.writeBuffer[childCall.nextMessageToSend]; + switch (bufferEntry.entryType) { + case 'MESSAGE': + childCall.call.sendMessageWithContext({ + callback: (error) => { + // Ignore error + childCall.nextMessageToSend += 1; + this.sendNextChildMessage(childIndex); + } + }, bufferEntry.message!.message); + break; + case 'HALF_CLOSE': + childCall.nextMessageToSend += 1; + childCall.call.halfClose(); + break; + case 'FREED': + // Should not be possible + break; + } + } + } + + sendMessageWithContext(context: MessageContext, message: Buffer): void { + this.trace('write() called with message of length ' + message.length); + const writeObj: WriteObject = { + message, + flags: context.flags, + }; + const messageIndex = this.writeBuffer.length; + const bufferEntry: WriteBufferEntry = { + entryType: 'MESSAGE', + message: writeObj + }; + this.writeBuffer[messageIndex] = bufferEntry; + if (this.bufferTracker.allocate(message.length, this.callNumber)) { + context.callback?.(); + for (const [callIndex, call] of this.underlyingCalls.entries()) { + if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { + call.call.sendMessageWithContext({ + callback: (error) => { + // Ignore error + call.nextMessageToSend += 1; + this.sendNextChildMessage(callIndex); + } + }, message); + } + } + } else { + this.commitCallWithMostMessages(); + bufferEntry.callback = context.callback; + } + } + startRead(): void { + this.trace('startRead called'); + for (const underlyingCall of this.underlyingCalls) { + if (underlyingCall?.state === 'ACTIVE') { + underlyingCall.call.startRead(); + } + } + } + halfClose(): void { + this.trace('halfClose called'); + const halfCloseIndex = this.writeBuffer.length; + this.writeBuffer[halfCloseIndex] = { + entryType: 'HALF_CLOSE' + }; + for (const call of this.underlyingCalls) { + if (call?.state === 'ACTIVE' && call.nextMessageToSend === halfCloseIndex) { + call.nextMessageToSend += 1; + call.call.halfClose(); + } + } + } + setCredentials(newCredentials: CallCredentials): void { + throw new Error("Method not implemented."); + } + getMethod(): string { + return this.methodName; + } + getHost(): string { + return this.host; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index 12802dad8..a45355ace 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -86,7 +86,7 @@ export interface ServiceConfigCanaryConfig { * Recognizes a number with up to 9 digits after the decimal point, followed by * an "s", representing a number of seconds. */ -const TIMEOUT_REGEX = /^\d+(\.\d{1,9})?s$/; +const DURATION_REGEX = /^\d+(\.\d{1,9})?s$/; /** * Client language name used for determining whether this client matches a @@ -111,6 +111,75 @@ function validateName(obj: any): MethodConfigName { return result; } +function validateRetryPolicy(obj: any): RetryPolicy { + if (!('maxAttempts' in obj) || !Number.isInteger(obj.maxAttempts) || obj.maxAttempts < 2) { + throw new Error('Invalid method config retry policy: maxAttempts must be an integer at least 2'); + } + if (!('initialBackoff' in obj) || typeof obj.initialBackoff !== 'string' || !DURATION_REGEX.test(obj.initialBackoff)) { + throw new Error('Invalid method config retry policy: initialBackoff must be a string consisting of a positive integer followed by s'); + } + if (!('maxBackoff' in obj) || typeof obj.maxBackoff !== 'string' || !DURATION_REGEX.test(obj.maxBackoff)) { + throw new Error('Invalid method config retry policy: maxBackoff must be a string consisting of a positive integer followed by s'); + } + if (!('backoffMultiplier' in obj) || typeof obj.backoffMultiplier !== 'number' || obj.backoffMultiplier <= 0) { + throw new Error('Invalid method config retry policy: backoffMultiplier must be a number greater than 0'); + } + if (('retryableStatusCodes' in obj) && Array.isArray(obj.retryableStatusCodes)) { + for (const value of obj.retryableStatusCodes) { + if (typeof value === 'number') { + if (!Object.values(Status).includes(value)) { + throw new Error('Invlid method config retry policy: retryableStatusCodes value not in status code range'); + } + } else if (typeof value === 'string') { + if (!Object.values(Status).includes(value.toUpperCase())) { + throw new Error('Invlid method config retry policy: retryableStatusCodes value not a status code name'); + } + } else { + throw new Error('Invlid method config retry policy: retryableStatusCodes value must be a string or number'); + } + } + } + return { + maxAttempts: obj.maxAttempts, + initialBackoff: obj.initialBackoff, + maxBackoff: obj.maxBackoff, + backoffMultiplier: obj.backoffMultiplier, + retryableStatusCodes: obj.retryableStatusCodes + }; +} + +function validateHedgingPolicy(obj: any): HedgingPolicy { + if (!('maxAttempts' in obj) || !Number.isInteger(obj.maxAttempts) || obj.maxAttempts < 2) { + throw new Error('Invalid method config hedging policy: maxAttempts must be an integer at least 2'); + } + if (('hedgingDelay' in obj) && (typeof obj.hedgingDelay !== 'string' || !DURATION_REGEX.test(obj.hedgingDelay))) { + throw new Error('Invalid method config hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s'); + } + if (('nonFatalStatusCodes' in obj) && Array.isArray(obj.nonFatalStatusCodes)) { + for (const value of obj.nonFatalStatusCodes) { + if (typeof value === 'number') { + if (!Object.values(Status).includes(value)) { + throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value not in status code range'); + } + } else if (typeof value === 'string') { + if (!Object.values(Status).includes(value.toUpperCase())) { + throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value not a status code name'); + } + } else { + throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value must be a string or number'); + } + } + } + const result: HedgingPolicy = { + maxAttempts: obj.maxAttempts, + nonFatalStatusCodes: obj.nonFatalStatusCodes + } + if (obj.hedgingDelay) { + result.hedgingDelay = obj.hedgingDelay; + } + return result; +} + function validateMethodConfig(obj: any): MethodConfig { const result: MethodConfig = { name: [], @@ -144,7 +213,7 @@ function validateMethodConfig(obj: any): MethodConfig { result.timeout = obj.timeout; } else if ( typeof obj.timeout === 'string' && - TIMEOUT_REGEX.test(obj.timeout) + DURATION_REGEX.test(obj.timeout) ) { const timeoutParts = obj.timeout .substring(0, obj.timeout.length - 1) @@ -169,9 +238,31 @@ function validateMethodConfig(obj: any): MethodConfig { } result.maxResponseBytes = obj.maxResponseBytes; } + if ('retryPolicy' in obj) { + if ('hedgingPolicy' in obj) { + throw new Error('Invalid method config: retryPolicy and hedgingPolicy cannot both be specified'); + } else { + result.retryPolicy = validateRetryPolicy(obj.retryPolicy); + } + } else if ('hedgingPolicy' in obj) { + result.hedgingPolicy = validateHedgingPolicy(obj.hedgingPolicy); + } return result; } +export function validateRetryThrottling(obj: any): RetryThrottling { + if (!('maxTokens' in obj) || typeof obj.maxTokens !== 'number' || obj.maxTokens <=0 || obj.maxTokens > 1000) { + throw new Error('Invalid retryThrottling: maxTokens must be a number in (0, 1000]'); + } + if (!('tokenRatio' in obj) || typeof obj.tokenRatio !== 'number' || obj.tokenRatio <= 0) { + throw new Error('Invalid retryThrottling: tokenRatio must be a number greater than 0'); + } + return { + maxTokens: +(obj.maxTokens as number).toFixed(3), + tokenRatio: +(obj.tokenRatio as number).toFixed(3) + }; +} + export function validateServiceConfig(obj: any): ServiceConfig { const result: ServiceConfig = { loadBalancingConfig: [], diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index e2cd645af..2bc6fb0c7 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -75,6 +75,14 @@ export interface SubchannelCall { getCallNumber(): number; } +export interface StatusObjectWithRstCode extends StatusObject { + rstCode?: number; +} + +export interface SubchannelCallInterceptingListener extends InterceptingListener { + onReceiveStatus(status: StatusObjectWithRstCode): void; +} + export class Http2SubchannelCall implements SubchannelCall { private decoder = new StreamDecoder(); @@ -103,7 +111,7 @@ export class Http2SubchannelCall implements SubchannelCall { constructor( private readonly http2Stream: http2.ClientHttp2Stream, private readonly callStatsTracker: SubchannelCallStatsTracker, - private readonly listener: InterceptingListener, + private readonly listener: SubchannelCallInterceptingListener, private readonly subchannel: Subchannel, private readonly callId: number ) { @@ -257,7 +265,7 @@ export class Http2SubchannelCall implements SubchannelCall { // This is OK, because status codes emitted here correspond to more // catastrophic issues that prevent us from receiving trailers in the // first place. - this.endCall({ code, details, metadata: new Metadata() }); + this.endCall({ code, details, metadata: new Metadata(), rstCode: http2Stream.rstCode }); }); }); http2Stream.on('error', (err: SystemError) => { @@ -329,7 +337,7 @@ export class Http2SubchannelCall implements SubchannelCall { * Subsequent calls are no-ops. * @param status The status of the call. */ - private endCall(status: StatusObject): void { + private endCall(status: StatusObjectWithRstCode): void { /* If the status is OK and a new status comes in (e.g. from a * deserialization failure), that new status takes priority */ if (this.finalStatus === null || this.finalStatus.code === Status.OK) { diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 104c36c24..0d9773b30 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -36,7 +36,7 @@ import { } from './subchannel-address'; import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, SocketInfo, SocketRef, unregisterChannelzRef, registerChannelzSocket, TlsInfo } from './channelz'; import { ConnectivityStateListener } from './subchannel-interface'; -import { Http2SubchannelCall } from './subchannel-call'; +import { Http2SubchannelCall, SubchannelCallInterceptingListener } from './subchannel-call'; import { getNextCallNumber } from './call-number'; import { SubchannelCall } from './subchannel-call'; import { InterceptingListener, StatusObject } from './call-interface'; @@ -815,7 +815,7 @@ export class Subchannel { return false; } - createCall(metadata: Metadata, host: string, method: string, listener: InterceptingListener): SubchannelCall { + createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener): SubchannelCall { const headers = metadata.toHttp2Headers(); headers[HTTP2_HEADER_AUTHORITY] = host; headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; From e840d1f855f5320bd1c2ee2a3642e5d92b9bafbb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 Oct 2022 15:47:16 -0700 Subject: [PATCH 354/694] grpc-js: Bump to 1.7.3 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 087060154..55ddb10aa 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.7.2", + "version": "1.7.3", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From bcf4ce2b401b7a1d399b19b1fd8d9fe89fc14ebe Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 21 Oct 2022 15:21:19 -0700 Subject: [PATCH 355/694] grpc-js-xds: Log stats periodically in interop tests --- packages/grpc-js-xds/interop/xds-interop-client.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 029525a45..59c505b46 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -310,6 +310,9 @@ function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpc makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker); } }, 1000/qps); + setInterval(() => { + console.log(`Accumulated stats: ${JSON.stringify(accumulatedStats, undefined, 2)}`); + }, 1000); } const callTypeEnumMap = { From 124712979b3b0af4a83aa6ee88d634d63a2bea87 Mon Sep 17 00:00:00 2001 From: natiz Date: Wed, 26 Oct 2022 12:12:09 +0300 Subject: [PATCH 356/694] grpc-tools: Update protoc to v3.19.1 last working version of protoc that includes javascript --- packages/grpc-tools/deps/protobuf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/deps/protobuf b/packages/grpc-tools/deps/protobuf index 6aa539bf0..7c40b2df1 160000 --- a/packages/grpc-tools/deps/protobuf +++ b/packages/grpc-tools/deps/protobuf @@ -1 +1 @@ -Subproject commit 6aa539bf0195f188ff86efe6fb8bfa2b676cdd46 +Subproject commit 7c40b2df1fdf6f414c1c18c789715a9c948a0725 From e7144897d06ce910f684c729c96ce56db9c9c3d9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 1 Nov 2022 09:26:29 -0700 Subject: [PATCH 357/694] grpc-js: Restart deadline timer after getting timeout from service config --- packages/grpc-js/src/resolving-call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index 76a7bd208..96592d71d 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -145,6 +145,7 @@ export class ResolvingCall implements Call { config.methodConfig.timeout.nanos / 1_000_000 ); this.deadline = minDeadline(this.deadline, configDeadline); + this.runDeadlineTimer(); } this.filterStackFactory.push(config.dynamicFilterFactories); From b3bcff1d7b9e30b664390432fcf9758f03842dd9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 1 Nov 2022 10:39:06 -0700 Subject: [PATCH 358/694] grpc-js: Pin @types/lodash to fix broken build --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 087060154..fa0df34e0 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@types/gulp": "^4.0.6", "@types/gulp-mocha": "0.0.32", - "@types/lodash": "^4.14.108", + "@types/lodash": "4.14.186", "@types/mocha": "^5.2.6", "@types/ncp": "^2.0.1", "@types/pify": "^3.0.2", From 8f33dc72466b02a6ef5bdd8784d03d8c3e5e16f4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 4 Nov 2022 11:21:24 -0700 Subject: [PATCH 359/694] grpc-js: Update to newest typescript compiler --- packages/grpc-js/package.json | 6 ++-- packages/grpc-js/src/call-credentials.ts | 4 +++ packages/grpc-js/src/client-interceptors.ts | 5 +-- packages/grpc-js/src/error.ts | 37 +++++++++++++++++++ packages/grpc-js/src/metadata.ts | 3 +- packages/grpc-js/src/server-call.ts | 39 +++++++++++---------- packages/grpc-js/src/server.ts | 11 ++++-- 7 files changed, 78 insertions(+), 27 deletions(-) create mode 100644 packages/grpc-js/src/error.ts diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index fff572fa9..cd1c74bb5 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -17,14 +17,14 @@ "devDependencies": { "@types/gulp": "^4.0.6", "@types/gulp-mocha": "0.0.32", - "@types/lodash": "4.14.186", + "@types/lodash": "^4.14.186", "@types/mocha": "^5.2.6", "@types/ncp": "^2.0.1", "@types/pify": "^3.0.2", "@types/semver": "^7.3.9", "clang-format": "^1.0.55", "execa": "^2.0.3", - "gts": "^2.0.0", + "gts": "^3.1.1", "gulp": "^4.0.2", "gulp-mocha": "^6.0.0", "lodash": "^4.17.4", @@ -35,7 +35,7 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "ts-node": "^8.3.0", - "typescript": "^3.7.2" + "typescript": "^4.8.4" }, "contributors": [ { diff --git a/packages/grpc-js/src/call-credentials.ts b/packages/grpc-js/src/call-credentials.ts index bbc88a895..b98624ee2 100644 --- a/packages/grpc-js/src/call-credentials.ts +++ b/packages/grpc-js/src/call-credentials.ts @@ -115,6 +115,10 @@ export abstract class CallCredentials { reject(err); return; } + if (!headers) { + reject(new Error('Headers not set by metadata plugin')); + return; + } resolve(headers); } ); diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index d9c88f448..d95828550 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -34,6 +34,7 @@ import { Channel } from './channel'; import { CallOptions } from './client'; import { CallCredentials } from './call-credentials'; import { ClientMethodDefinition } from './make-client'; +import { getErrorMessage } from './error'; /** * Error class associated with passing both interceptors and interceptor @@ -374,7 +375,7 @@ class BaseInterceptingCall implements InterceptingCallInterface { } catch (e) { this.call.cancelWithStatus( Status.INTERNAL, - `Request message serialization failure: ${e.message}` + `Request message serialization failure: ${getErrorMessage(e)}` ); return; } @@ -401,7 +402,7 @@ class BaseInterceptingCall implements InterceptingCallInterface { } catch (e) { readError = { code: Status.INTERNAL, - details: `Response message parsing error: ${e.message}`, + details: `Response message parsing error: ${getErrorMessage(e)}`, metadata: new Metadata(), }; this.call.cancelWithStatus(readError.code, readError.details); diff --git a/packages/grpc-js/src/error.ts b/packages/grpc-js/src/error.ts new file mode 100644 index 000000000..b973128a7 --- /dev/null +++ b/packages/grpc-js/src/error.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 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. + * + */ + +export function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } else { + return String(error); + } +} + +export function getErrorCode(error: unknown): number | null { + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + typeof (error as Record).code === 'number' + ) { + return (error as Record).code; + } else { + return null; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts index 376cb491a..0dddd9465 100644 --- a/packages/grpc-js/src/metadata.ts +++ b/packages/grpc-js/src/metadata.ts @@ -18,6 +18,7 @@ import * as http2 from 'http2'; import { log } from './logging'; import { LogVerbosity } from './constants'; +import { getErrorMessage } from './error'; const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/; const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/; @@ -285,7 +286,7 @@ export class Metadata { } } } catch (error) { - const message = `Failed to add metadata entry ${key}: ${values}. ${error.message}. For more information see https://github.com/grpc/grpc-node/issues/1173`; + const message = `Failed to add metadata entry ${key}: ${values}. ${getErrorMessage(error)}. For more information see https://github.com/grpc/grpc-node/issues/1173`; log(LogVerbosity.ERROR, message); } } diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index b27bb1b20..a000b8ffe 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -35,6 +35,7 @@ import { ChannelOptions } from './channel-options'; import * as logging from './logging'; import { StatusObject, PartialStatusObject } from './call-interface'; import { Deadline } from './deadline'; +import { getErrorCode, getErrorMessage } from './error'; const TRACER_NAME = 'server_call'; const unzip = promisify(zlib.unzip); @@ -232,8 +233,10 @@ export class ServerWritableStreamImpl return; } } catch (err) { - err.code = Status.INTERNAL; - this.emit('error', err); + this.emit('error', { + details: getErrorMessage(err), + code: Status.INTERNAL + }); } callback(); @@ -630,8 +633,10 @@ export class Http2ServerCallStream< try { next(null, this.deserializeMessage(buffer)); } catch (err) { - err.code = Status.INTERNAL; - next(err); + next({ + details: getErrorMessage(err), + code: Status.INTERNAL + }); } } @@ -679,8 +684,10 @@ export class Http2ServerCallStream< this.write(response); this.sendStatus({ code: Status.OK, details: 'OK', metadata }); } catch (err) { - err.code = Status.INTERNAL; - this.sendError(err); + this.sendError({ + details: getErrorMessage(err), + code: Status.INTERNAL + }); } } @@ -909,21 +916,15 @@ export class Http2ServerCallStream< } catch (error) { // Ignore any remaining messages when errors occur. this.bufferedMessages.length = 0; - - if ( - !( - 'code' in error && - typeof error.code === 'number' && - Number.isInteger(error.code) && - error.code >= Status.OK && - error.code <= Status.UNAUTHENTICATED - ) - ) { - // The error code is not a valid gRPC code so its being overwritten. - error.code = Status.INTERNAL; + let code = getErrorCode(error); + if (code === null || code < Status.OK || code > Status.UNAUTHENTICATED) { + code = Status.INTERNAL } - readable.emit('error', error); + readable.emit('error', { + details: getErrorMessage(error), + code: code + }); } this.isPushPending = false; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 2e89f459b..9f7321408 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -61,6 +61,7 @@ import { import { parseUri } from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; +import { getErrorCode, getErrorMessage } from './error'; const { HTTP2_HEADER_PATH @@ -814,7 +815,10 @@ export class Server { try { handler = this._retrieveHandler(headers) } catch (err) { - this._respondWithError(err, stream, channelzSessionInfo) + this._respondWithError({ + details: getErrorMessage(err), + code: getErrorCode(err) ?? undefined + }, stream, channelzSessionInfo) return } @@ -866,7 +870,10 @@ export class Server { try { handler = this._retrieveHandler(headers) } catch (err) { - this._respondWithError(err, stream, null) + this._respondWithError({ + details: getErrorMessage(err), + code: getErrorCode(err) ?? undefined + }, stream, null) return } From f392d4d8c5b3d1992f571b210e4f50f62c973e46 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 4 Nov 2022 15:15:54 -0700 Subject: [PATCH 360/694] grpc-js-xds: interop client: correct for setInterval variance --- .../grpc-js-xds/interop/xds-interop-client.ts | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 59c505b46..72bbec61d 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -180,6 +180,27 @@ class CallStatsTracker { } } +class RecentTimestampList { + private timeList: bigint[] = []; + private nextIndex = 0; + + constructor(private readonly size: number) {} + + isFull() { + return this.timeList.length === this.size; + } + + insertTimestamp(timestamp: bigint) { + this.timeList[this.nextIndex] = timestamp; + this.nextIndex = (this.nextIndex + 1) % this.size; + } + + getSpan(): bigint { + const lastIndex = (this.nextIndex + this.size - 1) % this.size; + return this.timeList[lastIndex] - this.timeList[this.nextIndex]; + } +} + type CallType = 'EmptyCall' | 'UnaryCall'; interface ClientConfiguration { @@ -246,7 +267,13 @@ const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = { EmptyCall: {} } -function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { +/** + * Timestamps output by process.hrtime.bigint() are a bigint number of + * nanoseconds. This is the representation of 1 second in that context. + */ +const TIMESTAMP_ONE_SECOND = BigInt(1e9); + +function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker, callStartTimestamps: RecentTimestampList) { const callEnumName = callTypeEnumMapReverse[type]; addAccumulatedCallStarted(callEnumName); const notifier = callStatsTracker.startCall(); @@ -254,19 +281,20 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail let hostname: string | null = null; let completed: boolean = false; let completedWithError: boolean = false; - const startTime = process.hrtime(); + const startTime = process.hrtime.bigint(); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + currentConfig.timeoutSec); const callback = (error: grpc.ServiceError | undefined, value: Empty__Output | undefined) => { const statusCode = error?.code ?? grpc.status.OK; - const duration = process.hrtime(startTime); + const duration = process.hrtime.bigint() - startTime; + const durationSeconds = Number(duration / TIMESTAMP_ONE_SECOND) | 0; if (!callTimeHistogram[type][statusCode]) { callTimeHistogram[type][statusCode] = []; } - if (callTimeHistogram[type][statusCode][duration[0]]) { - callTimeHistogram[type][statusCode][duration[0]] += 1; + if (callTimeHistogram[type][statusCode][durationSeconds]) { + callTimeHistogram[type][statusCode][durationSeconds] += 1; } else { - callTimeHistogram[type][statusCode][duration[0]] = 1; + callTimeHistogram[type][statusCode][durationSeconds] = 1; } addAccumulatedCallEnded(callEnumName, statusCode); if (error) { @@ -301,13 +329,28 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail } } }); - + /* callStartTimestamps tracks the last N timestamps of started calls, where N + * is the target QPS. If the measured span of time between the first and last + * of those N calls is greater than 1 second, we make another call + * ~immediately to correct for that. */ + callStartTimestamps.insertTimestamp(startTime); + if (callStartTimestamps.isFull()) { + if (callStartTimestamps.getSpan() > TIMESTAMP_ONE_SECOND) { + setImmediate(() => { + makeSingleRequest(client, type, failOnFailedRpcs, callStatsTracker, callStartTimestamps); + }); + } + } } function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { + const callStartTimestampsTrackers: {[callType: string]: RecentTimestampList} = {}; + for (const callType of currentConfig.callTypes) { + callStartTimestampsTrackers[callType] = new RecentTimestampList(qps); + } setInterval(() => { for (const callType of currentConfig.callTypes) { - makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker); + makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker, callStartTimestampsTrackers[callType]); } }, 1000/qps); setInterval(() => { From 26c8c379853eeba3f3c5c1a90064bc01fa47fbd3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 17 Oct 2022 11:32:22 -0700 Subject: [PATCH 361/694] grpc-js: Handle filters in ResolvingCall instead of LoadBalancingCall --- packages/grpc-js/src/internal-channel.ts | 2 +- packages/grpc-js/src/load-balancing-call.ts | 59 +++---------- packages/grpc-js/src/resolving-call.ts | 96 +++++++++++++++------ 3 files changed, 83 insertions(+), 74 deletions(-) diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 2a775e7d8..16f4b9832 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -402,7 +402,7 @@ export class InternalChannel { method + '"' ); - return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, this.filterStackFactory, callNumber); + return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, callNumber); } createInnerCall( diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 73c1fa9fd..6faa15592 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -41,14 +41,11 @@ export interface StatusObjectWithProgress extends StatusObject { export class LoadBalancingCall implements Call { private child: SubchannelCall | null = null; private readPending = false; - private writeFilterPending = false; private pendingMessage: {context: MessageContext, message: Buffer} | null = null; private pendingHalfClose = false; - private readFilterPending = false; private pendingChildStatus: StatusObject | null = null; private ended = false; private serviceUrl: string; - private filterStack: FilterStack; private metadata: Metadata | null = null; private listener: InterceptingListener | null = null; private onCallEnded: ((statusCode: Status) => void) | null = null; @@ -59,11 +56,8 @@ export class LoadBalancingCall implements Call { private readonly host : string, private readonly credentials: CallCredentials, private readonly deadline: Deadline, - filterStackFactory: FilterStackFactory, private readonly callNumber: number ) { - this.filterStack = filterStackFactory.createFilter(); - const splitPath: string[] = this.methodName.split('/'); let serviceName = ''; /* The standard path format is "/{serviceName}/{methodName}", so if we split @@ -90,8 +84,7 @@ export class LoadBalancingCall implements Call { if (!this.ended) { this.ended = true; this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); - const filteredStatus = this.filterStack.receiveTrailers(status); - const finalStatus = {...filteredStatus, progress}; + const finalStatus = {...status, progress}; this.listener?.onReceiveStatus(finalStatus); this.onCallEnded?.(finalStatus.code); } @@ -152,26 +145,13 @@ export class LoadBalancingCall implements Call { try { this.child = pickResult.subchannel!.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, { onReceiveMetadata: metadata => { - this.listener!.onReceiveMetadata(this.filterStack.receiveMetadata(metadata)); + this.listener!.onReceiveMetadata(metadata); }, onReceiveMessage: message => { - this.readFilterPending = true; - this.filterStack.receiveMessage(message).then(filteredMesssage => { - this.readFilterPending = false; - this.listener!.onReceiveMessage(filteredMesssage); - if (this.pendingChildStatus) { - this.outputStatus(this.pendingChildStatus, 'PROCESSED'); - } - }, (status: StatusObject) => { - this.cancelWithStatus(status.code, status.details); - }); + this.listener!.onReceiveMessage(message); }, onReceiveStatus: status => { - if (this.readFilterPending) { - this.pendingChildStatus = status; - } else { - this.outputStatus(status, 'PROCESSED'); - } + this.outputStatus(status, 'PROCESSED'); } }); } catch (error) { @@ -201,7 +181,7 @@ export class LoadBalancingCall implements Call { if (this.pendingMessage) { this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); } - if (this.pendingHalfClose && !this.writeFilterPending) { + if (this.pendingHalfClose) { this.child.halfClose(); } }, (error: Error & { code: number }) => { @@ -249,29 +229,16 @@ export class LoadBalancingCall implements Call { start(metadata: Metadata, listener: InterceptingListener): void { this.trace('start called'); this.listener = listener; - this.filterStack.sendMetadata(Promise.resolve(metadata)).then(filteredMetadata => { - this.metadata = filteredMetadata; - this.doPick(); - }, (status: StatusObject) => { - this.outputStatus(status, 'PROCESSED'); - }); + this.metadata = metadata; + this.doPick(); } sendMessageWithContext(context: MessageContext, message: Buffer): void { this.trace('write() called with message of length ' + message.length); - this.writeFilterPending = true; - this.filterStack.sendMessage(Promise.resolve({message: message, flags: context.flags})).then((filteredMessage) => { - this.writeFilterPending = false; - if (this.child) { - this.child.sendMessageWithContext(context, filteredMessage.message); - if (this.pendingHalfClose) { - this.child.halfClose(); - } - } else { - this.pendingMessage = {context, message: filteredMessage.message}; - } - }, (status: StatusObject) => { - this.cancelWithStatus(status.code, status.details); - }) + if (this.child) { + this.child.sendMessageWithContext(context, message); + } else { + this.pendingMessage = {context, message}; + } } startRead(): void { this.trace('startRead called'); @@ -283,7 +250,7 @@ export class LoadBalancingCall implements Call { } halfClose(): void { this.trace('halfClose called'); - if (this.child && !this.writeFilterPending) { + if (this.child) { this.child.halfClose(); } else { this.pendingHalfClose = true; diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index 96592d71d..fe29a4f7a 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -19,7 +19,7 @@ import { CallCredentials } from "./call-credentials"; import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; import { LogVerbosity, Propagate, Status } from "./constants"; import { Deadline, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; -import { FilterStackFactory } from "./filter-stack"; +import { FilterStack, FilterStackFactory } from "./filter-stack"; import { InternalChannel } from "./internal-channel"; import { Metadata } from "./metadata"; import * as logging from './logging'; @@ -33,12 +33,16 @@ export class ResolvingCall implements Call { private pendingMessage: {context: MessageContext, message: Buffer} | null = null; private pendingHalfClose = false; private ended = false; + private readFilterPending = false; + private writeFilterPending = false; + private pendingChildStatus: StatusObject | null = null; private metadata: Metadata | null = null; private listener: InterceptingListener | null = null; private deadline: Deadline; private host: string; private statusWatchers: ((status: StatusObject) => void)[] = []; private deadlineTimer: NodeJS.Timer = setTimeout(() => {}, 0); + private filterStack: FilterStack | null = null; constructor( private readonly channel: InternalChannel, @@ -96,14 +100,35 @@ export class ResolvingCall implements Call { private outputStatus(status: StatusObject) { if (!this.ended) { this.ended = true; - this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); - this.statusWatchers.forEach(watcher => watcher(status)); + if (!this.filterStack) { + this.filterStack = this.filterStackFactory.createFilter(); + } + const filteredStatus = this.filterStack.receiveTrailers(status); + this.trace('ended with status: code=' + filteredStatus.code + ' details="' + filteredStatus.details + '"'); + this.statusWatchers.forEach(watcher => watcher(filteredStatus)); process.nextTick(() => { - this.listener?.onReceiveStatus(status); + this.listener?.onReceiveStatus(filteredStatus); }); } } + private sendMessageOnChild(context: MessageContext, message: Buffer): void { + if (!this.child) { + throw new Error('sendMessageonChild called with child not populated'); + } + const child = this.child; + this.writeFilterPending = true; + this.filterStack!.sendMessage(Promise.resolve({message: message, flags: context.flags})).then((filteredMessage) => { + this.writeFilterPending = false; + child.sendMessageWithContext(context, filteredMessage.message); + if (this.pendingHalfClose) { + child.halfClose(); + } + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }); + } + getConfig(): void { if (this.ended) { return; @@ -149,29 +174,46 @@ export class ResolvingCall implements Call { } this.filterStackFactory.push(config.dynamicFilterFactories); - - this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); - this.child.start(this.metadata, { - onReceiveMetadata: metadata => { - this.listener!.onReceiveMetadata(metadata); - }, - onReceiveMessage: message => { - this.listener!.onReceiveMessage(message); - }, - onReceiveStatus: status => { - this.outputStatus(status); + this.filterStack = this.filterStackFactory.createFilter(); + this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then(filteredMetadata => { + this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); + this.child.start(filteredMetadata, { + onReceiveMetadata: metadata => { + this.listener!.onReceiveMetadata(this.filterStack!.receiveMetadata(metadata)); + }, + onReceiveMessage: message => { + this.readFilterPending = true; + this.filterStack!.receiveMessage(message).then(filteredMesssage => { + this.readFilterPending = false; + this.listener!.onReceiveMessage(filteredMesssage); + if (this.pendingChildStatus) { + this.outputStatus(this.pendingChildStatus); + } + }, (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + }); + }, + onReceiveStatus: status => { + if (this.readFilterPending) { + this.pendingChildStatus = status; + } else { + this.outputStatus(status); + } + } + }); + if (this.readPending) { + this.child.startRead(); } - }); - if (this.readPending) { - this.child.startRead(); - } - if (this.pendingMessage) { - this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); - } - if (this.pendingHalfClose) { - this.child.halfClose(); - } + if (this.pendingMessage) { + this.sendMessageOnChild(this.pendingMessage.context, this.pendingMessage.message); + } else if (this.pendingHalfClose) { + this.child.halfClose(); + } + }, (status: StatusObject) => { + this.outputStatus(status); + }) } + reportResolverError(status: StatusObject) { if (this.metadata?.getOptions().waitForReady) { this.channel.queueCallForConfig(this); @@ -196,7 +238,7 @@ export class ResolvingCall implements Call { sendMessageWithContext(context: MessageContext, message: Buffer): void { this.trace('write() called with message of length ' + message.length); if (this.child) { - this.child.sendMessageWithContext(context, message); + this.sendMessageOnChild(context, message); } else { this.pendingMessage = {context, message}; } @@ -211,7 +253,7 @@ export class ResolvingCall implements Call { } halfClose(): void { this.trace('halfClose called'); - if (this.child) { + if (this.child && !this.writeFilterPending) { this.child.halfClose(); } else { this.pendingHalfClose = true; From b4449083b905b4d4daa08fff93f8588c95730098 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 8 Nov 2022 12:40:22 -0800 Subject: [PATCH 362/694] grpc-js-xds: interop: output CPU profile logs in old framework tests --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 2df2ae42d..6b4987371 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -59,7 +59,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh --gcp_suffix=$(date '+%s') \ --verbose \ ${XDS_V3_OPT-} \ - --client_cmd="$(which node) --enable-source-maps grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ + --client_cmd="$(which node) --enable-source-maps --prof --logfile=grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ --stats_port={stats_port} \ --qps={qps} \ From 959f698fc4dfe257773157bca0c8f8a0e33de1da Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 8 Nov 2022 14:46:17 -0800 Subject: [PATCH 363/694] Use absolute path for logfile output Co-authored-by: Sergii Tkachenko --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 6b4987371..14cbed511 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -59,7 +59,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh --gcp_suffix=$(date '+%s') \ --verbose \ ${XDS_V3_OPT-} \ - --client_cmd="$(which node) --enable-source-maps --prof --logfile=grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ + --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ --stats_port={stats_port} \ --qps={qps} \ From f844ca30bb3bf908fc0fc3a021b52398a861b359 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 8 Nov 2022 15:23:20 -0800 Subject: [PATCH 364/694] grpc-js-xds: interop: mkdir artifact directory before running tests --- packages/grpc-js-xds/scripts/xds.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 14cbed511..f556b0e76 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -48,6 +48,8 @@ git clone -b master --single-branch --depth=1 https://github.com/grpc/grpc.git grpc/tools/run_tests/helper_scripts/prep_xds.sh +mkdir -p "${KOKORO_ARTIFACTS_DIR}/grpc/reports/prof.log" + GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds \ GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ From e8396a5542ada132abfafe770fa065ea7a5cb09d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 8 Nov 2022 15:47:09 -0800 Subject: [PATCH 365/694] Don't try to create the target file as a directory Co-authored-by: Sergii Tkachenko --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index f556b0e76..96348e6ba 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -48,7 +48,7 @@ git clone -b master --single-branch --depth=1 https://github.com/grpc/grpc.git grpc/tools/run_tests/helper_scripts/prep_xds.sh -mkdir -p "${KOKORO_ARTIFACTS_DIR}/grpc/reports/prof.log" +mkdir -p "${KOKORO_ARTIFACTS_DIR}/grpc/reports" GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds \ GRPC_NODE_VERBOSITY=DEBUG \ From 02c48f426dd2be7b5faf2442f9b100ddfcb8af3d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 9 Nov 2022 10:08:47 -0800 Subject: [PATCH 366/694] grpc-js-xds: interop: Fix target directory for profile log --- packages/grpc-js-xds/scripts/xds.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 96348e6ba..615b2a7c7 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -48,7 +48,7 @@ git clone -b master --single-branch --depth=1 https://github.com/grpc/grpc.git grpc/tools/run_tests/helper_scripts/prep_xds.sh -mkdir -p "${KOKORO_ARTIFACTS_DIR}/grpc/reports" +mkdir -p "${KOKORO_ARTIFACTS_DIR}/github/grpc/reports" GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds \ GRPC_NODE_VERBOSITY=DEBUG \ @@ -61,7 +61,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh --gcp_suffix=$(date '+%s') \ --verbose \ ${XDS_V3_OPT-} \ - --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ + --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/github/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ --stats_port={stats_port} \ --qps={qps} \ From a42d6b4f5c7351f700149af1e845ee4105e50e6f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 9 Nov 2022 14:53:19 -0800 Subject: [PATCH 367/694] grpc-js: Implement server connection management --- packages/grpc-js/src/channel-options.ts | 4 ++ packages/grpc-js/src/server.ts | 74 ++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index b7fc92fa9..c05253e9b 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -44,6 +44,8 @@ export interface ChannelOptions { 'grpc.default_compression_algorithm'?: CompressionAlgorithms; 'grpc.enable_channelz'?: number; 'grpc.dns_min_time_between_resolutions_ms'?: number; + 'grpc.max_connection_age_ms'?: number; + 'grpc.max_connection_age_grace_ms'?: number; 'grpc-node.max_session_memory'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; @@ -71,6 +73,8 @@ export const recognizedOptions = { 'grpc.enable_http_proxy': true, 'grpc.enable_channelz': true, 'grpc.dns_min_time_between_resolutions_ms': true, + 'grpc.max_connection_age_ms': true, + 'grpc.max_connection_age_grace_ms': true, 'grpc-node.max_session_memory': true, }; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 9f7321408..d19186a75 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -63,6 +63,10 @@ import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerCh import { CipherNameAndProtocol, TLSSocket } from 'tls'; import { getErrorCode, getErrorMessage } from './error'; +const UNLIMITED_CONNECTION_AGE_MS = ~(1<<31); +const KEEPALIVE_MAX_TIME_MS = ~(1<<31); +const KEEPALIVE_TIMEOUT_MS = 20000; + const { HTTP2_HEADER_PATH } = http2.constants @@ -161,6 +165,12 @@ export class Server { private listenerChildrenTracker = new ChannelzChildrenTracker(); private sessionChildrenTracker = new ChannelzChildrenTracker(); + private readonly maxConnectionAgeMs: number; + private readonly maxConnectionAgeGraceMs: number; + + private readonly keepaliveTimeMs: number; + private readonly keepaliveTimeoutMs: number; + constructor(options?: ChannelOptions) { this.options = options ?? {}; if (this.options['grpc.enable_channelz'] === 0) { @@ -170,7 +180,10 @@ export class Server { if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Server created'); } - + this.maxConnectionAgeMs = this.options['grpc.max_connection_age_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; + this.maxConnectionAgeGraceMs = this.options['grpc.max_connection_age_grace_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; + this.keepaliveTimeMs = this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS; + this.keepaliveTimeoutMs = this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; this.trace('Server constructed'); } @@ -970,12 +983,69 @@ export class Server { this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); this.sessionChildrenTracker.refChild(channelzRef); } + let connectionAgeTimer: NodeJS.Timer | null = null; + let connectionAgeGraceTimer: NodeJS.Timer | null = null; + let sessionClosedByServer = false; + if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { + // Apply a random jitter within a +/-10% range + const jitterMagnitude = this.maxConnectionAgeMs / 10; + const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude; + connectionAgeTimer = setTimeout(() => { + sessionClosedByServer = true; + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by max connection age from ' + clientAddress); + } + try { + session.goaway(http2.constants.NGHTTP2_NO_ERROR, ~(1<<31), Buffer.from('max_age')); + } catch (e) { + // The goaway can't be sent because the session is already closed + session.destroy(); + return; + } + session.close(); + /* Allow a grace period after sending the GOAWAY before forcibly + * closing the connection. */ + if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { + connectionAgeGraceTimer = setTimeout(() => { + session.destroy(); + }, this.maxConnectionAgeGraceMs).unref?.(); + } + }, this.maxConnectionAgeMs + jitter).unref?.(); + } + const keeapliveTimeTimer: NodeJS.Timer | null = setInterval(() => { + const timeoutTImer = setTimeout(() => { + sessionClosedByServer = true; + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by keepalive timeout from ' + clientAddress); + } + session.close(); + }, this.keepaliveTimeoutMs).unref?.(); + try { + session.ping((err: Error | null, duration: number, payload: Buffer) => { + clearTimeout(timeoutTImer); + }); + } catch (e) { + // The ping can't be sent because the session is already closed + session.destroy(); + } + }, this.keepaliveTimeMs).unref?.(); session.on('close', () => { if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); + if (!sessionClosedByServer) { + this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); + } this.sessionChildrenTracker.unrefChild(channelzRef); unregisterChannelzRef(channelzRef); } + if (connectionAgeTimer) { + clearTimeout(connectionAgeTimer); + } + if (connectionAgeGraceTimer) { + clearTimeout(connectionAgeGraceTimer); + } + if (keeapliveTimeTimer) { + clearTimeout(keeapliveTimeTimer); + } this.sessions.delete(session); }); }); From 0de2aad269e4a27669aaada0f71ae20f5209c426 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 10 Nov 2022 10:54:19 -0800 Subject: [PATCH 368/694] grpc-js: Fix reuse of channel filter stack factory --- packages/grpc-js/src/filter-stack.ts | 4 ++++ packages/grpc-js/src/internal-channel.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts index f44c43839..622f3dd0a 100644 --- a/packages/grpc-js/src/filter-stack.ts +++ b/packages/grpc-js/src/filter-stack.ts @@ -88,6 +88,10 @@ export class FilterStackFactory implements FilterFactory { this.factories.unshift(...filterFactories); } + clone(): FilterStackFactory { + return new FilterStackFactory(this.factories); + } + createFilter(): FilterStack { return new FilterStack( this.factories.map((factory) => factory.createFilter()) diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 16f4b9832..a8c6dc964 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -439,7 +439,7 @@ export class InternalChannel { parentCall: parentCall, }; - const call = new ResolvingCall(this, method, finalOptions, this.filterStackFactory, this.credentials._getCallCredentials(), getNextCallNumber()); + const call = new ResolvingCall(this, method, finalOptions, this.filterStackFactory.clone(), this.credentials._getCallCredentials(), getNextCallNumber()); if (this.channelzEnabled) { this.callTracker.addCallStarted(); From 38f2497daeabe49c7377c168cd9df4531f98a618 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 11 Nov 2022 09:24:15 -0800 Subject: [PATCH 369/694] grpc-js: Make filter stack factory clone with a copy of the array --- packages/grpc-js/src/filter-stack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts index 622f3dd0a..4733c8218 100644 --- a/packages/grpc-js/src/filter-stack.ts +++ b/packages/grpc-js/src/filter-stack.ts @@ -89,7 +89,7 @@ export class FilterStackFactory implements FilterFactory { } clone(): FilterStackFactory { - return new FilterStackFactory(this.factories); + return new FilterStackFactory([...this.factories]); } createFilter(): FilterStack { From f8f95ee9bbbfb867590879cc4b31c6db3e27d1bb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 14 Nov 2022 09:50:33 -0800 Subject: [PATCH 370/694] grpc-js-xds: interop: Fix timestamp handling when config changes --- packages/grpc-js-xds/interop/xds-interop-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 72bbec61d..0394c0ace 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -345,7 +345,7 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpcs: boolean, callStatsTracker: CallStatsTracker) { const callStartTimestampsTrackers: {[callType: string]: RecentTimestampList} = {}; - for (const callType of currentConfig.callTypes) { + for (const callType of ['EmptyCall', 'UnaryCall']) { callStartTimestampsTrackers[callType] = new RecentTimestampList(qps); } setInterval(() => { From c7125fbdb5590511c4b88aa5d939e6904db6a283 Mon Sep 17 00:00:00 2001 From: install <1994052+install@users.noreply.github.com> Date: Tue, 15 Nov 2022 16:44:52 -0500 Subject: [PATCH 371/694] proto-loader-gen-types Avoid TS enums --- .../bin/proto-loader-gen-types.ts | 51 ++++++--- .../google/api/FieldBehavior.ts | 77 +++++++++++-- .../google/protobuf/FieldDescriptorProto.ts | 108 +++++++++++++----- .../google/protobuf/FieldOptions.ts | 54 ++++++--- .../google/protobuf/FileOptions.ts | 24 ++-- .../google/showcase/v1beta1/EchoRequest.ts | 6 +- .../google/showcase/v1beta1/EchoResponse.ts | 6 +- .../google/showcase/v1beta1/Severity.ts | 30 ++++- 8 files changed, 266 insertions(+), 90 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index bb5b35f81..64ec28ddb 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -135,20 +135,16 @@ function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Serv /* If the dependency is defined within a message, it will be generated in that * message's file and exported using its typeInterfaceName. */ if (dependency.parent instanceof Protobuf.Type) { - if (dependency instanceof Protobuf.Type) { + if (dependency instanceof Protobuf.Type || dependency instanceof Protobuf.Enum) { importedTypes = `${inputName(typeInterfaceName)}, ${outputName(typeInterfaceName)}`; - } else if (dependency instanceof Protobuf.Enum) { - importedTypes = `${typeInterfaceName}`; } else if (dependency instanceof Protobuf.Service) { importedTypes = `${typeInterfaceName}Client, ${typeInterfaceName}Definition`; } else { throw new Error('Invalid object passed to getImportLine'); } } else { - if (dependency instanceof Protobuf.Type) { + if (dependency instanceof Protobuf.Type || dependency instanceof Protobuf.Enum) { importedTypes = `${inputName(dependency.name)} as ${inputName(typeInterfaceName)}, ${outputName(dependency.name)} as ${outputName(typeInterfaceName)}`; - } else if (dependency instanceof Protobuf.Enum) { - importedTypes = `${dependency.name} as ${typeInterfaceName}`; } else if (dependency instanceof Protobuf.Service) { importedTypes = `${dependency.name}Client as ${typeInterfaceName}Client, ${dependency.name}Definition as ${typeInterfaceName}Definition`; } else { @@ -213,14 +209,14 @@ function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | throw new Error('Found field with no usable type'); } const typeInterfaceName = getTypeInterfaceName(resolvedType); - if (resolvedType instanceof Protobuf.Type) { + if (resolvedType instanceof Protobuf.Type || resolvedType instanceof Protobuf.Enum) { if (repeated || map) { return inputName(typeInterfaceName); } else { return `${inputName(typeInterfaceName)} | null`; } } else { - return `${typeInterfaceName} | keyof typeof ${typeInterfaceName}`; + return ''; } } } @@ -315,7 +311,7 @@ function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | throw new Error('Found field with no usable type'); } const typeInterfaceName = getTypeInterfaceName(resolvedType); - if (resolvedType instanceof Protobuf.Type) { + if (resolvedType instanceof Protobuf.Type || resolvedType instanceof Protobuf.Enum) { /* null is only used to represent absent message values if the defaults * option is set, and only for non-repeated, non-map fields. */ if (options.defaults && !repeated && !map) { @@ -324,11 +320,7 @@ function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | return `${outputName(typeInterfaceName)}`; } } else { - if (options.enums == String) { - return `keyof typeof ${typeInterfaceName}`; - } else { - return typeInterfaceName; - } + return ''; } } } @@ -455,21 +447,46 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob } function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum, options: GeneratorOptions, nameOverride?: string) { + const {inputName, outputName} = useNameFmter(options); + const name = nameOverride ?? enumType.name; formatter.writeLine(`// Original file: ${(enumType.filename ?? 'null')?.replace(/\\/g, '/')}`); formatter.writeLine(''); if (options.includeComments) { formatComment(formatter, enumType.comment); } - formatter.writeLine(`export enum ${nameOverride ?? enumType.name} {`); + formatter.writeLine(`export const ${name} = {`); formatter.indent(); for (const key of Object.keys(enumType.values)) { if (options.includeComments) { formatComment(formatter, enumType.comments[key]); } - formatter.writeLine(`${key} = ${enumType.values[key]},`); + formatter.writeLine(`${key}: ${options.enums == String ? `'${key}'` : enumType.values[key]},`); } formatter.unindent(); - formatter.writeLine('}'); + formatter.writeLine('} as const;'); + + // Permissive Type + formatter.writeLine(''); + if (options.includeComments) { + formatComment(formatter, enumType.comment); + } + formatter.writeLine(`export type ${inputName(name)} =`) + formatter.indent(); + for (const key of Object.keys(enumType.values)) { + if (options.includeComments) { + formatComment(formatter, enumType.comments[key]); + } + formatter.writeLine(`| '${key}'`); + formatter.writeLine(`| ${enumType.values[key]}`); + } + formatter.unindent(); + + // Restrictive Type + formatter.writeLine(''); + if (options.includeComments) { + formatComment(formatter, enumType.comment); + } + formatter.writeLine(`export type ${outputName(name)} = typeof ${name}[keyof typeof ${name}]`) } /** diff --git a/packages/proto-loader/golden-generated/google/api/FieldBehavior.ts b/packages/proto-loader/golden-generated/google/api/FieldBehavior.ts index 8ab676709..189d25be5 100644 --- a/packages/proto-loader/golden-generated/google/api/FieldBehavior.ts +++ b/packages/proto-loader/golden-generated/google/api/FieldBehavior.ts @@ -8,40 +8,101 @@ * * Note: This enum **may** receive new values in the future. */ -export enum FieldBehavior { +export const FieldBehavior = { /** * Conventional default for enums. Do not use this. */ - FIELD_BEHAVIOR_UNSPECIFIED = 0, + FIELD_BEHAVIOR_UNSPECIFIED: 'FIELD_BEHAVIOR_UNSPECIFIED', /** * Specifically denotes a field as optional. * While all fields in protocol buffers are optional, this may be specified * for emphasis if appropriate. */ - OPTIONAL = 1, + OPTIONAL: 'OPTIONAL', /** * Denotes a field as required. * This indicates that the field **must** be provided as part of the request, * and failure to do so will cause an error (usually `INVALID_ARGUMENT`). */ - REQUIRED = 2, + REQUIRED: 'REQUIRED', /** * Denotes a field as output only. * This indicates that the field is provided in responses, but including the * field in a request does nothing (the server *must* ignore it and * *must not* throw an error as a result of the field's presence). */ - OUTPUT_ONLY = 3, + OUTPUT_ONLY: 'OUTPUT_ONLY', /** * Denotes a field as input only. * This indicates that the field is provided in requests, and the * corresponding field is not included in output. */ - INPUT_ONLY = 4, + INPUT_ONLY: 'INPUT_ONLY', /** * Denotes a field as immutable. * This indicates that the field may be set once in a request to create a * resource, but may not be changed thereafter. */ - IMMUTABLE = 5, -} + IMMUTABLE: 'IMMUTABLE', +} as const; + +/** + * An indicator of the behavior of a given field (for example, that a field + * is required in requests, or given as output but ignored as input). + * This **does not** change the behavior in protocol buffers itself; it only + * denotes the behavior and may affect how API tooling handles the field. + * + * Note: This enum **may** receive new values in the future. + */ +export type IFieldBehavior = + /** + * Conventional default for enums. Do not use this. + */ + | 'FIELD_BEHAVIOR_UNSPECIFIED' + | 0 + /** + * Specifically denotes a field as optional. + * While all fields in protocol buffers are optional, this may be specified + * for emphasis if appropriate. + */ + | 'OPTIONAL' + | 1 + /** + * Denotes a field as required. + * This indicates that the field **must** be provided as part of the request, + * and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + */ + | 'REQUIRED' + | 2 + /** + * Denotes a field as output only. + * This indicates that the field is provided in responses, but including the + * field in a request does nothing (the server *must* ignore it and + * *must not* throw an error as a result of the field's presence). + */ + | 'OUTPUT_ONLY' + | 3 + /** + * Denotes a field as input only. + * This indicates that the field is provided in requests, and the + * corresponding field is not included in output. + */ + | 'INPUT_ONLY' + | 4 + /** + * Denotes a field as immutable. + * This indicates that the field may be set once in a request to create a + * resource, but may not be changed thereafter. + */ + | 'IMMUTABLE' + | 5 + +/** + * An indicator of the behavior of a given field (for example, that a field + * is required in requests, or given as output but ignored as input). + * This **does not** change the behavior in protocol buffers itself; it only + * denotes the behavior and may affect how API tooling handles the field. + * + * Note: This enum **may** receive new values in the future. + */ +export type OFieldBehavior = typeof FieldBehavior[keyof typeof FieldBehavior] diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts index 0a713e9dc..3dbce5175 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts @@ -4,41 +4,91 @@ import type { IFieldOptions as I_google_protobuf_FieldOptions, OFieldOptions as // Original file: null -export enum _google_protobuf_FieldDescriptorProto_Label { - LABEL_OPTIONAL = 1, - LABEL_REQUIRED = 2, - LABEL_REPEATED = 3, -} +export const _google_protobuf_FieldDescriptorProto_Label = { + LABEL_OPTIONAL: 'LABEL_OPTIONAL', + LABEL_REQUIRED: 'LABEL_REQUIRED', + LABEL_REPEATED: 'LABEL_REPEATED', +} as const; + +export type I_google_protobuf_FieldDescriptorProto_Label = + | 'LABEL_OPTIONAL' + | 1 + | 'LABEL_REQUIRED' + | 2 + | 'LABEL_REPEATED' + | 3 + +export type O_google_protobuf_FieldDescriptorProto_Label = typeof _google_protobuf_FieldDescriptorProto_Label[keyof typeof _google_protobuf_FieldDescriptorProto_Label] // Original file: null -export enum _google_protobuf_FieldDescriptorProto_Type { - TYPE_DOUBLE = 1, - TYPE_FLOAT = 2, - TYPE_INT64 = 3, - TYPE_UINT64 = 4, - TYPE_INT32 = 5, - TYPE_FIXED64 = 6, - TYPE_FIXED32 = 7, - TYPE_BOOL = 8, - TYPE_STRING = 9, - TYPE_GROUP = 10, - TYPE_MESSAGE = 11, - TYPE_BYTES = 12, - TYPE_UINT32 = 13, - TYPE_ENUM = 14, - TYPE_SFIXED32 = 15, - TYPE_SFIXED64 = 16, - TYPE_SINT32 = 17, - TYPE_SINT64 = 18, -} +export const _google_protobuf_FieldDescriptorProto_Type = { + TYPE_DOUBLE: 'TYPE_DOUBLE', + TYPE_FLOAT: 'TYPE_FLOAT', + TYPE_INT64: 'TYPE_INT64', + TYPE_UINT64: 'TYPE_UINT64', + TYPE_INT32: 'TYPE_INT32', + TYPE_FIXED64: 'TYPE_FIXED64', + TYPE_FIXED32: 'TYPE_FIXED32', + TYPE_BOOL: 'TYPE_BOOL', + TYPE_STRING: 'TYPE_STRING', + TYPE_GROUP: 'TYPE_GROUP', + TYPE_MESSAGE: 'TYPE_MESSAGE', + TYPE_BYTES: 'TYPE_BYTES', + TYPE_UINT32: 'TYPE_UINT32', + TYPE_ENUM: 'TYPE_ENUM', + TYPE_SFIXED32: 'TYPE_SFIXED32', + TYPE_SFIXED64: 'TYPE_SFIXED64', + TYPE_SINT32: 'TYPE_SINT32', + TYPE_SINT64: 'TYPE_SINT64', +} as const; + +export type I_google_protobuf_FieldDescriptorProto_Type = + | 'TYPE_DOUBLE' + | 1 + | 'TYPE_FLOAT' + | 2 + | 'TYPE_INT64' + | 3 + | 'TYPE_UINT64' + | 4 + | 'TYPE_INT32' + | 5 + | 'TYPE_FIXED64' + | 6 + | 'TYPE_FIXED32' + | 7 + | 'TYPE_BOOL' + | 8 + | 'TYPE_STRING' + | 9 + | 'TYPE_GROUP' + | 10 + | 'TYPE_MESSAGE' + | 11 + | 'TYPE_BYTES' + | 12 + | 'TYPE_UINT32' + | 13 + | 'TYPE_ENUM' + | 14 + | 'TYPE_SFIXED32' + | 15 + | 'TYPE_SFIXED64' + | 16 + | 'TYPE_SINT32' + | 17 + | 'TYPE_SINT64' + | 18 + +export type O_google_protobuf_FieldDescriptorProto_Type = typeof _google_protobuf_FieldDescriptorProto_Type[keyof typeof _google_protobuf_FieldDescriptorProto_Type] export interface IFieldDescriptorProto { 'name'?: (string); 'extendee'?: (string); 'number'?: (number); - 'label'?: (_google_protobuf_FieldDescriptorProto_Label | keyof typeof _google_protobuf_FieldDescriptorProto_Label); - 'type'?: (_google_protobuf_FieldDescriptorProto_Type | keyof typeof _google_protobuf_FieldDescriptorProto_Type); + 'label'?: (I_google_protobuf_FieldDescriptorProto_Label | null); + 'type'?: (I_google_protobuf_FieldDescriptorProto_Type | null); 'typeName'?: (string); 'defaultValue'?: (string); 'options'?: (I_google_protobuf_FieldOptions | null); @@ -50,8 +100,8 @@ export interface OFieldDescriptorProto { 'name': (string); 'extendee': (string); 'number': (number); - 'label': (keyof typeof _google_protobuf_FieldDescriptorProto_Label); - 'type': (keyof typeof _google_protobuf_FieldDescriptorProto_Type); + 'label': (O_google_protobuf_FieldDescriptorProto_Label | null); + 'type': (O_google_protobuf_FieldDescriptorProto_Type | null); 'typeName': (string); 'defaultValue': (string); 'options': (O_google_protobuf_FieldOptions | null); diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts index 076b35983..daf307d64 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts @@ -1,42 +1,62 @@ // Original file: null import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption'; -import type { FieldBehavior as _google_api_FieldBehavior } from '../../google/api/FieldBehavior'; +import type { IFieldBehavior as I_google_api_FieldBehavior, OFieldBehavior as O_google_api_FieldBehavior } from '../../google/api/FieldBehavior'; // Original file: null -export enum _google_protobuf_FieldOptions_CType { - STRING = 0, - CORD = 1, - STRING_PIECE = 2, -} +export const _google_protobuf_FieldOptions_CType = { + STRING: 'STRING', + CORD: 'CORD', + STRING_PIECE: 'STRING_PIECE', +} as const; + +export type I_google_protobuf_FieldOptions_CType = + | 'STRING' + | 0 + | 'CORD' + | 1 + | 'STRING_PIECE' + | 2 + +export type O_google_protobuf_FieldOptions_CType = typeof _google_protobuf_FieldOptions_CType[keyof typeof _google_protobuf_FieldOptions_CType] // Original file: null -export enum _google_protobuf_FieldOptions_JSType { - JS_NORMAL = 0, - JS_STRING = 1, - JS_NUMBER = 2, -} +export const _google_protobuf_FieldOptions_JSType = { + JS_NORMAL: 'JS_NORMAL', + JS_STRING: 'JS_STRING', + JS_NUMBER: 'JS_NUMBER', +} as const; + +export type I_google_protobuf_FieldOptions_JSType = + | 'JS_NORMAL' + | 0 + | 'JS_STRING' + | 1 + | 'JS_NUMBER' + | 2 + +export type O_google_protobuf_FieldOptions_JSType = typeof _google_protobuf_FieldOptions_JSType[keyof typeof _google_protobuf_FieldOptions_JSType] export interface IFieldOptions { - 'ctype'?: (_google_protobuf_FieldOptions_CType | keyof typeof _google_protobuf_FieldOptions_CType); + 'ctype'?: (I_google_protobuf_FieldOptions_CType | null); 'packed'?: (boolean); 'deprecated'?: (boolean); 'lazy'?: (boolean); - 'jstype'?: (_google_protobuf_FieldOptions_JSType | keyof typeof _google_protobuf_FieldOptions_JSType); + 'jstype'?: (I_google_protobuf_FieldOptions_JSType | null); 'weak'?: (boolean); 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; - '.google.api.field_behavior'?: (_google_api_FieldBehavior | keyof typeof _google_api_FieldBehavior)[]; + '.google.api.field_behavior'?: (I_google_api_FieldBehavior)[]; } export interface OFieldOptions { - 'ctype': (keyof typeof _google_protobuf_FieldOptions_CType); + 'ctype': (O_google_protobuf_FieldOptions_CType | null); 'packed': (boolean); 'deprecated': (boolean); 'lazy': (boolean); - 'jstype': (keyof typeof _google_protobuf_FieldOptions_JSType); + 'jstype': (O_google_protobuf_FieldOptions_JSType | null); 'weak': (boolean); 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; - '.google.api.field_behavior': (keyof typeof _google_api_FieldBehavior)[]; + '.google.api.field_behavior': (O_google_api_FieldBehavior)[]; } diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts index 2b832e0f8..038ef4785 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts @@ -4,16 +4,26 @@ import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUn // Original file: null -export enum _google_protobuf_FileOptions_OptimizeMode { - SPEED = 1, - CODE_SIZE = 2, - LITE_RUNTIME = 3, -} +export const _google_protobuf_FileOptions_OptimizeMode = { + SPEED: 'SPEED', + CODE_SIZE: 'CODE_SIZE', + LITE_RUNTIME: 'LITE_RUNTIME', +} as const; + +export type I_google_protobuf_FileOptions_OptimizeMode = + | 'SPEED' + | 1 + | 'CODE_SIZE' + | 2 + | 'LITE_RUNTIME' + | 3 + +export type O_google_protobuf_FileOptions_OptimizeMode = typeof _google_protobuf_FileOptions_OptimizeMode[keyof typeof _google_protobuf_FileOptions_OptimizeMode] export interface IFileOptions { 'javaPackage'?: (string); 'javaOuterClassname'?: (string); - 'optimizeFor'?: (_google_protobuf_FileOptions_OptimizeMode | keyof typeof _google_protobuf_FileOptions_OptimizeMode); + 'optimizeFor'?: (I_google_protobuf_FileOptions_OptimizeMode | null); 'javaMultipleFiles'?: (boolean); 'goPackage'?: (string); 'ccGenericServices'?: (boolean); @@ -31,7 +41,7 @@ export interface IFileOptions { export interface OFileOptions { 'javaPackage': (string); 'javaOuterClassname': (string); - 'optimizeFor': (keyof typeof _google_protobuf_FileOptions_OptimizeMode); + 'optimizeFor': (O_google_protobuf_FileOptions_OptimizeMode | null); 'javaMultipleFiles': (boolean); 'goPackage': (string); 'ccGenericServices': (boolean); diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts index 649a5d505..715fcb342 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts @@ -1,7 +1,7 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto import type { IStatus as I_google_rpc_Status, OStatus as O_google_rpc_Status } from '../../../google/rpc/Status'; -import type { Severity as _google_showcase_v1beta1_Severity } from '../../../google/showcase/v1beta1/Severity'; +import type { ISeverity as I_google_showcase_v1beta1_Severity, OSeverity as O_google_showcase_v1beta1_Severity } from '../../../google/showcase/v1beta1/Severity'; /** * The request message used for the Echo, Collect and Chat methods. @@ -21,7 +21,7 @@ export interface IEchoRequest { /** * The severity to be echoed by the server. */ - 'severity'?: (_google_showcase_v1beta1_Severity | keyof typeof _google_showcase_v1beta1_Severity); + 'severity'?: (I_google_showcase_v1beta1_Severity | null); 'response'?: "content"|"error"; } @@ -43,6 +43,6 @@ export interface OEchoRequest { /** * The severity to be echoed by the server. */ - 'severity': (keyof typeof _google_showcase_v1beta1_Severity); + 'severity': (O_google_showcase_v1beta1_Severity | null); 'response': "content"|"error"; } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts index 96b7ba25d..68e685967 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts @@ -1,6 +1,6 @@ // Original file: deps/gapic-showcase/schema/google/showcase/v1beta1/echo.proto -import type { Severity as _google_showcase_v1beta1_Severity } from '../../../google/showcase/v1beta1/Severity'; +import type { ISeverity as I_google_showcase_v1beta1_Severity, OSeverity as O_google_showcase_v1beta1_Severity } from '../../../google/showcase/v1beta1/Severity'; /** * The response message for the Echo methods. @@ -13,7 +13,7 @@ export interface IEchoResponse { /** * The severity specified in the request. */ - 'severity'?: (_google_showcase_v1beta1_Severity | keyof typeof _google_showcase_v1beta1_Severity); + 'severity'?: (I_google_showcase_v1beta1_Severity | null); } /** @@ -27,5 +27,5 @@ export interface OEchoResponse { /** * The severity specified in the request. */ - 'severity': (keyof typeof _google_showcase_v1beta1_Severity); + 'severity': (O_google_showcase_v1beta1_Severity | null); } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Severity.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Severity.ts index fc3fe6415..d109fe1ce 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/Severity.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/Severity.ts @@ -3,9 +3,27 @@ /** * A severity enum used to test enum capabilities in GAPIC surfaces */ -export enum Severity { - UNNECESSARY = 0, - NECESSARY = 1, - URGENT = 2, - CRITICAL = 3, -} +export const Severity = { + UNNECESSARY: 'UNNECESSARY', + NECESSARY: 'NECESSARY', + URGENT: 'URGENT', + CRITICAL: 'CRITICAL', +} as const; + +/** + * A severity enum used to test enum capabilities in GAPIC surfaces + */ +export type ISeverity = + | 'UNNECESSARY' + | 0 + | 'NECESSARY' + | 1 + | 'URGENT' + | 2 + | 'CRITICAL' + | 3 + +/** + * A severity enum used to test enum capabilities in GAPIC surfaces + */ +export type OSeverity = typeof Severity[keyof typeof Severity] From ef7b8e8f14ca98dc7b2a04ebb64d660729b92494 Mon Sep 17 00:00:00 2001 From: install <1994052+install@users.noreply.github.com> Date: Wed, 16 Nov 2022 10:10:13 -0500 Subject: [PATCH 372/694] Don't allow `null` for enum field inputs/outputs --- packages/proto-loader/bin/proto-loader-gen-types.ts | 10 ++++++---- .../google/protobuf/FieldDescriptorProto.ts | 8 ++++---- .../golden-generated/google/protobuf/FieldOptions.ts | 8 ++++---- .../golden-generated/google/protobuf/FileOptions.ts | 4 ++-- .../google/showcase/v1beta1/EchoRequest.ts | 4 ++-- .../google/showcase/v1beta1/EchoResponse.ts | 4 ++-- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 64ec28ddb..a9ee4a6ed 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -209,14 +209,15 @@ function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | throw new Error('Found field with no usable type'); } const typeInterfaceName = getTypeInterfaceName(resolvedType); - if (resolvedType instanceof Protobuf.Type || resolvedType instanceof Protobuf.Enum) { + if (resolvedType instanceof Protobuf.Type) { if (repeated || map) { return inputName(typeInterfaceName); } else { return `${inputName(typeInterfaceName)} | null`; } } else { - return ''; + // Enum + return inputName(typeInterfaceName); } } } @@ -311,7 +312,7 @@ function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | throw new Error('Found field with no usable type'); } const typeInterfaceName = getTypeInterfaceName(resolvedType); - if (resolvedType instanceof Protobuf.Type || resolvedType instanceof Protobuf.Enum) { + if (resolvedType instanceof Protobuf.Type) { /* null is only used to represent absent message values if the defaults * option is set, and only for non-repeated, non-map fields. */ if (options.defaults && !repeated && !map) { @@ -320,7 +321,8 @@ function getTypeNameRestricted(fieldType: string, resolvedType: Protobuf.Type | return `${outputName(typeInterfaceName)}`; } } else { - return ''; + // Enum + return outputName(typeInterfaceName); } } } diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts index 3dbce5175..1bcb69abe 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldDescriptorProto.ts @@ -87,8 +87,8 @@ export interface IFieldDescriptorProto { 'name'?: (string); 'extendee'?: (string); 'number'?: (number); - 'label'?: (I_google_protobuf_FieldDescriptorProto_Label | null); - 'type'?: (I_google_protobuf_FieldDescriptorProto_Type | null); + 'label'?: (I_google_protobuf_FieldDescriptorProto_Label); + 'type'?: (I_google_protobuf_FieldDescriptorProto_Type); 'typeName'?: (string); 'defaultValue'?: (string); 'options'?: (I_google_protobuf_FieldOptions | null); @@ -100,8 +100,8 @@ export interface OFieldDescriptorProto { 'name': (string); 'extendee': (string); 'number': (number); - 'label': (O_google_protobuf_FieldDescriptorProto_Label | null); - 'type': (O_google_protobuf_FieldDescriptorProto_Type | null); + 'label': (O_google_protobuf_FieldDescriptorProto_Label); + 'type': (O_google_protobuf_FieldDescriptorProto_Type); 'typeName': (string); 'defaultValue': (string); 'options': (O_google_protobuf_FieldOptions | null); diff --git a/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts index daf307d64..16e532d95 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FieldOptions.ts @@ -40,22 +40,22 @@ export type I_google_protobuf_FieldOptions_JSType = export type O_google_protobuf_FieldOptions_JSType = typeof _google_protobuf_FieldOptions_JSType[keyof typeof _google_protobuf_FieldOptions_JSType] export interface IFieldOptions { - 'ctype'?: (I_google_protobuf_FieldOptions_CType | null); + 'ctype'?: (I_google_protobuf_FieldOptions_CType); 'packed'?: (boolean); 'deprecated'?: (boolean); 'lazy'?: (boolean); - 'jstype'?: (I_google_protobuf_FieldOptions_JSType | null); + 'jstype'?: (I_google_protobuf_FieldOptions_JSType); 'weak'?: (boolean); 'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[]; '.google.api.field_behavior'?: (I_google_api_FieldBehavior)[]; } export interface OFieldOptions { - 'ctype': (O_google_protobuf_FieldOptions_CType | null); + 'ctype': (O_google_protobuf_FieldOptions_CType); 'packed': (boolean); 'deprecated': (boolean); 'lazy': (boolean); - 'jstype': (O_google_protobuf_FieldOptions_JSType | null); + 'jstype': (O_google_protobuf_FieldOptions_JSType); 'weak': (boolean); 'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[]; '.google.api.field_behavior': (O_google_api_FieldBehavior)[]; diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts index 038ef4785..fdeac9cd4 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts @@ -23,7 +23,7 @@ export type O_google_protobuf_FileOptions_OptimizeMode = typeof _google_protobuf export interface IFileOptions { 'javaPackage'?: (string); 'javaOuterClassname'?: (string); - 'optimizeFor'?: (I_google_protobuf_FileOptions_OptimizeMode | null); + 'optimizeFor'?: (I_google_protobuf_FileOptions_OptimizeMode); 'javaMultipleFiles'?: (boolean); 'goPackage'?: (string); 'ccGenericServices'?: (boolean); @@ -41,7 +41,7 @@ export interface IFileOptions { export interface OFileOptions { 'javaPackage': (string); 'javaOuterClassname': (string); - 'optimizeFor': (O_google_protobuf_FileOptions_OptimizeMode | null); + 'optimizeFor': (O_google_protobuf_FileOptions_OptimizeMode); 'javaMultipleFiles': (boolean); 'goPackage': (string); 'ccGenericServices': (boolean); diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts index 715fcb342..a5fb8f766 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoRequest.ts @@ -21,7 +21,7 @@ export interface IEchoRequest { /** * The severity to be echoed by the server. */ - 'severity'?: (I_google_showcase_v1beta1_Severity | null); + 'severity'?: (I_google_showcase_v1beta1_Severity); 'response'?: "content"|"error"; } @@ -43,6 +43,6 @@ export interface OEchoRequest { /** * The severity to be echoed by the server. */ - 'severity': (O_google_showcase_v1beta1_Severity | null); + 'severity': (O_google_showcase_v1beta1_Severity); 'response': "content"|"error"; } diff --git a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts index 68e685967..ac50115bf 100644 --- a/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts +++ b/packages/proto-loader/golden-generated/google/showcase/v1beta1/EchoResponse.ts @@ -13,7 +13,7 @@ export interface IEchoResponse { /** * The severity specified in the request. */ - 'severity'?: (I_google_showcase_v1beta1_Severity | null); + 'severity'?: (I_google_showcase_v1beta1_Severity); } /** @@ -27,5 +27,5 @@ export interface OEchoResponse { /** * The severity specified in the request. */ - 'severity': (O_google_showcase_v1beta1_Severity | null); + 'severity': (O_google_showcase_v1beta1_Severity); } From 5a5e42498ce1ce2de7b14dc02c14985ce7770b4a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 16 Nov 2022 14:09:40 -0800 Subject: [PATCH 373/694] grpc-js: Enable servers to send trailers-only responses --- packages/grpc-js/README.md | 3 + packages/grpc-js/src/load-balancing-call.ts | 3 + packages/grpc-js/src/retrying-call.ts | 21 +- packages/grpc-js/src/server-call.ts | 40 ++- packages/grpc-js/src/service-config.ts | 39 ++- packages/grpc-js/test/test-retry-config.ts | 292 +++++++++++++++++++ packages/grpc-js/test/test-retry.ts | 301 ++++++++++++++++++++ 7 files changed, 666 insertions(+), 33 deletions(-) create mode 100644 packages/grpc-js/test/test-retry-config.ts create mode 100644 packages/grpc-js/test/test-retry.ts diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 11a8ca9aa..3d698dfd8 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -59,6 +59,9 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.default_compression_algorithm` - `grpc.enable_channelz` - `grpc.dns_min_time_between_resolutions_ms` + - `grpc.enable_retries` + - `grpc.per_rpc_retry_buffer_size` + - `grpc.retry_buffer_size` - `grpc-node.max_session_memory` - `channelOverride` - `channelFactoryOverride` diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 23b9a9174..a7af83d6d 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -150,12 +150,15 @@ export class LoadBalancingCall implements Call { try { this.child = pickResult.subchannel!.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, { onReceiveMetadata: metadata => { + this.trace('Received metadata'); this.listener!.onReceiveMetadata(metadata); }, onReceiveMessage: message => { + this.trace('Received message'); this.listener!.onReceiveMessage(message); }, onReceiveStatus: status => { + this.trace('Received status'); if (status.code === http2.constants.NGHTTP2_REFUSED_STREAM) { this.outputStatus(status, 'REFUSED'); } else { diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index 7c15023d5..71dedd46f 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -145,6 +145,13 @@ export class RetryingCall implements Call { private initialMetadata: Metadata | null = null; private underlyingCalls: UnderlyingCall[] = []; private writeBuffer: WriteBufferEntry[] = []; + /** + * Tracks whether a read has been started, so that we know whether to start + * reads on new child calls. This only matters for the first read, because + * once a message comes in the child call becomes committed and there will + * be no new child calls. + */ + private readStarted = false; private transparentRetryUsed: boolean = false; /** * Number of attempts so far @@ -319,7 +326,7 @@ export class RetryingCall implements Call { this.reportStatus(status); break; case 'HEDGING': - if (this.isStatusCodeInList(this.callConfig!.methodConfig.hedgingPolicy!.nonFatalStatusCodes, status.code)) { + if (this.isStatusCodeInList(this.callConfig!.methodConfig.hedgingPolicy!.nonFatalStatusCodes ?? [], status.code)) { this.retryThrottler?.addCallFailed(); let delayMs: number; if (pushback === null) { @@ -378,6 +385,7 @@ export class RetryingCall implements Call { if (this.underlyingCalls[callIndex].state === 'COMPLETED') { return; } + this.trace('state=' + this.state + ' handling status from child [' + this.underlyingCalls[callIndex].call.getCallNumber() + '] in state ' + this.underlyingCalls[callIndex].state); this.underlyingCalls[callIndex].state = 'COMPLETED'; if (status.code === Status.OK) { this.retryThrottler?.addCallSucceeded(); @@ -465,6 +473,7 @@ export class RetryingCall implements Call { let receivedMetadata = false; child.start(initialMetadata, { onReceiveMetadata: metadata => { + this.trace('Received metadata from child [' + child.getCallNumber() + ']'); this.commitCall(index); receivedMetadata = true; if (previousAttempts > 0) { @@ -475,19 +484,24 @@ export class RetryingCall implements Call { } }, onReceiveMessage: message => { + this.trace('Received message from child [' + child.getCallNumber() + ']'); this.commitCall(index); if (this.underlyingCalls[index].state === 'ACTIVE') { this.listener!.onReceiveMessage(message); } }, onReceiveStatus: status => { + this.trace('Received status from child [' + child.getCallNumber() + ']'); if (!receivedMetadata && previousAttempts > 0) { status.metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); } - this.commitCall(index); this.handleChildStatus(status, index); } - }) + }); + this.sendNextChildMessage(index); + if (this.readStarted) { + child.startRead(); + } } start(metadata: Metadata, listener: InterceptingListener): void { @@ -559,6 +573,7 @@ export class RetryingCall implements Call { } startRead(): void { this.trace('startRead called'); + this.readStarted = true; for (const underlyingCall of this.underlyingCalls) { if (underlyingCall?.state === 'ACTIVE') { underlyingCall.call.startRead(); diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 38b4379ea..e48d20441 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -673,21 +673,31 @@ export class Http2ServerCallStream< clearTimeout(this.deadlineTimer); - if (!this.wantTrailers) { - this.wantTrailers = true; - this.stream.once('wantTrailers', () => { - const trailersToSend = Object.assign( - { - [GRPC_STATUS_HEADER]: statusObj.code, - [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details as string), - }, - statusObj.metadata.toHttp2Headers() - ); - - this.stream.sendTrailers(trailersToSend); - }); - this.sendMetadata(); - this.stream.end(); + if (this.stream.headersSent) { + if (!this.wantTrailers) { + this.wantTrailers = true; + this.stream.once('wantTrailers', () => { + const trailersToSend = Object.assign( + { + [GRPC_STATUS_HEADER]: statusObj.code, + [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details as string), + }, + statusObj.metadata.toHttp2Headers() + ); + + this.stream.sendTrailers(trailersToSend); + }); + this.stream.end(); + } + } else { + const trailersToSend = Object.assign( + { + [GRPC_STATUS_HEADER]: statusObj.code, + [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details as string), + }, + statusObj.metadata.toHttp2Headers() + ); + this.stream.respond(trailersToSend, {endStream: true}); } } diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index a45355ace..2b8c0a7eb 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -50,7 +50,7 @@ export interface RetryPolicy { export interface HedgingPolicy { maxAttempts: number; hedgingDelay?: string; - nonFatalStatusCodes: (Status | string)[]; + nonFatalStatusCodes?: (Status | string)[]; } export interface MethodConfig { @@ -124,19 +124,23 @@ function validateRetryPolicy(obj: any): RetryPolicy { if (!('backoffMultiplier' in obj) || typeof obj.backoffMultiplier !== 'number' || obj.backoffMultiplier <= 0) { throw new Error('Invalid method config retry policy: backoffMultiplier must be a number greater than 0'); } - if (('retryableStatusCodes' in obj) && Array.isArray(obj.retryableStatusCodes)) { - for (const value of obj.retryableStatusCodes) { - if (typeof value === 'number') { - if (!Object.values(Status).includes(value)) { - throw new Error('Invlid method config retry policy: retryableStatusCodes value not in status code range'); - } - } else if (typeof value === 'string') { - if (!Object.values(Status).includes(value.toUpperCase())) { - throw new Error('Invlid method config retry policy: retryableStatusCodes value not a status code name'); - } - } else { - throw new Error('Invlid method config retry policy: retryableStatusCodes value must be a string or number'); + if (!(('retryableStatusCodes' in obj) && Array.isArray(obj.retryableStatusCodes))) { + throw new Error('Invalid method config retry policy: retryableStatusCodes is required'); + } + if (obj.retryableStatusCodes.length === 0) { + throw new Error('Invalid method config retry policy: retryableStatusCodes must be non-empty'); + } + for (const value of obj.retryableStatusCodes) { + if (typeof value === 'number') { + if (!Object.values(Status).includes(value)) { + throw new Error('Invlid method config retry policy: retryableStatusCodes value not in status code range'); } + } else if (typeof value === 'string') { + if (!Object.values(Status).includes(value.toUpperCase())) { + throw new Error('Invlid method config retry policy: retryableStatusCodes value not a status code name'); + } + } else { + throw new Error('Invlid method config retry policy: retryableStatusCodes value must be a string or number'); } } return { @@ -171,12 +175,14 @@ function validateHedgingPolicy(obj: any): HedgingPolicy { } } const result: HedgingPolicy = { - maxAttempts: obj.maxAttempts, - nonFatalStatusCodes: obj.nonFatalStatusCodes + maxAttempts: obj.maxAttempts } if (obj.hedgingDelay) { result.hedgingDelay = obj.hedgingDelay; } + if (obj.nonFatalStatusCodes) { + result.nonFatalStatusCodes = obj.nonFatalStatusCodes; + } return result; } @@ -291,6 +297,9 @@ export function validateServiceConfig(obj: any): ServiceConfig { } } } + if ('retryThrottling' in obj) { + result.retryThrottling = validateRetryThrottling(obj.retryThrottling); + } // Validate method name uniqueness const seenMethodNames: MethodConfigName[] = []; for (const methodConfig of result.methodConfig) { diff --git a/packages/grpc-js/test/test-retry-config.ts b/packages/grpc-js/test/test-retry-config.ts new file mode 100644 index 000000000..e27f236e2 --- /dev/null +++ b/packages/grpc-js/test/test-retry-config.ts @@ -0,0 +1,292 @@ +/* + * Copyright 2022 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 assert = require("assert"); +import { validateServiceConfig } from "../src/service-config"; + +function createRetryServiceConfig(retryConfig: object): object { + return { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'A', + method: 'B' + }], + + retryPolicy: retryConfig + } + ] + }; +} + +function createHedgingServiceConfig(hedgingConfig: object): object { + return { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'A', + method: 'B' + }], + + hedgingPolicy: hedgingConfig + } + ] + }; +} + +function createThrottlingServiceConfig(retryThrottling: object): object { + return { + loadBalancingConfig: [], + methodConfig: [], + retryThrottling: retryThrottling + }; +} + +interface TestCase { + description: string; + config: object; + error: RegExp; +} + +const validRetryConfig = { + maxAttempts: 2, + initialBackoff: '1s', + maxBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] +}; + +const RETRY_TEST_CASES: TestCase[] = [ + { + description: 'omitted maxAttempts', + config: { + initialBackoff: '1s', + maxBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14] + }, + error: /retry policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'a low maxAttempts', + config: {...validRetryConfig, maxAttempts: 1}, + error: /retry policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'omitted initialBackoff', + config: { + maxAttempts: 2, + maxBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14] + }, + error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'a non-numeric initialBackoff', + config: {...validRetryConfig, initialBackoff: 'abcs'}, + error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'an initialBackoff without an s', + config: {...validRetryConfig, initialBackoff: '123'}, + error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'omitted maxBackoff', + config: { + maxAttempts: 2, + initialBackoff: '1s', + backoffMultiplier: 1, + retryableStatusCodes: [14] + }, + error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'a non-numeric maxBackoff', + config: {...validRetryConfig, maxBackoff: 'abcs'}, + error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'an maxBackoff without an s', + config: {...validRetryConfig, maxBackoff: '123'}, + error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + }, + { + description: 'omitted backoffMultiplier', + config: { + maxAttempts: 2, + initialBackoff: '1s', + maxBackoff: '1s', + retryableStatusCodes: [14] + }, + error: /retry policy: backoffMultiplier must be a number greater than 0/ + }, + { + description: 'a negative backoffMultiplier', + config: {...validRetryConfig, backoffMultiplier: -1}, + error: /retry policy: backoffMultiplier must be a number greater than 0/ + }, + { + description: 'omitted retryableStatusCodes', + config: { + maxAttempts: 2, + initialBackoff: '1s', + maxBackoff: '1s', + backoffMultiplier: 1 + }, + error: /retry policy: retryableStatusCodes is required/ + }, + { + description: 'empty retryableStatusCodes', + config: {...validRetryConfig, retryableStatusCodes: []}, + error: /retry policy: retryableStatusCodes must be non-empty/ + }, + { + description: 'unknown status code name', + config: {...validRetryConfig, retryableStatusCodes: ['abcd']}, + error: /retry policy: retryableStatusCodes value not a status code name/ + }, + { + description: 'out of range status code number', + config: {...validRetryConfig, retryableStatusCodes: [12345]}, + error: /retry policy: retryableStatusCodes value not in status code range/ + } +]; + +const validHedgingConfig = { + maxAttempts: 2 +}; + +const HEDGING_TEST_CASES: TestCase[] = [ + { + description: 'omitted maxAttempts', + config: {}, + error: /hedging policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'a low maxAttempts', + config: {...validHedgingConfig, maxAttempts: 1}, + error: /hedging policy: maxAttempts must be an integer at least 2/ + }, + { + description: 'a non-numeric hedgingDelay', + config: {...validHedgingConfig, hedgingDelay: 'abcs'}, + error: /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/ + }, + { + description: 'a hedgingDelay without an s', + config: {...validHedgingConfig, hedgingDelay: '123'}, + error: /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/ + }, + { + description: 'unknown status code name', + config: {...validHedgingConfig, nonFatalStatusCodes: ['abcd']}, + error: /hedging policy: nonFatalStatusCodes value not a status code name/ + }, + { + description: 'out of range status code number', + config: {...validHedgingConfig, nonFatalStatusCodes: [12345]}, + error: /hedging policy: nonFatalStatusCodes value not in status code range/ + } +]; + +const validThrottlingConfig = { + maxTokens: 100, + tokenRatio: 0.1 +}; + +const THROTTLING_TEST_CASES: TestCase[] = [ + { + description: 'omitted maxTokens', + config: {tokenRatio: 0.1}, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + }, + { + description: 'a large maxTokens', + config: {...validThrottlingConfig, maxTokens: 1001}, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + }, + { + description: 'zero maxTokens', + config: {...validThrottlingConfig, maxTokens: 0}, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + }, + { + description: 'omitted tokenRatio', + config: {maxTokens: 100}, + error: /retryThrottling: tokenRatio must be a number greater than 0/ + }, + { + description: 'zero tokenRatio', + config: {...validThrottlingConfig, tokenRatio: 0}, + error: /retryThrottling: tokenRatio must be a number greater than 0/ + } +]; + +describe('Retry configs', () => { + describe('Retry', () => { + it('Should accept a valid config', () => { + assert.doesNotThrow(() => { + validateServiceConfig(createRetryServiceConfig(validRetryConfig)); + }); + }); + for (const testCase of RETRY_TEST_CASES) { + it(`Should reject ${testCase.description}`, () => { + assert.throws(() => { + validateServiceConfig(createRetryServiceConfig(testCase.config)); + }, testCase.error); + }); + } + }); + describe('Hedging', () => { + it('Should accept valid configs', () => { + assert.doesNotThrow(() => { + validateServiceConfig(createHedgingServiceConfig(validHedgingConfig)); + }); + assert.doesNotThrow(() => { + validateServiceConfig(createHedgingServiceConfig({...validHedgingConfig, hedgingDelay: '1s'})); + }); + assert.doesNotThrow(() => { + validateServiceConfig(createHedgingServiceConfig({...validHedgingConfig, nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED']})); + }); + }); + for (const testCase of HEDGING_TEST_CASES) { + it(`Should reject ${testCase.description}`, () => { + assert.throws(() => { + validateServiceConfig(createHedgingServiceConfig(testCase.config)); + }, testCase.error); + }); + } + }); + describe('Throttling', () => { + it('Should accept a valid config', () => { + assert.doesNotThrow(() => { + validateServiceConfig(createThrottlingServiceConfig(validThrottlingConfig)); + }); + }); + for (const testCase of THROTTLING_TEST_CASES) { + it(`Should reject ${testCase.description}`, () => { + assert.throws(() => { + validateServiceConfig(createThrottlingServiceConfig(testCase.config)); + }, testCase.error); + }); + } + }); +}); \ No newline at end of file diff --git a/packages/grpc-js/test/test-retry.ts b/packages/grpc-js/test/test-retry.ts new file mode 100644 index 000000000..4dd96cc43 --- /dev/null +++ b/packages/grpc-js/test/test-retry.ts @@ -0,0 +1,301 @@ +/* + * Copyright 2022 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 * as assert from 'assert'; +import * as path from 'path'; +import * as grpc from '../src'; +import { loadProtoFile } from './common'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const EchoService = loadProtoFile(protoFile) + .EchoService as grpc.ServiceClientConstructor; + +const serviceImpl = { + echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + const succeedOnRetryAttempt = call.metadata.get('succeed-on-retry-attempt'); + const previousAttempts = call.metadata.get('grpc-previous-rpc-attempts'); + if (succeedOnRetryAttempt.length === 0 || (previousAttempts.length > 0 && previousAttempts[0] === succeedOnRetryAttempt[0])) { + callback(null, call.request); + } else { + const statusCode = call.metadata.get('respond-with-status'); + const code = statusCode[0] ? Number.parseInt(statusCode[0] as string) : grpc.status.UNKNOWN; + callback({ + code: code, + details: `Failed on retry ${previousAttempts[0] ?? 0}` + }); + } + } +} + +describe('Retries', () => { + let server: grpc.Server; + let port: number; + before((done) => { + server = new grpc.Server(); + server.addService(EchoService.service, serviceImpl); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, portNumber) => { + if (error) { + done(error); + return; + } + port = portNumber; + server.start(); + done(); + }); + }); + + after(() => { + server.forceShutdown(); + }); + + describe('Client with retries disabled', () => { + let client: InstanceType; + before(() => { + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.enable_retries': 0}); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail if the server fails the first request', (done) =>{ + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '1'); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 0'); + done(); + } + ); + }); + }); + + describe('Client with retries enabled but not configured', () => { + let client: InstanceType; + before(() => { + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure()); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail if the server fails the first request', (done) =>{ + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '1'); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 0'); + done(); + } + ); + }); + }); + + describe('Client with retries configured', () => { + let client: InstanceType; + before(() => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + retryPolicy: { + maxAttempts: 3, + initialBackoff: '0.1s', + maxBackoff: '10s', + backoffMultiplier: 1.2, + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should succeed with few required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail with many required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '4'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 2'); + done(); + } + ); + }); + + it('Should fail with a fatal status code', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.NOT_FOUND}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 0'); + done(); + } + ); + }); + }); + + describe('Client with hedging configured', () => { + let client: InstanceType; + before(() => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + hedgingPolicy: { + maxAttempts: 3, + nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + }); + + after(() =>{ + client.close(); + }); + + it('Should be able to make a basic request', (done) => { + client.echo( + { value: 'test value', value2: 3 }, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should succeed with few required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + done(); + } + ); + }); + + it('Should fail with many required attempts', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '4'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert(error.details.startsWith('Failed on retry')); + done(); + } + ); + }); + + it('Should fail with a fatal status code', (done) => { + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '2'); + metadata.set('respond-with-status', `${grpc.status.NOT_FOUND}`); + client.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert(error.details.startsWith('Failed on retry')); + done(); + } + ); + }); + }); +}); \ No newline at end of file From e19a7737051d055d77482b0e4bc1947e6f22c208 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 16 Nov 2022 14:10:26 -0800 Subject: [PATCH 374/694] grpc-js: Add retry tests, and fix bugs and add tracing --- .../generated/grpc/channelz/v1/Channelz.ts | 104 +++++++++--------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts index ace712454..4c8c18aa7 100644 --- a/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts +++ b/packages/grpc-js/src/generated/grpc/channelz/v1/Channelz.ts @@ -25,102 +25,102 @@ export interface ChannelzClient extends grpc.Client { /** * Returns a single Channel, or else a NOT_FOUND code. */ - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; - GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetChannelResponse__Output) => void): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; + GetChannel(argument: _grpc_channelz_v1_GetChannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetChannelResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Server, or else a NOT_FOUND code. */ - GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - GetServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - GetServer(argument: _grpc_channelz_v1_GetServerRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + GetServer(argument: _grpc_channelz_v1_GetServerRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Server, or else a NOT_FOUND code. */ - getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - getServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; - getServer(argument: _grpc_channelz_v1_GetServerRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerResponse__Output) => void): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; + getServer(argument: _grpc_channelz_v1_GetServerRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerResponse__Output>): grpc.ClientUnaryCall; /** * Gets all server sockets that exist in the process. */ - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + GetServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; /** * Gets all server sockets that exist in the process. */ - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; - getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServerSocketsResponse__Output) => void): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; + getServerSockets(argument: _grpc_channelz_v1_GetServerSocketsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServerSocketsResponse__Output>): grpc.ClientUnaryCall; /** * Gets all servers that exist in the process. */ - GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - GetServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - GetServers(argument: _grpc_channelz_v1_GetServersRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + GetServers(argument: _grpc_channelz_v1_GetServersRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; /** * Gets all servers that exist in the process. */ - getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - getServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; - getServers(argument: _grpc_channelz_v1_GetServersRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetServersResponse__Output) => void): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; + getServers(argument: _grpc_channelz_v1_GetServersRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetServersResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Socket or else a NOT_FOUND code. */ - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + GetSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Socket or else a NOT_FOUND code. */ - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; - getSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSocketResponse__Output) => void): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; + getSocket(argument: _grpc_channelz_v1_GetSocketRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSocketResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Subchannel, or else a NOT_FOUND code. */ - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + GetSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; /** * Returns a single Subchannel, or else a NOT_FOUND code. */ - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; - getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetSubchannelResponse__Output) => void): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; + getSubchannel(argument: _grpc_channelz_v1_GetSubchannelRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetSubchannelResponse__Output>): grpc.ClientUnaryCall; /** * Gets all root channels (i.e. channels the application has directly * created). This does not include subchannels nor non-top level channels. */ - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + GetTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; /** * Gets all root channels (i.e. channels the application has directly * created). This does not include subchannels nor non-top level channels. */ - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; - getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_channelz_v1_GetTopChannelsResponse__Output) => void): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; + getTopChannels(argument: _grpc_channelz_v1_GetTopChannelsRequest, callback: grpc.requestCallback<_grpc_channelz_v1_GetTopChannelsResponse__Output>): grpc.ClientUnaryCall; } From 95516b66a089fbce15dedfcd30d263f6fa5687ef Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 16 Nov 2022 14:37:31 -0800 Subject: [PATCH 375/694] Fix detection of refused streams --- packages/grpc-js/src/load-balancing-call.ts | 2 +- packages/grpc-js/src/retrying-call.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index a7af83d6d..48aaf48ac 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -159,7 +159,7 @@ export class LoadBalancingCall implements Call { }, onReceiveStatus: status => { this.trace('Received status'); - if (status.code === http2.constants.NGHTTP2_REFUSED_STREAM) { + if (status.rstCode === http2.constants.NGHTTP2_REFUSED_STREAM) { this.outputStatus(status, 'REFUSED'); } else { this.outputStatus(status, 'PROCESSED'); diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index 71dedd46f..a85fac67c 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -385,7 +385,7 @@ export class RetryingCall implements Call { if (this.underlyingCalls[callIndex].state === 'COMPLETED') { return; } - this.trace('state=' + this.state + ' handling status from child [' + this.underlyingCalls[callIndex].call.getCallNumber() + '] in state ' + this.underlyingCalls[callIndex].state); + this.trace('state=' + this.state + ' handling status with progress ' + status.progress + ' from child [' + this.underlyingCalls[callIndex].call.getCallNumber() + '] in state ' + this.underlyingCalls[callIndex].state); this.underlyingCalls[callIndex].state = 'COMPLETED'; if (status.code === Status.OK) { this.retryThrottler?.addCallSucceeded(); From 47ba3578610db38c0b8c4dff1ddc009d0054a9ed Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 17 Nov 2022 09:34:16 -0800 Subject: [PATCH 376/694] Fix typo in service config validation error messages --- packages/grpc-js/src/service-config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index 2b8c0a7eb..201c0c648 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -133,14 +133,14 @@ function validateRetryPolicy(obj: any): RetryPolicy { for (const value of obj.retryableStatusCodes) { if (typeof value === 'number') { if (!Object.values(Status).includes(value)) { - throw new Error('Invlid method config retry policy: retryableStatusCodes value not in status code range'); + throw new Error('Invalid method config retry policy: retryableStatusCodes value not in status code range'); } } else if (typeof value === 'string') { if (!Object.values(Status).includes(value.toUpperCase())) { - throw new Error('Invlid method config retry policy: retryableStatusCodes value not a status code name'); + throw new Error('Invalid method config retry policy: retryableStatusCodes value not a status code name'); } } else { - throw new Error('Invlid method config retry policy: retryableStatusCodes value must be a string or number'); + throw new Error('Invalid method config retry policy: retryableStatusCodes value must be a string or number'); } } return { From f1f351f3cde6c2975785d622de63cdf9a7132984 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 17 Nov 2022 11:09:16 -0800 Subject: [PATCH 377/694] Fix handling of messages that overflow the buffer limit --- packages/grpc-js/src/retrying-call.ts | 64 ++++++++++++++++++++------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index a85fac67c..8d3bb6579 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -135,6 +135,12 @@ interface WriteBufferEntry { * state. */ callback?: WriteCallback; + /** + * Indicates whether the message is allocated in the buffer tracker. Ignored + * if entryType is not MESSAGE. Should be the return value of + * bufferTracker.allocate. + */ + allocated: boolean; } const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts'; @@ -216,6 +222,22 @@ export class RetryingCall implements Call { } } + private maybefreeMessageBufferEntry(messageIndex: number) { + if (this.state !== 'COMMITTED') { + return; + } + const bufferEntry = this.writeBuffer[messageIndex]; + if (bufferEntry.entryType === 'MESSAGE') { + if (bufferEntry.allocated) { + this.bufferTracker.free(bufferEntry.message!.message.length, this.callNumber); + } + this.writeBuffer[messageIndex] = { + entryType: 'FREED', + allocated: false + }; + } + } + private commitCall(index: number) { if (this.state === 'COMMITTED') { return; @@ -237,13 +259,7 @@ export class RetryingCall implements Call { this.underlyingCalls[i].call.cancelWithStatus(Status.CANCELLED, 'Discarded in favor of other hedged attempt'); } for (let messageIndex = 0; messageIndex < this.underlyingCalls[index].nextMessageToSend - 1; messageIndex += 1) { - const bufferEntry = this.writeBuffer[messageIndex]; - if (bufferEntry.entryType === 'MESSAGE') { - this.bufferTracker.free(bufferEntry.message!.message.length, this.callNumber); - this.writeBuffer[messageIndex] = { - entryType: 'FREED' - }; - } + this.maybefreeMessageBufferEntry(messageIndex); } } @@ -513,6 +529,15 @@ export class RetryingCall implements Call { this.maybeStartHedgingTimer(); } + private handleChildWriteCompleted(childIndex: number) { + const childCall = this.underlyingCalls[childIndex]; + const messageIndex = childCall.nextMessageToSend; + this.writeBuffer[messageIndex].callback?.(); + this.maybefreeMessageBufferEntry(messageIndex); + childCall.nextMessageToSend += 1; + this.sendNextChildMessage(childIndex); + } + private sendNextChildMessage(childIndex: number) { const childCall = this.underlyingCalls[childIndex]; if (childCall.state === 'COMPLETED') { @@ -525,8 +550,7 @@ export class RetryingCall implements Call { childCall.call.sendMessageWithContext({ callback: (error) => { // Ignore error - childCall.nextMessageToSend += 1; - this.sendNextChildMessage(childIndex); + this.handleChildWriteCompleted(childIndex); } }, bufferEntry.message!.message); break; @@ -550,25 +574,34 @@ export class RetryingCall implements Call { const messageIndex = this.writeBuffer.length; const bufferEntry: WriteBufferEntry = { entryType: 'MESSAGE', - message: writeObj + message: writeObj, + allocated: this.bufferTracker.allocate(message.length, this.callNumber) }; this.writeBuffer[messageIndex] = bufferEntry; - if (this.bufferTracker.allocate(message.length, this.callNumber)) { + if (bufferEntry.allocated) { context.callback?.(); for (const [callIndex, call] of this.underlyingCalls.entries()) { if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { call.call.sendMessageWithContext({ callback: (error) => { // Ignore error - call.nextMessageToSend += 1; - this.sendNextChildMessage(callIndex); + this.handleChildWriteCompleted(callIndex); } }, message); } } } else { this.commitCallWithMostMessages(); - bufferEntry.callback = context.callback; + const call = this.underlyingCalls[this.committedCallIndex!]; + bufferEntry.callback = context.callback; + if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { + call.call.sendMessageWithContext({ + callback: (error) => { + // Ignore error + this.handleChildWriteCompleted(this.committedCallIndex!); + } + }, message); + } } } startRead(): void { @@ -584,7 +617,8 @@ export class RetryingCall implements Call { this.trace('halfClose called'); const halfCloseIndex = this.writeBuffer.length; this.writeBuffer[halfCloseIndex] = { - entryType: 'HALF_CLOSE' + entryType: 'HALF_CLOSE', + allocated: false }; for (const call of this.underlyingCalls) { if (call?.state === 'ACTIVE' && call.nextMessageToSend === halfCloseIndex) { From fa21e13ef38dd7cbc44fd1156fd97169c876025e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 17 Nov 2022 11:51:49 -0800 Subject: [PATCH 378/694] Limit maxAttempts to 5 for retries and hedging --- packages/grpc-js/src/retrying-call.ts | 6 +-- packages/grpc-js/test/test-retry.ts | 63 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index 8d3bb6579..f9bda9eb5 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -296,7 +296,7 @@ export class RetryingCall implements Call { return; } const retryPolicy = this.callConfig!.methodConfig.retryPolicy!; - if (this.attempts >= retryPolicy.maxAttempts) { + if (this.attempts >= Math.min(retryPolicy.maxAttempts, 5)) { callback(false); return; } @@ -446,7 +446,7 @@ export class RetryingCall implements Call { return; } const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy; - if (this.attempts >= hedgingPolicy.maxAttempts) { + if (this.attempts >= Math.min(hedgingPolicy.maxAttempts, 5)) { return; } this.attempts += 1; @@ -465,7 +465,7 @@ export class RetryingCall implements Call { return; } const hedgingPolicy = this.callConfig.methodConfig.hedgingPolicy; - if (this.attempts >= hedgingPolicy.maxAttempts) { + if (this.attempts >= Math.min(hedgingPolicy.maxAttempts, 5)) { return; } const hedgingDelayString = hedgingPolicy.hedgingDelay ?? '0s'; diff --git a/packages/grpc-js/test/test-retry.ts b/packages/grpc-js/test/test-retry.ts index 4dd96cc43..66c0f7941 100644 --- a/packages/grpc-js/test/test-retry.ts +++ b/packages/grpc-js/test/test-retry.ts @@ -216,6 +216,39 @@ describe('Retries', () => { } ); }); + + it('Should not be able to make more than 5 attempts', (done) => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + retryPolicy: { + maxAttempts: 10, + initialBackoff: '0.1s', + maxBackoff: '10s', + backoffMultiplier: 1.2, + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + const client2 = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '6'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client2.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.details, 'Failed on retry 4'); + done(); + } + ); + }) }); describe('Client with hedging configured', () => { @@ -297,5 +330,35 @@ describe('Retries', () => { } ); }); + + it('Should not be able to make more than 5 attempts', (done) => { + const serviceConfig = { + loadBalancingConfig: [], + methodConfig: [ + { + name: [{ + service: 'EchoService' + }], + hedgingPolicy: { + maxAttempts: 10, + nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + } + } + ] + } + const client2 = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + const metadata = new grpc.Metadata(); + metadata.set('succeed-on-retry-attempt', '6'); + metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); + client2.echo( + { value: 'test value', value2: 3 }, + metadata, + (error: grpc.ServiceError, response: any) => { + assert(error); + assert(error.details.startsWith('Failed on retry')); + done(); + } + ); + }) }); }); \ No newline at end of file From 641ed45d489811740dc84dccc2fbf9d460a96b7a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 18 Nov 2022 15:06:41 -0800 Subject: [PATCH 379/694] grpc-js-xds: Update failure mode behavior --- packages/grpc-js-xds/src/xds-client.ts | 42 +++++++++++++++---- .../src/xds-stream-state/xds-stream-state.ts | 3 ++ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 439ed80ea..2dfa41236 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -18,7 +18,7 @@ import * as protoLoader from '@grpc/proto-loader'; // This is a non-public, unstable API, but it's very convenient import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; -import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel } from '@grpc/grpc-js'; +import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel, connectivityState } from '@grpc/grpc-js'; import * as adsTypes from './generated/ads'; import * as lrsTypes from './generated/lrs'; import { loadBootstrapInfo } from './xds-bootstrap'; @@ -255,6 +255,7 @@ export class XdsClient { DiscoveryRequest, DiscoveryResponse__Output > | null = null; + private receivedAdsResponseOnCurrentStream = false; private lrsNode: Node | null = null; private lrsClient: LoadReportingServiceClient | null = null; @@ -373,6 +374,9 @@ export class XdsClient { {channelOverride: channel} ); this.maybeStartAdsStream(); + channel.watchConnectivityState(channel.getConnectivityState(false), Infinity, () => { + this.handleAdsConnectivityStateUpdate(); + }) this.lrsClient = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( serverUri, @@ -394,7 +398,29 @@ export class XdsClient { clearInterval(this.statsTimer); } + private handleAdsConnectivityStateUpdate() { + if (!this.adsClient) { + return; + } + const state = this.adsClient.getChannel().getConnectivityState(false); + if (state === connectivityState.READY && this.adsCall) { + this.reportAdsStreamStarted(); + } + if (state === connectivityState.TRANSIENT_FAILURE) { + this.reportStreamError({ + code: status.UNAVAILABLE, + details: 'No connection established to xDS server', + metadata: new Metadata() + }); + } + this.adsClient.getChannel().watchConnectivityState(state, Infinity, () => { + this.handleAdsConnectivityStateUpdate(); + }); + } + private handleAdsResponse(message: DiscoveryResponse__Output) { + this.receivedAdsResponseOnCurrentStream = true; + this.adsBackoff.reset(); let handleResponseResult: { result: HandleResponseResult; serviceKind: AdsServiceKind; @@ -466,7 +492,7 @@ export class XdsClient { 'ADS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details ); this.adsCall = null; - if (streamStatus.code !== status.OK) { + if (streamStatus.code !== status.OK && !this.receivedAdsResponseOnCurrentStream) { this.reportStreamError(streamStatus); } /* If the backoff timer is no longer running, we do not need to wait any @@ -496,7 +522,9 @@ export class XdsClient { if (this.adsCall !== null) { return; } - this.adsCall = this.adsClient.StreamAggregatedResources(); + this.receivedAdsResponseOnCurrentStream = false; + const metadata = new Metadata({waitForReady: true}); + this.adsCall = this.adsClient.StreamAggregatedResources(metadata); this.adsCall.on('data', (message: DiscoveryResponse__Output) => { this.handleAdsResponse(message); }); @@ -515,7 +543,9 @@ export class XdsClient { this.updateNames(service); } } - this.reportAdsStreamStarted(); + if (this.adsClient.getChannel().getConnectivityState(false) === connectivityState.READY) { + this.reportAdsStreamStarted(); + } } private maybeSendAdsMessage(typeUrl: string, resourceNames: string[], responseNonce: string, versionInfo: string, errorMessage?: string) { @@ -547,10 +577,6 @@ export class XdsClient { * version info are updated so that it sends the post-update values. */ ack(serviceKind: AdsServiceKind) { - /* An ack is the best indication of a successful interaction between the - * client and the server, so we can reset the backoff timer here. */ - this.adsBackoff.reset(); - this.updateNames(serviceKind); } diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 86c2cea4b..e20bc7e9b 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -213,6 +213,9 @@ export abstract class BaseXdsStreamState implements XdsStreamState } reportAdsStreamStart() { + if (this.isAdsStreamRunning) { + return; + } this.isAdsStreamRunning = true; for (const subscriptionEntry of this.subscriptions.values()) { if (subscriptionEntry.cachedResponse === null) { From 6b4dd60f1130f1f8aea6c4df111666cb3965f024 Mon Sep 17 00:00:00 2001 From: natiz Date: Sun, 27 Nov 2022 23:37:56 +0200 Subject: [PATCH 380/694] fix: windows build --- packages/grpc-tools/CMakeLists.txt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt index 9d6690ba7..14fb3c943 100644 --- a/packages/grpc-tools/CMakeLists.txt +++ b/packages/grpc-tools/CMakeLists.txt @@ -1,17 +1,21 @@ -cmake_minimum_required(VERSION 3.6) +cmake_minimum_required(VERSION 3.15) if(COMMAND cmake_policy) cmake_policy(SET CMP0003 NEW) endif(COMMAND cmake_policy) +# MSVC runtime library flags are selected by an abstraction. +if(COMMAND cmake_policy AND POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() + set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(PROTOBUF_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/protobuf) -add_subdirectory(${PROTOBUF_ROOT_DIR}/cmake deps/protobuf) - set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf tests") set(protobuf_WITH_ZLIB OFF CACHE BOOL "Build protobuf with zlib.") +set(PROTOBUF_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/protobuf) +add_subdirectory(${PROTOBUF_ROOT_DIR}/cmake deps/protobuf) set(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector") @@ -22,7 +26,7 @@ add_executable(grpc_node_plugin ) if (MSVC) - add_definitions(/MTd) + set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) endif (MSVC) target_include_directories(grpc_node_plugin @@ -34,4 +38,4 @@ target_include_directories(grpc_node_plugin target_link_libraries(grpc_node_plugin libprotoc libprotobuf -) \ No newline at end of file +) From edf612a56af58b3f8829bb78741639d75b19db3e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 29 Nov 2022 14:29:47 -0500 Subject: [PATCH 381/694] grpc-js-xds: Implement retry support --- packages/grpc-js-xds/src/environment.ts | 3 +- packages/grpc-js-xds/src/resolver-xds.ts | 54 +++++++++++++++++-- packages/grpc-js-xds/src/route-action.ts | 31 ++++------- .../src/xds-stream-state/rds-state.ts | 43 ++++++++++++++- packages/grpc-js/src/experimental.ts | 2 +- 5 files changed, 104 insertions(+), 29 deletions(-) diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 250f791ac..47222f8a4 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -16,4 +16,5 @@ */ export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; -export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; \ No newline at end of file +export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; +export const EXPERIMENTAL_RETRY = process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY === 'true'; \ No newline at end of file diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 496c37094..401465be6 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -44,9 +44,10 @@ import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resourc import Duration = experimental.Duration; import { Duration__Output } from './generated/google/protobuf/Duration'; import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter'; -import { EXPERIMENTAL_FAULT_INJECTION } from './environment'; +import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; +import RetryPolicy = experimental.RetryPolicy; const TRACER_NAME = 'xds_resolver'; @@ -199,6 +200,24 @@ function protoDurationToDuration(duration: Duration__Output): Duration { } } +function protoDurationToSecondsString(duration: Duration__Output): string { + return `${duration.seconds + duration.nanos / 1_000_000_000}s`; +} + +const DEFAULT_RETRY_BASE_INTERVAL = '0.025s' + +function getDefaultRetryMaxInterval(baseInterval: string): string { + return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`; +} + +const RETRY_CODES: {[key: string]: status} = { + 'cancelled': status.CANCELLED, + 'deadline-exceeded': status.DEADLINE_EXCEEDED, + 'internal': status.INTERNAL, + 'resource-exhausted': status.RESOURCE_EXHAUSTED, + 'unavailable': status.UNAVAILABLE +}; + class XdsResolver implements Resolver { private hasReportedSuccess = false; @@ -363,6 +382,33 @@ class XdsResolver implements Resolver { } } } + let retryPolicy: RetryPolicy | undefined = undefined; + if (EXPERIMENTAL_RETRY) { + const retryConfig = route.route!.retry_policy ?? virtualHost.retry_policy; + if (retryConfig) { + const retryableStatusCodes = []; + for (const code of retryConfig.retry_on.split(',')) { + if (RETRY_CODES[code]) { + retryableStatusCodes.push(RETRY_CODES[code]); + } + } + if (retryableStatusCodes.length > 0) { + const baseInterval = retryConfig.retry_back_off?.base_interval ? + protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) : + DEFAULT_RETRY_BASE_INTERVAL; + const maxInterval = retryConfig.retry_back_off?.max_interval ? + protoDurationToSecondsString(retryConfig.retry_back_off.max_interval) : + getDefaultRetryMaxInterval(baseInterval); + retryPolicy = { + backoffMultiplier: 2, + initialBackoff: baseInterval, + maxBackoff: maxInterval, + maxAttempts: (retryConfig.num_retries?.value ?? 1) + 1, + retryableStatusCodes: retryableStatusCodes + }; + } + } + } switch (route.route!.cluster_specifier) { case 'cluster_header': continue; @@ -390,7 +436,7 @@ class XdsResolver implements Resolver { } } } - routeAction = new SingleClusterRouteAction(cluster, timeout, extraFilterFactories); + routeAction = new SingleClusterRouteAction(cluster, {name: [], timeout: timeout, retryPolicy: retryPolicy}, extraFilterFactories); break; } case 'weighted_clusters': { @@ -432,7 +478,7 @@ class XdsResolver implements Resolver { } weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, dynamicFilterFactories: extraFilterFactories}); } - routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, timeout); + routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, {name: [], timeout: timeout, retryPolicy: retryPolicy}); break; } default: @@ -470,7 +516,7 @@ class XdsResolver implements Resolver { this.unrefCluster(clusterResult.name); } return { - methodConfig: {name: [], timeout: action.getTimeout()}, + methodConfig: clusterResult.methodConfig, onCommitted: onCommitted, pickInformation: {cluster: clusterResult.name}, status: status.OK, diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts index d29e67b9b..5ae5885af 100644 --- a/packages/grpc-js-xds/src/route-action.ts +++ b/packages/grpc-js-xds/src/route-action.ts @@ -18,16 +18,17 @@ import { experimental } from '@grpc/grpc-js'; import Duration = experimental.Duration; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; +import MethodConfig = experimental.MethodConfig; export interface ClusterResult { name: string; + methodConfig: MethodConfig; dynamicFilterFactories: FilterFactory[]; } export interface RouteAction { toString(): string; getCluster(): ClusterResult; - getTimeout(): Duration | undefined; } function durationToLogString(duration: Duration) { @@ -40,25 +41,18 @@ function durationToLogString(duration: Duration) { } export class SingleClusterRouteAction implements RouteAction { - constructor(private cluster: string, private timeout: Duration | undefined, private extraFilterFactories: FilterFactory[]) {} + constructor(private cluster: string, private methodConfig: MethodConfig, private extraFilterFactories: FilterFactory[]) {} getCluster() { return { name: this.cluster, + methodConfig: this.methodConfig, dynamicFilterFactories: this.extraFilterFactories }; } toString() { - if (this.timeout) { - return 'SingleCluster(' + this.cluster + ', ' + 'timeout=' + durationToLogString(this.timeout) + 's)'; - } else { - return 'SingleCluster(' + this.cluster + ')'; - } - } - - getTimeout() { - return this.timeout; + return 'SingleCluster(' + this.cluster + ', ' + JSON.stringify(this.methodConfig) + ')'; } } @@ -79,7 +73,7 @@ export class WeightedClusterRouteAction implements RouteAction { * The weighted cluster choices represented as a CDF */ private clusterChoices: ClusterChoice[]; - constructor(private clusters: WeightedCluster[], private totalWeight: number, private timeout: Duration | undefined) { + constructor(private clusters: WeightedCluster[], private totalWeight: number, private methodConfig: MethodConfig) { this.clusterChoices = []; let lastNumerator = 0; for (const clusterWeight of clusters) { @@ -94,24 +88,17 @@ export class WeightedClusterRouteAction implements RouteAction { if (randomNumber < choice.numerator) { return { name: choice.name, + methodConfig: this.methodConfig, dynamicFilterFactories: choice.dynamicFilterFactories }; } } // This should be prevented by the validation rules - return {name: '', dynamicFilterFactories: []}; + return {name: '', methodConfig: this.methodConfig, dynamicFilterFactories: []}; } toString() { const clusterListString = this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ') - if (this.timeout) { - return 'WeightedCluster(' + clusterListString + ', ' + 'timeout=' + durationToLogString(this.timeout) + 's)'; - } else { - return 'WeightedCluster(' + clusterListString + ')'; - } - } - - getTimeout() { - return this.timeout; + return 'WeightedCluster(' + clusterListString + ', ' + JSON.stringify(this.methodConfig) + ')'; } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 119ac6b92..891eb7c8e 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -15,8 +15,10 @@ * */ -import { EXPERIMENTAL_FAULT_INJECTION } from "../environment"; +import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from "../environment"; +import { RetryPolicy__Output } from "../generated/envoy/config/route/v3/RetryPolicy"; import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; +import { Duration__Output } from "../generated/google/protobuf/Duration"; import { validateOverrideFilter } from "../http-filter"; import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; @@ -30,6 +32,13 @@ const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ 'suffix_match']; const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; +function durationToMs(duration: Duration__Output | null): number | null { + if (duration === null) { + return null; + } + return (Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000) | 0; +} + export class RdsState extends BaseXdsStreamState implements XdsStreamState { protected isStateOfTheWorld(): boolean { return false; @@ -40,6 +49,28 @@ export class RdsState extends BaseXdsStreamState imp protected getProtocolName(): string { return 'RDS'; } + + private validateRetryPolicy(policy: RetryPolicy__Output | null): boolean { + if (policy === null) { + return true; + } + const numRetries = policy.num_retries?.value ?? 1 + if (numRetries < 1) { + return false; + } + if (policy.retry_back_off) { + if (!policy.retry_back_off.base_interval) { + return false; + } + const baseInterval = durationToMs(policy.retry_back_off.base_interval)!; + const maxInterval = durationToMs(policy.retry_back_off.max_interval) ?? (10 * baseInterval); + if (!(maxInterval >= baseInterval) && (baseInterval > 0)) { + return false; + } + } + return true; + } + validateResponse(message: RouteConfiguration__Output): boolean { // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation for (const virtualHost of message.virtual_hosts) { @@ -62,6 +93,11 @@ export class RdsState extends BaseXdsStreamState imp } } } + if (EXPERIMENTAL_RETRY) { + if (!this.validateRetryPolicy(virtualHost.retry_policy)) { + return false; + } + } for (const route of virtualHost.routes) { const match = route.match; if (!match) { @@ -88,6 +124,11 @@ export class RdsState extends BaseXdsStreamState imp } } } + if (EXPERIMENTAL_RETRY) { + if (!this.validateRetryPolicy(route.route.retry_policy)) { + return false; + } + } if (route.route!.cluster_specifier === 'weighted_clusters') { if (route.route.weighted_clusters!.total_weight?.value === 0) { return false; diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 92d6d44c6..a7c28219b 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -7,7 +7,7 @@ export { } from './resolver'; export { GrpcUri, uriToString } from './uri-parser'; export { Duration, durationToMs } from './duration'; -export { ServiceConfig } from './service-config'; +export { ServiceConfig, MethodConfig, RetryPolicy } from './service-config'; export { BackoffTimeout } from './backoff-timeout'; export { LoadBalancer, From 6f755fe3469b888e7c0bca775f8eac430d0c8fb7 Mon Sep 17 00:00:00 2001 From: Taegeun Moon Date: Wed, 30 Nov 2022 22:31:22 +0900 Subject: [PATCH 382/694] add branded option for proto-loader-gen-types --- packages/proto-loader/bin/proto-loader-gen-types.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index a9ee4a6ed..ae7e147b5 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -45,6 +45,7 @@ type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeComments?: boolean; inputTemplate: string; outputTemplate: string; + branded: boolean; } class TextFormatter { @@ -263,6 +264,9 @@ function generatePermissiveMessageInterface(formatter: TextFormatter, messageTyp } formatter.writeLine(`'${oneof.name}'?: ${typeString};`); } + if (options.branded) { + formatter.writeLine(`__type: '${messageType.fullName}'`) + } formatter.unindent(); formatter.writeLine('}'); } @@ -383,6 +387,9 @@ function generateRestrictedMessageInterface(formatter: TextFormatter, messageTyp formatter.writeLine(`'${oneof.name}': ${typeString};`); } } + if (options.branded) { + formatter.writeLine(`__type: '${messageType.fullName}'`) + } formatter.unindent(); formatter.writeLine('}'); } @@ -815,7 +822,7 @@ async function runScript() { .string(['includeDirs', 'grpcLib']) .normalize(['includeDirs', 'outDir']) .array('includeDirs') - .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments']) + .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments', 'branded']) .string(['longs', 'enums', 'bytes', 'inputTemplate', 'outputTemplate']) .default('keepCase', false) .default('defaults', false) @@ -829,6 +836,7 @@ async function runScript() { .default('bytes', 'Buffer') .default('inputTemplate', `${templateStr}`) .default('outputTemplate', `${templateStr}__Output`) + .default('branded', false) .coerce('longs', value => { switch (value) { case 'String': return String; @@ -868,6 +876,7 @@ async function runScript() { grpcLib: 'The gRPC implementation library that these types will be used with', inputTemplate: 'Template for mapping input or "permissive" type names', outputTemplate: 'Template for mapping output or "restricted" type names', + branded: 'Emit property for branded type whose value is fullName of the Message', }).demandOption(['outDir', 'grpcLib']) .demand(1) .usage('$0 [options] filenames...') From 9e548d4d87c55f2710aa5a62968fa1df565da5d4 Mon Sep 17 00:00:00 2001 From: Taegeun Moon Date: Wed, 30 Nov 2022 22:34:15 +0900 Subject: [PATCH 383/694] update readme --- packages/proto-loader/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/proto-loader/README.md b/packages/proto-loader/README.md index 818f0efda..740658786 100644 --- a/packages/proto-loader/README.md +++ b/packages/proto-loader/README.md @@ -92,6 +92,8 @@ Options: [string] [default: "%s"] --outputTemplate Template for mapping output or "restricted" type names [string] [default: "%s__Output"] + --branded Emit property for branded type whose value is fullName + of the Message [boolean] [default: false] ``` ### Example Usage From 87e70e890bc37c50791c03fb6dd18d1728f1448e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 30 Nov 2022 13:59:03 -0500 Subject: [PATCH 384/694] grpc-tools: Update native build docker image --- tools/release/native/Dockerfile | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/release/native/Dockerfile b/tools/release/native/Dockerfile index d065bddbe..7346d9aff 100644 --- a/tools/release/native/Dockerfile +++ b/tools/release/native/Dockerfile @@ -1,11 +1,7 @@ -FROM debian:jessie +FROM debian:stretch -RUN echo "deb http://archive.debian.org/debian jessie-backports main" > /etc/apt/sources.list.d/backports.list -RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf -RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list RUN apt-get update -RUN apt-get -t jessie-backports install -y cmake -RUN apt-get install -y curl build-essential python libc6-dev-i386 lib32stdc++-4.9-dev jq +RUN apt-get install -y cmake curl build-essential python libc6-dev-i386 lib32stdc++-6-dev jq RUN mkdir /usr/local/nvm ENV NVM_DIR /usr/local/nvm From b07b74bfd4c1e6886b046371772fc14b98c34e63 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 30 Nov 2022 15:40:49 -0500 Subject: [PATCH 385/694] Update information in README and PACKAGE-COMPARISON docs --- PACKAGE-COMPARISON.md | 10 +++++----- README.md | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/PACKAGE-COMPARISON.md b/PACKAGE-COMPARISON.md index f0c444195..cd6308892 100644 --- a/PACKAGE-COMPARISON.md +++ b/PACKAGE-COMPARISON.md @@ -1,6 +1,6 @@ # Feature comparison of `grpc` and `@grpc/grpc-js` packages -Feature | `grpc` | `@grpc/grpc-js` +Feature | `grpc` (deprecated) | `@grpc/grpc-js` --------|--------|---------- Client | :heavy_check_mark: | :heavy_check_mark: Server | :heavy_check_mark: | :heavy_check_mark: @@ -9,7 +9,7 @@ Streaming RPCs | :heavy_check_mark: | :heavy_check_mark: Deadlines | :heavy_check_mark: | :heavy_check_mark: Cancellation | :heavy_check_mark: | :heavy_check_mark: Automatic Reconnection | :heavy_check_mark: | :heavy_check_mark: -Per-message Compression | :heavy_check_mark: | only for response messages +Per-message Compression | :heavy_check_mark: | :heavy_check_mark: (except messages sent by the server) Channel State | :heavy_check_mark: | :heavy_check_mark: JWT Access and Service Account Credentials | provided by the [Google Auth Library](https://www.npmjs.com/package/google-auth-library) | provided by the [Google Auth Library](https://www.npmjs.com/package/google-auth-library) Interceptors | :heavy_check_mark: | :heavy_check_mark: @@ -17,14 +17,14 @@ Connection Keepalives | :heavy_check_mark: | :heavy_check_mark: HTTP Connect Support | :heavy_check_mark: | :heavy_check_mark: Retries | :heavy_check_mark: | :x: Stats/tracing/monitoring | :heavy_check_mark: | :x: -Load Balancing | :heavy_check_mark: | Pick first and round robin +Load Balancing | :heavy_check_mark: | :heavy_check_mark: Initial Metadata Options | :heavy_check_mark: | only `waitForReady` Other Properties | `grpc` | `@grpc/grpc-js` -----------------|--------|---------------- Pure JavaScript Code | :x: | :heavy_check_mark: -Supported Node Versions | >= 4 | ^8.13.0 or >=10.10.0 -Supported Electron Versions | All | >= 3 +Supported Node Versions | >= 4 and <=14 | ^8.13.0 or >=10.10.0 +Supported Electron Versions | <=11.2 | >= 3 Supported Platforms | Linux, Windows, MacOS | All Supported Architectures | x86, x86-64, ARM7+ | All diff --git a/README.md b/README.md index 159fbab4f..f0f215ce3 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,21 @@ For a comparison of the features available in these two libraries, see [this document](https://github.com/grpc/grpc-node/tree/master/PACKAGE-COMPARISON.md) -### C-based Client and Server +### Pure JavaScript Client and Server -Directory: [`packages/grpc-native-core`](https://github.com/grpc/grpc-node/tree/grpc@1.24.x/packages/grpc-native-core) (lives in the `grpc@1.24.x` branch) (see here for installation information) +Directory: [`packages/grpc-js`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) -npm package: [grpc](https://www.npmjs.com/package/grpc). +npm package: [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js) -This is the existing, feature-rich implementation of gRPC using a C++ addon. It works on all LTS versions of Node.js on most platforms that Node.js runs on. +This library implements the core functionality of gRPC purely in JavaScript, without a C++ addon. It works on the latest versions of Node.js on all platforms that Node.js runs on. -### Pure JavaScript Client +### C-based Client and Server (deprecated) -Directory: [`packages/grpc-js`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) +Directory: [`packages/grpc-native-core`](https://github.com/grpc/grpc-node/tree/grpc@1.24.x/packages/grpc-native-core) (lives in the `grpc@1.24.x` branch) (see here for installation information) -npm package: [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js) +npm package: [grpc](https://www.npmjs.com/package/grpc). -This library implements the core functionality of gRPC purely in JavaScript, without a C++ addon. It works on the latest version of Node.js on all platforms that Node.js runs on. +This is the deprecated implementation of gRPC using a C++ addon. It works on versions of Node.js up to 14 on most platforms that Node.js runs on. ## Other Packages From e955c47bd51332e5cf08ac3b3b02c396ebe124f0 Mon Sep 17 00:00:00 2001 From: Taegeun Moon Date: Thu, 1 Dec 2022 11:09:09 +0900 Subject: [PATCH 386/694] rename as outputBranded --- .../bin/proto-loader-gen-types.ts | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index ae7e147b5..6327da951 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -37,6 +37,9 @@ const useNameFmter = ({outputTemplate, inputTemplate}: GeneratorOptions) => { }; } +const typeBrandHint = `This field is a type brand and is not populated at runtime. Instances of this type should be created using type assertions. +https://github.com/grpc/grpc-node/pull/2281`; + type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeDirs?: string[]; grpcLib: string; @@ -45,7 +48,7 @@ type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeComments?: boolean; inputTemplate: string; outputTemplate: string; - branded: boolean; + outputBranded: boolean; } class TextFormatter { @@ -179,6 +182,11 @@ function formatComment(formatter: TextFormatter, comment?: string | null) { formatter.writeLine(' */'); } +function formatTypeBrand(formatter: TextFormatter, messageType: Protobuf.Type) { + formatComment(formatter, typeBrandHint); + formatter.writeLine(`__type: '${messageType.fullName}'`); +} + // GENERATOR FUNCTIONS function getTypeNamePermissive(fieldType: string, resolvedType: Protobuf.Type | Protobuf.Enum | null, repeated: boolean, map: boolean, options: GeneratorOptions): string { @@ -264,9 +272,9 @@ function generatePermissiveMessageInterface(formatter: TextFormatter, messageTyp } formatter.writeLine(`'${oneof.name}'?: ${typeString};`); } - if (options.branded) { - formatter.writeLine(`__type: '${messageType.fullName}'`) - } + // if (options.inputBranded) { + // formatTypeBrand(formatter, messageType); + // } formatter.unindent(); formatter.writeLine('}'); } @@ -387,8 +395,8 @@ function generateRestrictedMessageInterface(formatter: TextFormatter, messageTyp formatter.writeLine(`'${oneof.name}': ${typeString};`); } } - if (options.branded) { - formatter.writeLine(`__type: '${messageType.fullName}'`) + if (options.outputBranded) { + formatTypeBrand(formatter, messageType); } formatter.unindent(); formatter.writeLine('}'); @@ -822,7 +830,7 @@ async function runScript() { .string(['includeDirs', 'grpcLib']) .normalize(['includeDirs', 'outDir']) .array('includeDirs') - .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments', 'branded']) + .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments', 'outputBranded']) .string(['longs', 'enums', 'bytes', 'inputTemplate', 'outputTemplate']) .default('keepCase', false) .default('defaults', false) @@ -836,7 +844,7 @@ async function runScript() { .default('bytes', 'Buffer') .default('inputTemplate', `${templateStr}`) .default('outputTemplate', `${templateStr}__Output`) - .default('branded', false) + .default('outputBranded', false) .coerce('longs', value => { switch (value) { case 'String': return String; @@ -876,7 +884,7 @@ async function runScript() { grpcLib: 'The gRPC implementation library that these types will be used with', inputTemplate: 'Template for mapping input or "permissive" type names', outputTemplate: 'Template for mapping output or "restricted" type names', - branded: 'Emit property for branded type whose value is fullName of the Message', + outputBranded: 'Output property for branded type for "restricted" types with fullName of the Message as its value', }).demandOption(['outDir', 'grpcLib']) .demand(1) .usage('$0 [options] filenames...') From 927c29de4ad8ac3f8642b6468fdd59d0cf587361 Mon Sep 17 00:00:00 2001 From: Taegeun Moon Date: Thu, 1 Dec 2022 12:37:48 +0900 Subject: [PATCH 387/694] support both input and output update readme update readme --- packages/proto-loader/README.md | 8 ++++++-- packages/proto-loader/bin/proto-loader-gen-types.ts | 13 +++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/proto-loader/README.md b/packages/proto-loader/README.md index 740658786..99b34a05e 100644 --- a/packages/proto-loader/README.md +++ b/packages/proto-loader/README.md @@ -92,8 +92,12 @@ Options: [string] [default: "%s"] --outputTemplate Template for mapping output or "restricted" type names [string] [default: "%s__Output"] - --branded Emit property for branded type whose value is fullName - of the Message [boolean] [default: false] + --inputBranded Output property for branded type for "permissive" + types with fullName of the Message as its value + [boolean] + --outputBranded Output property for branded type for "restricted" + types with fullName of the Message as its value + [boolean] ``` ### Example Usage diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 6327da951..dc9da1a9d 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -48,7 +48,8 @@ type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeComments?: boolean; inputTemplate: string; outputTemplate: string; - outputBranded: boolean; + inputBranded?: boolean; + outputBranded?: boolean; } class TextFormatter { @@ -272,9 +273,9 @@ function generatePermissiveMessageInterface(formatter: TextFormatter, messageTyp } formatter.writeLine(`'${oneof.name}'?: ${typeString};`); } - // if (options.inputBranded) { - // formatTypeBrand(formatter, messageType); - // } + if (options.inputBranded) { + formatTypeBrand(formatter, messageType); + } formatter.unindent(); formatter.writeLine('}'); } @@ -830,7 +831,7 @@ async function runScript() { .string(['includeDirs', 'grpcLib']) .normalize(['includeDirs', 'outDir']) .array('includeDirs') - .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments', 'outputBranded']) + .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments', 'inputBranded', 'outputBranded']) .string(['longs', 'enums', 'bytes', 'inputTemplate', 'outputTemplate']) .default('keepCase', false) .default('defaults', false) @@ -844,7 +845,6 @@ async function runScript() { .default('bytes', 'Buffer') .default('inputTemplate', `${templateStr}`) .default('outputTemplate', `${templateStr}__Output`) - .default('outputBranded', false) .coerce('longs', value => { switch (value) { case 'String': return String; @@ -884,6 +884,7 @@ async function runScript() { grpcLib: 'The gRPC implementation library that these types will be used with', inputTemplate: 'Template for mapping input or "permissive" type names', outputTemplate: 'Template for mapping output or "restricted" type names', + inputBranded: 'Output property for branded type for "permissive" types with fullName of the Message as its value', outputBranded: 'Output property for branded type for "restricted" types with fullName of the Message as its value', }).demandOption(['outDir', 'grpcLib']) .demand(1) From 256fbd89150e1f69e96a5d631c97745d5f51b169 Mon Sep 17 00:00:00 2001 From: Taegeun Moon Date: Thu, 1 Dec 2022 13:27:04 +0900 Subject: [PATCH 388/694] set defaults for brand option --- packages/proto-loader/README.md | 12 ++++++------ .../proto-loader/bin/proto-loader-gen-types.ts | 14 +++++++++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/proto-loader/README.md b/packages/proto-loader/README.md index 99b34a05e..f10831eeb 100644 --- a/packages/proto-loader/README.md +++ b/packages/proto-loader/README.md @@ -63,6 +63,12 @@ proto-loader-gen-types.js [options] filenames... Options: --help Show help [boolean] --version Show version number [boolean] + --inputBranded Output property for branded type for "permissive" + types with fullName of the Message as its value + [boolean] [default: false] + --outputBranded Output property for branded type for "restricted" + types with fullName of the Message as its value + [boolean] [default: false] --keepCase Preserve the case of field names [boolean] [default: false] --longs The type that should be used to output 64 bit integer @@ -92,12 +98,6 @@ Options: [string] [default: "%s"] --outputTemplate Template for mapping output or "restricted" type names [string] [default: "%s__Output"] - --inputBranded Output property for branded type for "permissive" - types with fullName of the Message as its value - [boolean] - --outputBranded Output property for branded type for "restricted" - types with fullName of the Message as its value - [boolean] ``` ### Example Usage diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index dc9da1a9d..465f362fe 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -48,8 +48,8 @@ type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeComments?: boolean; inputTemplate: string; outputTemplate: string; - inputBranded?: boolean; - outputBranded?: boolean; + inputBranded: boolean; + outputBranded: boolean; } class TextFormatter { @@ -831,7 +831,7 @@ async function runScript() { .string(['includeDirs', 'grpcLib']) .normalize(['includeDirs', 'outDir']) .array('includeDirs') - .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments', 'inputBranded', 'outputBranded']) + .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments']) .string(['longs', 'enums', 'bytes', 'inputTemplate', 'outputTemplate']) .default('keepCase', false) .default('defaults', false) @@ -864,6 +864,14 @@ async function runScript() { default: return undefined; } }) + .option('inputBranded', { + boolean: true, + default: false, + }) + .option('outputBranded', { + boolean: true, + default: false, + }) .alias({ includeDirs: 'I', outDir: 'O', From 80332044c73ba0789efc563b329fd9c9257cad6b Mon Sep 17 00:00:00 2001 From: Taegeun Moon Date: Thu, 1 Dec 2022 14:12:10 +0900 Subject: [PATCH 389/694] update typeBrandHint location --- packages/proto-loader/bin/proto-loader-gen-types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 465f362fe..5a9a32f88 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -37,9 +37,6 @@ const useNameFmter = ({outputTemplate, inputTemplate}: GeneratorOptions) => { }; } -const typeBrandHint = `This field is a type brand and is not populated at runtime. Instances of this type should be created using type assertions. -https://github.com/grpc/grpc-node/pull/2281`; - type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeDirs?: string[]; grpcLib: string; @@ -183,6 +180,9 @@ function formatComment(formatter: TextFormatter, comment?: string | null) { formatter.writeLine(' */'); } +const typeBrandHint = `This field is a type brand and is not populated at runtime. Instances of this type should be created using type assertions. +https://github.com/grpc/grpc-node/pull/2281`; + function formatTypeBrand(formatter: TextFormatter, messageType: Protobuf.Type) { formatComment(formatter, typeBrandHint); formatter.writeLine(`__type: '${messageType.fullName}'`); From 8ce5bf8c247fa41cba94e4134b44f9001358b227 Mon Sep 17 00:00:00 2001 From: natiz Date: Thu, 1 Dec 2022 14:21:22 +0200 Subject: [PATCH 390/694] fix: lower cmake version to 3.7 --- packages/grpc-tools/CMakeLists.txt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt index 14fb3c943..555346912 100644 --- a/packages/grpc-tools/CMakeLists.txt +++ b/packages/grpc-tools/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.7) if(COMMAND cmake_policy) cmake_policy(SET CMP0003 NEW) endif(COMMAND cmake_policy) @@ -26,8 +26,21 @@ add_executable(grpc_node_plugin ) if (MSVC) - set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) -endif (MSVC) + if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.15) + set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) + else () + foreach (flag_var + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif (${flag_var} MATCHES "/MD") + endforeach (flag_var) + endif () +endif (MVC) target_include_directories(grpc_node_plugin PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} From df8c719ceb881488026a56147d5d4072c816cf25 Mon Sep 17 00:00:00 2001 From: natiz Date: Thu, 1 Dec 2022 18:30:49 +0200 Subject: [PATCH 391/694] chore: bump to 1.12.0 --- packages/grpc-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index 1c7deb250..333ef1eb1 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.11.3", + "version": "1.12.0", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", From 161af8ca7b174e96c4703a6c83d75c43fbd05ffc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 1 Dec 2022 15:53:10 -0500 Subject: [PATCH 392/694] grpc-js: Prepare for 1.8.0 release De-experimentalize xDS retry support, and update versions and documentation --- PACKAGE-COMPARISON.md | 2 +- packages/grpc-js-xds/README.md | 3 ++- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js-xds/src/environment.ts | 2 +- packages/grpc-js/package.json | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/PACKAGE-COMPARISON.md b/PACKAGE-COMPARISON.md index cd6308892..e6cee8934 100644 --- a/PACKAGE-COMPARISON.md +++ b/PACKAGE-COMPARISON.md @@ -15,7 +15,7 @@ JWT Access and Service Account Credentials | provided by the [Google Auth Librar Interceptors | :heavy_check_mark: | :heavy_check_mark: Connection Keepalives | :heavy_check_mark: | :heavy_check_mark: HTTP Connect Support | :heavy_check_mark: | :heavy_check_mark: -Retries | :heavy_check_mark: | :x: +Retries | :heavy_check_mark: (without hedging) | :heavy_check_mark: (including hedging) Stats/tracing/monitoring | :heavy_check_mark: | :x: Load Balancing | :heavy_check_mark: | :heavy_check_mark: Initial Metadata Options | :heavy_check_mark: | only `waitForReady` diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index bbdd98863..793e0c0d7 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -28,4 +28,5 @@ const client = new MyServiceClient('xds:///example.com:123'); - [xDS Circuit Breaking](https://github.com/grpc/proposal/blob/master/A32-xds-circuit-breaking.md) - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) - - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) \ No newline at end of file + - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) + - [xDS Retry Support](https://github.com/grpc/proposal/blob/master/A44-xds-retry.md) \ No newline at end of file diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 124c3ed7d..c0c3200f5 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.7.0", + "version": "1.8.0", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -47,7 +47,7 @@ "re2-wasm": "^1.0.1" }, "peerDependencies": { - "@grpc/grpc-js": "~1.7.0" + "@grpc/grpc-js": "~1.8.0" }, "engines": { "node": ">=10.10.0" diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 47222f8a4..7ec0fd187 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -17,4 +17,4 @@ export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; -export const EXPERIMENTAL_RETRY = process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY === 'true'; \ No newline at end of file +export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; \ No newline at end of file diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index cd1c74bb5..9aa685748 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.7.3", + "version": "1.8.0", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From f1e3f6d7d3a291ec47b93f726591ca0c46883f44 Mon Sep 17 00:00:00 2001 From: Taegeun Moon Date: Fri, 2 Dec 2022 23:57:22 +0900 Subject: [PATCH 393/694] use option method --- packages/proto-loader/README.md | 12 ++-- .../bin/proto-loader-gen-types.ts | 56 ++++++++++--------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/packages/proto-loader/README.md b/packages/proto-loader/README.md index f10831eeb..2a7af61e3 100644 --- a/packages/proto-loader/README.md +++ b/packages/proto-loader/README.md @@ -63,12 +63,6 @@ proto-loader-gen-types.js [options] filenames... Options: --help Show help [boolean] --version Show version number [boolean] - --inputBranded Output property for branded type for "permissive" - types with fullName of the Message as its value - [boolean] [default: false] - --outputBranded Output property for branded type for "restricted" - types with fullName of the Message as its value - [boolean] [default: false] --keepCase Preserve the case of field names [boolean] [default: false] --longs The type that should be used to output 64 bit integer @@ -98,6 +92,12 @@ Options: [string] [default: "%s"] --outputTemplate Template for mapping output or "restricted" type names [string] [default: "%s__Output"] + --inputBranded Output property for branded type for "permissive" + types with fullName of the Message as its value + [boolean] [default: false] + --outputBranded Output property for branded type for "restricted" + types with fullName of the Message as its value + [boolean] [default: false] ``` ### Example Usage diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 5a9a32f88..f75822084 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -824,27 +824,39 @@ async function writeAllFiles(protoFiles: string[], options: GeneratorOptions) { } async function runScript() { + const boolDefaultFalseOption = { + boolean: true, + default: false, + }; const argv = yargs .parserConfiguration({ 'parse-positional-numbers': false }) - .string(['includeDirs', 'grpcLib']) - .normalize(['includeDirs', 'outDir']) - .array('includeDirs') - .boolean(['keepCase', 'defaults', 'arrays', 'objects', 'oneofs', 'json', 'verbose', 'includeComments']) - .string(['longs', 'enums', 'bytes', 'inputTemplate', 'outputTemplate']) - .default('keepCase', false) - .default('defaults', false) - .default('arrays', false) - .default('objects', false) - .default('oneofs', false) - .default('json', false) - .default('includeComments', false) - .default('longs', 'Long') - .default('enums', 'number') - .default('bytes', 'Buffer') - .default('inputTemplate', `${templateStr}`) - .default('outputTemplate', `${templateStr}__Output`) + .option('keepCase', boolDefaultFalseOption) + .option('longs', { string: true, default: 'Long' }) + .option('enums', { string: true, default: 'number' }) + .option('bytes', { string: true, default: 'Buffer' }) + .option('defaults', boolDefaultFalseOption) + .option('arrays', boolDefaultFalseOption) + .option('objects', boolDefaultFalseOption) + .option('oneofs', boolDefaultFalseOption) + .option('json', boolDefaultFalseOption) + .boolean('verbose') + .option('includeComments', boolDefaultFalseOption) + .option('includeDirs', { + normalize: true, + array: true, + alias: 'I' + }) + .option('outDir', { + alias: 'O', + normalize: true, + }) + .option('grpcLib', { string: true }) + .option('inputTemplate', { string: true, default: `${templateStr}` }) + .option('outputTemplate', { string: true, default: `${templateStr}__Output` }) + .option('inputBranded', boolDefaultFalseOption) + .option('outputBranded', boolDefaultFalseOption) .coerce('longs', value => { switch (value) { case 'String': return String; @@ -864,17 +876,7 @@ async function runScript() { default: return undefined; } }) - .option('inputBranded', { - boolean: true, - default: false, - }) - .option('outputBranded', { - boolean: true, - default: false, - }) .alias({ - includeDirs: 'I', - outDir: 'O', verbose: 'v' }).describe({ keepCase: 'Preserve the case of field names', From 488803740e69ea3594c5e60be00d780aa4fab58f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 5 Dec 2022 10:59:50 -0500 Subject: [PATCH 394/694] grpc-tools: Force GNU format for artifact tarballs --- packages/grpc-tools/build_binaries.sh | 2 +- packages/grpc-tools/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-tools/build_binaries.sh b/packages/grpc-tools/build_binaries.sh index e44881083..e25eb1f51 100755 --- a/packages/grpc-tools/build_binaries.sh +++ b/packages/grpc-tools/build_binaries.sh @@ -47,7 +47,7 @@ artifacts() { arch=$2 dir=$3 - tar -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) + tar --format=gnu -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) } case $(uname -s) in diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index 333ef1eb1..2d10b9272 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.12.0", + "version": "1.12.1", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", From 2ddd628747c676302d56db86d5ce73eb00d32777 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 5 Dec 2022 11:24:33 -0500 Subject: [PATCH 395/694] Use BSD tar-specific options on Mac --- packages/grpc-tools/build_binaries.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/grpc-tools/build_binaries.sh b/packages/grpc-tools/build_binaries.sh index e25eb1f51..c05d7da86 100755 --- a/packages/grpc-tools/build_binaries.sh +++ b/packages/grpc-tools/build_binaries.sh @@ -46,8 +46,14 @@ artifacts() { platform=$1 arch=$2 dir=$3 - - tar --format=gnu -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) + case $(uname -s) in + Linux) + tar -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) + ;; + Darwin) + tar --format=gnutar -czf $out_dir/$platform-$arch.tar.gz -C $(dirname $dir) $(basename $dir) + ;; + esac } case $(uname -s) in From 45adf24cf0a99a9d199041214b132f41c27d7045 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 5 Dec 2022 13:46:55 -0500 Subject: [PATCH 396/694] grpc-tools: Build for older Mac version --- packages/grpc-tools/CMakeLists.txt | 1 + packages/grpc-tools/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt index 555346912..1849ac1af 100644 --- a/packages/grpc-tools/CMakeLists.txt +++ b/packages/grpc-tools/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.7) +set(CMAKE_OSX_DEPLOYMENT_TARGET "11.7" CACHE STRING "Minimum OS X deployment version") if(COMMAND cmake_policy) cmake_policy(SET CMP0003 NEW) endif(COMMAND cmake_policy) diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index 2d10b9272..673c2cf90 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.12.1", + "version": "1.12.2", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", From 40ef3ec3a3b72fa7179e45e024fa99766cdbab83 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 5 Dec 2022 13:59:55 -0500 Subject: [PATCH 397/694] proto-loader: Bump to version 0.7.4 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index cae7635f6..495f20059 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.3", + "version": "0.7.4", "author": "Google Inc.", "contributors": [ { From 11aa7226d882095954aac0dc049f70ef0bade41c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 5 Dec 2022 15:10:42 -0500 Subject: [PATCH 398/694] grpc-tools: Build for an older Mac version (attempt 2) --- packages/grpc-tools/CMakeLists.txt | 2 +- packages/grpc-tools/build_binaries.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt index 1849ac1af..60dcdb675 100644 --- a/packages/grpc-tools/CMakeLists.txt +++ b/packages/grpc-tools/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.7) -set(CMAKE_OSX_DEPLOYMENT_TARGET "11.7" CACHE STRING "Minimum OS X deployment version") +set(CMAKE_OSX_DEPLOYMENT_TARGET "11.7" CACHE STRING "Minimum OS X deployment version" FORCE) if(COMMAND cmake_policy) cmake_policy(SET CMP0003 NEW) endif(COMMAND cmake_policy) diff --git a/packages/grpc-tools/build_binaries.sh b/packages/grpc-tools/build_binaries.sh index c05d7da86..d22bbc4ff 100755 --- a/packages/grpc-tools/build_binaries.sh +++ b/packages/grpc-tools/build_binaries.sh @@ -70,6 +70,7 @@ case $(uname -s) in mkdir $base/build/bin/$arch for bin in protoc grpc_node_plugin; do lipo -extract x86_64 $base/build/bin/$bin -o $base/build/bin/$arch/$bin + otool -l $base/build/bin/$arch/$bin | grep minos done artifacts darwin $arch $base/build/bin/$arch/ done From b735abf544bf22afc69f1761b4f06c6a97da3b57 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 5 Dec 2022 15:44:24 -0500 Subject: [PATCH 399/694] grpc-tools: Bump to version 1.12.3 --- packages/grpc-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index 673c2cf90..d4ac74ea1 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.12.2", + "version": "1.12.3", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", From 787aba72cfaae536e3e0081702fb2484ed85a479 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 8 Dec 2022 19:04:47 +0200 Subject: [PATCH 400/694] build: harden grpc-tools-build.yml permissions Signed-off-by: Alex --- .github/workflows/grpc-tools-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/grpc-tools-build.yml b/.github/workflows/grpc-tools-build.yml index f32a688c4..64ee81212 100644 --- a/.github/workflows/grpc-tools-build.yml +++ b/.github/workflows/grpc-tools-build.yml @@ -8,6 +8,9 @@ on: branches: - master +permissions: + contents: read # to fetch code (actions/checkout) + jobs: linux_build: name: Linux grpc-tools Build From 111264badfc12552587bb56839e11d5d9fcb86b0 Mon Sep 17 00:00:00 2001 From: Shubham Waje Date: Thu, 15 Dec 2022 12:41:45 +0530 Subject: [PATCH 401/694] Fix host_override param typo: - Fix `host_override` param typo in /test/interop/interop_client.js - Fix other typos --- packages/grpc-js-xds/src/csds.ts | 2 +- test/api/error_test.js | 2 +- test/interop/interop_client.js | 2 +- test/performance/benchmark_client.js | 2 +- test/performance/benchmark_client_express.js | 2 +- test/performance/histogram.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts index 114c18042..95a3bf7b7 100644 --- a/packages/grpc-js-xds/src/csds.ts +++ b/packages/grpc-js-xds/src/csds.ts @@ -154,7 +154,7 @@ function getCurrentConfig(): ClientConfig { node: clientNode, generic_xds_configs: genericConfigList }; - trace('Sending curent config ' + JSON.stringify(config, undefined, 2)); + trace('Sending current config ' + JSON.stringify(config, undefined, 2)); return config; } diff --git a/test/api/error_test.js b/test/api/error_test.js index a99619fbd..4dbf1ada1 100644 --- a/test/api/error_test.js +++ b/test/api/error_test.js @@ -341,7 +341,7 @@ describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, functio after(function() { server.forceShutdown(); }); - describe('Server recieving bad input', function() { + describe('Server receiving bad input', function() { var misbehavingClient; var badArg = Buffer.from([0xFF]); before(function() { diff --git a/test/interop/interop_client.js b/test/interop/interop_client.js index 57f4f1846..61390c524 100644 --- a/test/interop/interop_client.js +++ b/test/interop/interop_client.js @@ -550,7 +550,7 @@ exports.test_cases = test_cases; * Execute a single test case. * @param {string} address The address of the server to connect to, in the * format 'hostname:port' - * @param {string} host_overrirde The hostname of the server to use as an SSL + * @param {string} host_override The hostname of the server to use as an SSL * override * @param {string} test_case The name of the test case to run * @param {bool} tls Indicates that a secure channel should be used diff --git a/test/performance/benchmark_client.js b/test/performance/benchmark_client.js index 42605a2fd..7b2a689a7 100644 --- a/test/performance/benchmark_client.js +++ b/test/performance/benchmark_client.js @@ -328,7 +328,7 @@ BenchmarkClient.prototype.startPoisson = function( }; /** - * Return curent statistics for the client. If reset is set, restart + * Return current statistics for the client. If reset is set, restart * statistic collection. * @param {boolean} reset Indicates that statistics should be reset * @return {object} Client statistics diff --git a/test/performance/benchmark_client_express.js b/test/performance/benchmark_client_express.js index f8be6d45e..b21346399 100644 --- a/test/performance/benchmark_client_express.js +++ b/test/performance/benchmark_client_express.js @@ -243,7 +243,7 @@ BenchmarkClient.prototype.startPoisson = function( }; /** - * Return curent statistics for the client. If reset is set, restart + * Return current statistics for the client. If reset is set, restart * statistic collection. * @param {boolean} reset Indicates that statistics should be reset * @return {object} Client statistics diff --git a/test/performance/histogram.js b/test/performance/histogram.js index a03f2c13a..717988967 100644 --- a/test/performance/histogram.js +++ b/test/performance/histogram.js @@ -95,7 +95,7 @@ Histogram.prototype.mean = function() { }; /** - * Get the variance of all added values. Used to calulate the standard deviation + * Get the variance of all added values. Used to calculate the standard deviation * @return {number} The variance */ Histogram.prototype.variance = function() { From 677c0093855b9234c501ba510bfbc83bafa3320c Mon Sep 17 00:00:00 2001 From: Nick Kleinschmidt Date: Sat, 17 Dec 2022 15:19:32 -0700 Subject: [PATCH 402/694] grpc-js: Add support for grpc.service_config_disable_resolution --- packages/grpc-js/README.md | 1 + packages/grpc-js/src/channel-options.ts | 2 + packages/grpc-js/src/resolver-dns.ts | 7 ++- packages/grpc-js/test/test-resolver.ts | 58 +++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 3d698dfd8..652ce5fef 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -62,6 +62,7 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.enable_retries` - `grpc.per_rpc_retry_buffer_size` - `grpc.retry_buffer_size` + - `grpc.service_config_disable_resolution` - `grpc-node.max_session_memory` - `channelOverride` - `channelFactoryOverride` diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index 8830ed43d..a41b89e9b 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -55,6 +55,7 @@ export interface ChannelOptions { 'grpc.max_connection_age_ms'?: number; 'grpc.max_connection_age_grace_ms'?: number; 'grpc-node.max_session_memory'?: number; + 'grpc.service_config_disable_resolution'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } @@ -87,6 +88,7 @@ export const recognizedOptions = { 'grpc.max_connection_age_ms': true, 'grpc.max_connection_age_grace_ms': true, 'grpc-node.max_session_memory': true, + 'grpc.service_config_disable_resolution': true, }; export function channelOptionsEqual( diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 4b7cb2fec..355ce2dfd 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -98,6 +98,7 @@ class DnsResolver implements Resolver { private continueResolving = false; private nextResolutionTimer: NodeJS.Timer; private isNextResolutionTimerRunning = false; + private isServiceConfigEnabled = true; constructor( private target: GrpcUri, private listener: ResolverListener, @@ -127,6 +128,10 @@ class DnsResolver implements Resolver { } this.percentage = Math.random() * 100; + if (channelOptions['grpc.service_config_disable_resolution'] === 1) { + this.isServiceConfigEnabled = false; + } + this.defaultResolutionError = { code: Status.UNAVAILABLE, details: `Name resolution failed for target ${uriToString(this.target)}`, @@ -255,7 +260,7 @@ class DnsResolver implements Resolver { ); /* If there already is a still-pending TXT resolution, we can just use * that result when it comes in */ - if (this.pendingTxtPromise === null) { + if (this.isServiceConfigEnabled && this.pendingTxtPromise === null) { /* We handle the TXT query promise differently than the others because * the name resolution attempt as a whole is a success even if the TXT * lookup fails */ diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index a3e6793f1..1d458125b 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -207,6 +207,64 @@ describe('Name Resolver', () => { const resolver = resolverManager.createResolver(target, listener, {}); resolver.updateResolution(); }); + // Created DNS TXT record using TXT sample from https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md + // "grpc_config=[{\"serviceConfig\":{\"loadBalancingPolicy\":\"round_robin\",\"methodConfig\":[{\"name\":[{\"service\":\"MyService\",\"method\":\"Foo\"}],\"waitForReady\":true}]}}]" + it.skip('Should resolve a name with TXT service config', done => { + const target = resolverManager.mapUriDefaultScheme(parseUri('grpctest.kleinsch.com')!)!; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + if (serviceConfig !== null) { + assert( + serviceConfig.loadBalancingPolicy === 'round_robin', + 'Should have found round robin LB policy' + ); + done(); + } + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, {}); + resolver.updateResolution(); + }); + it.skip( + 'Should not resolve TXT service config if we disabled service config', + (done) => { + const target = resolverManager.mapUriDefaultScheme( + parseUri('grpctest.kleinsch.com')! + )!; + let count = 0; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert( + serviceConfig === null, + 'Should not have found service config' + ); + count++; + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, { + 'grpc.service_config_disable_resolution': 1, + }); + resolver.updateResolution(); + setTimeout(() => { + assert(count === 1, 'Should have only resolved once'); + done(); + }, 2_000); + } + ); /* The DNS entry for loopback4.unittest.grpc.io only has a single A record * with the address 127.0.0.1, but the Mac DNS resolver appears to use * NAT64 to create an IPv6 address in that case, so it instead returns From a1b9464de8c63831a6053525039f3077f618ac6f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Jan 2023 09:35:43 -0800 Subject: [PATCH 403/694] grpc-js: Add HTTP status and content type headers to trailers-only responses --- packages/grpc-js/src/server-call.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 631ec7675..8dcf6be33 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -63,11 +63,13 @@ const deadlineUnitsToMs: DeadlineUnitIndexSignature = { u: 0.001, n: 0.000001, }; -const defaultResponseHeaders = { +const defaultCompressionHeaders = { // TODO(cjihrig): Remove these encoding headers from the default response // once compression is integrated. [GRPC_ACCEPT_ENCODING_HEADER]: 'identity,deflate,gzip', [GRPC_ENCODING_HEADER]: 'identity', +} +const defaultResponseHeaders = { [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', }; @@ -500,7 +502,7 @@ export class Http2ServerCallStream< this.metadataSent = true; const custom = customMetadata ? customMetadata.toHttp2Headers() : null; // TODO(cjihrig): Include compression headers. - const headers = { ...defaultResponseHeaders, ...custom }; + const headers = { ...defaultResponseHeaders, ...defaultCompressionHeaders, ...custom }; this.stream.respond(headers, defaultResponseOptions); } @@ -725,9 +727,11 @@ export class Http2ServerCallStream< this.stream.end(); } } else { + // Trailers-only response const trailersToSend = { [GRPC_STATUS_HEADER]: statusObj.code, [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), + ...defaultResponseHeaders, ...statusObj.metadata?.toHttp2Headers(), }; this.stream.respond(trailersToSend, {endStream: true}); From c62d41623b361d793586d930f47b5e44829e29f2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Jan 2023 09:53:00 -0800 Subject: [PATCH 404/694] grpc-js: Discard buffer tracker entry when RetryingCall ends --- packages/grpc-js/src/retrying-call.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index f9bda9eb5..8daf5ba70 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -202,6 +202,15 @@ export class RetryingCall implements Call { private reportStatus(statusObject: StatusObject) { this.trace('ended with status: code=' + statusObject.code + ' details="' + statusObject.details + '"'); + this.bufferTracker.freeAll(this.callNumber); + for (let i = 0; i < this.writeBuffer.length; i++) { + if (this.writeBuffer[i].entryType === 'MESSAGE') { + this.writeBuffer[i] = { + entryType: 'FREED', + allocated: false + }; + } + } process.nextTick(() => { this.listener?.onReceiveStatus(statusObject); }); From 5006c14d729bc65e558bfdbd4864467b7a1f7473 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 3 Jan 2023 13:43:55 -0800 Subject: [PATCH 405/694] grpc-js: Bump to version 1.8.1 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 9aa685748..06707c2c4 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.0", + "version": "1.8.1", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From df8b8976dcb1d2fb17f826f7ceb930eb83c6885a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 7 Dec 2022 10:19:07 -0500 Subject: [PATCH 406/694] grpc-js: Refactor Transport and SubchannelConnector out of Subchannel --- packages/grpc-js/src/subchannel-call.ts | 30 +- packages/grpc-js/src/subchannel-pool.ts | 4 +- packages/grpc-js/src/subchannel.ts | 693 ++---------------------- packages/grpc-js/src/transport.ts | 634 ++++++++++++++++++++++ 4 files changed, 714 insertions(+), 647 deletions(-) create mode 100644 packages/grpc-js/src/transport.ts diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 2bc6fb0c7..6556bc460 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -21,12 +21,12 @@ import * as os from 'os'; import { Status } from './constants'; import { Metadata } from './metadata'; import { StreamDecoder } from './stream-decoder'; -import { SubchannelCallStatsTracker, Subchannel } from './subchannel'; import * as logging from './logging'; import { LogVerbosity } from './constants'; import { ServerSurfaceCall } from './server-call'; import { Deadline } from './deadline'; import { InterceptingListener, MessageContext, StatusObject, WriteCallback } from './call-interface'; +import { CallEventTracker } from './transport'; const TRACER_NAME = 'subchannel_call'; @@ -110,9 +110,9 @@ export class Http2SubchannelCall implements SubchannelCall { constructor( private readonly http2Stream: http2.ClientHttp2Stream, - private readonly callStatsTracker: SubchannelCallStatsTracker, + private readonly callEventTracker: CallEventTracker, private readonly listener: SubchannelCallInterceptingListener, - private readonly subchannel: Subchannel, + private readonly peerName: string, private readonly callId: number ) { this.disconnectListener = () => { @@ -122,8 +122,6 @@ export class Http2SubchannelCall implements SubchannelCall { metadata: new Metadata(), }); }; - subchannel.addDisconnectListener(this.disconnectListener); - subchannel.callRef(); http2Stream.on('response', (headers, flags) => { let headersString = ''; for (const header of Object.keys(headers)) { @@ -185,7 +183,7 @@ export class Http2SubchannelCall implements SubchannelCall { for (const message of messages) { this.trace('parsed message of length ' + message.length); - this.callStatsTracker!.addMessageReceived(); + this.callEventTracker!.addMessageReceived(); this.tryPush(message); } }); @@ -289,7 +287,15 @@ export class Http2SubchannelCall implements SubchannelCall { ); this.internalError = err; } - this.callStatsTracker.onStreamEnd(false); + this.callEventTracker.onStreamEnd(false); + }); + } + + public onDisconnect() { + this.endCall({ + code: Status.UNAVAILABLE, + details: 'Connection dropped', + metadata: new Metadata(), }); } @@ -304,7 +310,7 @@ export class Http2SubchannelCall implements SubchannelCall { this.finalStatus!.details + '"' ); - this.callStatsTracker.onCallEnd(this.finalStatus!); + this.callEventTracker.onCallEnd(this.finalStatus!); /* We delay the actual action of bubbling up the status to insulate the * cleanup code in this class from any errors that may be thrown in the * upper layers as a result of bubbling up the status. In particular, @@ -319,8 +325,6 @@ export class Http2SubchannelCall implements SubchannelCall { * not push more messages after the status is output, so the messages go * nowhere either way. */ this.http2Stream.resume(); - this.subchannel.callUnref(); - this.subchannel.removeDisconnectListener(this.disconnectListener); } } @@ -395,7 +399,7 @@ export class Http2SubchannelCall implements SubchannelCall { } private handleTrailers(headers: http2.IncomingHttpHeaders) { - this.callStatsTracker.onStreamEnd(true); + this.callEventTracker.onStreamEnd(true); let headersString = ''; for (const header of Object.keys(headers)) { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; @@ -467,7 +471,7 @@ export class Http2SubchannelCall implements SubchannelCall { } getPeer(): string { - return this.subchannel.getAddress(); + return this.peerName; } getCallNumber(): number { @@ -506,7 +510,7 @@ export class Http2SubchannelCall implements SubchannelCall { context.callback?.(); }; this.trace('sending data chunk of length ' + message.length); - this.callStatsTracker.addMessageSent(); + this.callEventTracker.addMessageSent(); try { this.http2Stream!.write(message, cb); } catch (error) { diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts index b7ef362c3..bbfbea02b 100644 --- a/packages/grpc-js/src/subchannel-pool.ts +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -23,6 +23,7 @@ import { } from './subchannel-address'; import { ChannelCredentials } from './channel-credentials'; import { GrpcUri, uriToString } from './uri-parser'; +import { Http2SubchannelConnector } from './transport'; // 10 seconds in milliseconds. This value is arbitrary. /** @@ -143,7 +144,8 @@ export class SubchannelPool { channelTargetUri, subchannelTarget, channelArguments, - channelCredentials + channelCredentials, + new Http2SubchannelConnector(channelTargetUri) ); if (!(channelTarget in this.pool)) { this.pool[channelTarget] = []; diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 0d9773b30..b4876f178 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -15,60 +15,30 @@ * */ -import * as http2 from 'http2'; import { ChannelCredentials } from './channel-credentials'; import { Metadata } from './metadata'; import { ChannelOptions } from './channel-options'; -import { PeerCertificate, checkServerIdentity, TLSSocket, CipherNameAndProtocol } from 'tls'; import { ConnectivityState } from './connectivity-state'; import { BackoffTimeout, BackoffOptions } from './backoff-timeout'; -import { getDefaultAuthority } from './resolver'; import * as logging from './logging'; import { LogVerbosity, Status } from './constants'; -import { getProxiedConnection, ProxyConnectionResult } from './http_proxy'; -import * as net from 'net'; -import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; -import { ConnectionOptions } from 'tls'; +import { GrpcUri, uriToString } from './uri-parser'; import { - stringToSubchannelAddress, SubchannelAddress, subchannelAddressToString, } from './subchannel-address'; -import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, SocketInfo, SocketRef, unregisterChannelzRef, registerChannelzSocket, TlsInfo } from './channelz'; +import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, unregisterChannelzRef } from './channelz'; import { ConnectivityStateListener } from './subchannel-interface'; -import { Http2SubchannelCall, SubchannelCallInterceptingListener } from './subchannel-call'; -import { getNextCallNumber } from './call-number'; +import { SubchannelCallInterceptingListener } from './subchannel-call'; import { SubchannelCall } from './subchannel-call'; -import { InterceptingListener, StatusObject } from './call-interface'; - -const clientVersion = require('../../package.json').version; +import { CallEventTracker, SubchannelConnector, Transport } from './transport'; const TRACER_NAME = 'subchannel'; -const FLOW_CONTROL_TRACER_NAME = 'subchannel_flowctrl'; /* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't * have a constant for the max signed 32 bit integer, so this is a simple way * to calculate it */ const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); -const KEEPALIVE_TIMEOUT_MS = 20000; - -export interface SubchannelCallStatsTracker { - addMessageSent(): void; - addMessageReceived(): void; - onCallEnd(status: StatusObject): void; - onStreamEnd(success: boolean): void; -} - -const { - HTTP2_HEADER_AUTHORITY, - HTTP2_HEADER_CONTENT_TYPE, - HTTP2_HEADER_METHOD, - HTTP2_HEADER_PATH, - HTTP2_HEADER_TE, - HTTP2_HEADER_USER_AGENT, -} = http2.constants; - -const tooManyPingsData: Buffer = Buffer.from('too_many_pings', 'ascii'); export class Subchannel { /** @@ -79,7 +49,7 @@ export class Subchannel { /** * The underlying http2 session used to make requests. */ - private session: http2.ClientHttp2Session | null = null; + private transport: Transport | null = null; /** * Indicates that the subchannel should transition from TRANSIENT_FAILURE to * CONNECTING instead of IDLE when the backoff timeout ends. @@ -92,45 +62,9 @@ export class Subchannel { */ private stateListeners: ConnectivityStateListener[] = []; - /** - * A list of listener functions that will be called when the underlying - * socket disconnects. Used for ending active calls with an UNAVAILABLE - * status. - */ - private disconnectListeners: Set<() => void> = new Set(); - private backoffTimeout: BackoffTimeout; - /** - * The complete user agent string constructed using channel args. - */ - private userAgent: string; - - /** - * The amount of time in between sending pings - */ - private keepaliveTimeMs: number = KEEPALIVE_MAX_TIME_MS; - /** - * The amount of time to wait for an acknowledgement after sending a ping - */ - private keepaliveTimeoutMs: number = KEEPALIVE_TIMEOUT_MS; - /** - * Timer reference for timeout that indicates when to send the next ping - */ - private keepaliveIntervalId: NodeJS.Timer; - /** - * Timer reference tracking when the most recent ping will be considered lost - */ - private keepaliveTimeoutId: NodeJS.Timer; - /** - * Indicates whether keepalive pings should be sent without any active calls - */ - private keepaliveWithoutCalls = false; - - /** - * Tracks calls with references to this subchannel - */ - private callRefcount = 0; + private keepaliveTimeMultiplier = 1; /** * Tracks channels and subchannel pools with references to this subchannel */ @@ -149,18 +83,7 @@ export class Subchannel { private childrenTracker = new ChannelzChildrenTracker(); // Channelz socket info - private channelzSocketRef: SocketRef | null = null; - /** - * Name of the remote server, if it is not the same as the subchannel - * address, i.e. if connecting through an HTTP CONNECT proxy. - */ - private remoteName: string | null = null; private streamTracker = new ChannelzCallTracker(); - private keepalivesSent = 0; - private messagesSent = 0; - private messagesReceived = 0; - private lastMessageSentTimestamp: Date | null = null; - private lastMessageReceivedTimestamp: Date | null = null; /** * A class representing a connection to a single backend. @@ -176,33 +99,9 @@ export class Subchannel { private channelTarget: GrpcUri, private subchannelAddress: SubchannelAddress, private options: ChannelOptions, - private credentials: ChannelCredentials + private credentials: ChannelCredentials, + private connector: SubchannelConnector ) { - // Build user-agent string. - this.userAgent = [ - options['grpc.primary_user_agent'], - `grpc-node-js/${clientVersion}`, - options['grpc.secondary_user_agent'], - ] - .filter((e) => e) - .join(' '); // remove falsey values first - - if ('grpc.keepalive_time_ms' in options) { - this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!; - } - if ('grpc.keepalive_timeout_ms' in options) { - this.keepaliveTimeoutMs = options['grpc.keepalive_timeout_ms']!; - } - if ('grpc.keepalive_permit_without_calls' in options) { - this.keepaliveWithoutCalls = - options['grpc.keepalive_permit_without_calls'] === 1; - } else { - this.keepaliveWithoutCalls = false; - } - this.keepaliveIntervalId = setTimeout(() => {}, 0); - clearTimeout(this.keepaliveIntervalId); - this.keepaliveTimeoutId = setTimeout(() => {}, 0); - clearTimeout(this.keepaliveTimeoutId); const backoffOptions: BackoffOptions = { initialDelay: options['grpc.initial_reconnect_backoff_ms'], maxDelay: options['grpc.max_reconnect_backoff_ms'], @@ -233,67 +132,6 @@ export class Subchannel { }; } - private getChannelzSocketInfo(): SocketInfo | null { - if (this.session === null) { - return null; - } - const sessionSocket = this.session.socket; - const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; - const localAddress = sessionSocket.localAddress ? stringToSubchannelAddress(sessionSocket.localAddress, sessionSocket.localPort) : null; - let tlsInfo: TlsInfo | null; - if (this.session.encrypted) { - const tlsSocket: TLSSocket = sessionSocket as TLSSocket; - const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); - const certificate = tlsSocket.getCertificate(); - const peerCertificate = tlsSocket.getPeerCertificate(); - tlsInfo = { - cipherSuiteStandardName: cipherInfo.standardName ?? null, - cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, - localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, - remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null - }; - } else { - tlsInfo = null; - } - const socketInfo: SocketInfo = { - remoteAddress: remoteAddress, - localAddress: localAddress, - security: tlsInfo, - remoteName: this.remoteName, - streamsStarted: this.streamTracker.callsStarted, - streamsSucceeded: this.streamTracker.callsSucceeded, - streamsFailed: this.streamTracker.callsFailed, - messagesSent: this.messagesSent, - messagesReceived: this.messagesReceived, - keepAlivesSent: this.keepalivesSent, - lastLocalStreamCreatedTimestamp: this.streamTracker.lastCallStartedTimestamp, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: this.lastMessageSentTimestamp, - lastMessageReceivedTimestamp: this.lastMessageReceivedTimestamp, - localFlowControlWindow: this.session.state.localWindowSize ?? null, - remoteFlowControlWindow: this.session.state.remoteWindowSize ?? null - }; - return socketInfo; - } - - private resetChannelzSocketInfo() { - if (!this.channelzEnabled) { - return; - } - if (this.channelzSocketRef) { - unregisterChannelzRef(this.channelzSocketRef); - this.childrenTracker.unrefChild(this.channelzSocketRef); - this.channelzSocketRef = null; - } - this.remoteName = null; - this.streamTracker = new ChannelzCallTracker(); - this.keepalivesSent = 0; - this.messagesSent = 0; - this.messagesReceived = 0; - this.lastMessageSentTimestamp = null; - this.lastMessageReceivedTimestamp = null; - } - private trace(text: string): void { logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); } @@ -302,18 +140,6 @@ export class Subchannel { logging.trace(LogVerbosity.DEBUG, 'subchannel_refcount', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); } - private flowControlTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); - } - - private internalsTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, 'subchannel_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); - } - - private keepaliveTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, 'keepalive', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); - } - private handleBackoffTimer() { if (this.continueConnecting) { this.transitionToState( @@ -340,313 +166,39 @@ export class Subchannel { this.backoffTimeout.reset(); } - private sendPing() { - if (this.channelzEnabled) { - this.keepalivesSent += 1; - } - this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); - this.keepaliveTimeoutId = setTimeout(() => { - this.keepaliveTrace('Ping timeout passed without response'); - this.handleDisconnect(); - }, this.keepaliveTimeoutMs); - this.keepaliveTimeoutId.unref?.(); - try { - this.session!.ping( - (err: Error | null, duration: number, payload: Buffer) => { - this.keepaliveTrace('Received ping response'); - clearTimeout(this.keepaliveTimeoutId); - } - ); - } catch (e) { - /* If we fail to send a ping, the connection is no longer functional, so - * we should discard it. */ - this.transitionToState( - [ConnectivityState.READY], - ConnectivityState.TRANSIENT_FAILURE - ); - } - } - - private startKeepalivePings() { - this.keepaliveIntervalId = setInterval(() => { - this.sendPing(); - }, this.keepaliveTimeMs); - this.keepaliveIntervalId.unref?.(); - /* Don't send a ping immediately because whatever caused us to start - * sending pings should also involve some network activity. */ - } - - /** - * Stop keepalive pings when terminating a connection. This discards the - * outstanding ping timeout, so it should not be called if the same - * connection will still be used. - */ - private stopKeepalivePings() { - clearInterval(this.keepaliveIntervalId); - clearTimeout(this.keepaliveTimeoutId); - } - - private createSession(proxyConnectionResult: ProxyConnectionResult) { - if (proxyConnectionResult.realTarget) { - this.remoteName = uriToString(proxyConnectionResult.realTarget); - this.trace('creating HTTP/2 session through proxy to ' + proxyConnectionResult.realTarget); - } else { - this.remoteName = null; - this.trace('creating HTTP/2 session'); - } - const targetAuthority = getDefaultAuthority( - proxyConnectionResult.realTarget ?? this.channelTarget - ); - let connectionOptions: http2.SecureClientSessionOptions = - this.credentials._getConnectionOptions() || {}; - connectionOptions.maxSendHeaderBlockLength = Number.MAX_SAFE_INTEGER; - if ('grpc-node.max_session_memory' in this.options) { - connectionOptions.maxSessionMemory = this.options[ - 'grpc-node.max_session_memory' - ]; - } else { - /* By default, set a very large max session memory limit, to effectively - * disable enforcement of the limit. Some testing indicates that Node's - * behavior degrades badly when this limit is reached, so we solve that - * by disabling the check entirely. */ - connectionOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; - } - let addressScheme = 'http://'; - if ('secureContext' in connectionOptions) { - addressScheme = 'https://'; - // If provided, the value of grpc.ssl_target_name_override should be used - // to override the target hostname when checking server identity. - // This option is used for testing only. - if (this.options['grpc.ssl_target_name_override']) { - const sslTargetNameOverride = this.options[ - 'grpc.ssl_target_name_override' - ]!; - connectionOptions.checkServerIdentity = ( - host: string, - cert: PeerCertificate - ): Error | undefined => { - return checkServerIdentity(sslTargetNameOverride, cert); - }; - connectionOptions.servername = sslTargetNameOverride; - } else { - const authorityHostname = - splitHostPort(targetAuthority)?.host ?? 'localhost'; - // We want to always set servername to support SNI - connectionOptions.servername = authorityHostname; - } - if (proxyConnectionResult.socket) { - /* This is part of the workaround for - * https://github.com/nodejs/node/issues/32922. Without that bug, - * proxyConnectionResult.socket would always be a plaintext socket and - * this would say - * connectionOptions.socket = proxyConnectionResult.socket; */ - connectionOptions.createConnection = (authority, option) => { - return proxyConnectionResult.socket!; - }; - } - } else { - /* In all but the most recent versions of Node, http2.connect does not use - * the options when establishing plaintext connections, so we need to - * establish that connection explicitly. */ - connectionOptions.createConnection = (authority, option) => { - if (proxyConnectionResult.socket) { - return proxyConnectionResult.socket; - } else { - /* net.NetConnectOpts is declared in a way that is more restrictive - * than what net.connect will actually accept, so we use the type - * assertion to work around that. */ - return net.connect(this.subchannelAddress); - } - }; - } - - connectionOptions = { - ...connectionOptions, - ...this.subchannelAddress, - }; - - /* http2.connect uses the options here: - * https://github.com/nodejs/node/blob/70c32a6d190e2b5d7b9ff9d5b6a459d14e8b7d59/lib/internal/http2/core.js#L3028-L3036 - * The spread operator overides earlier values with later ones, so any port - * or host values in the options will be used rather than any values extracted - * from the first argument. In addition, the path overrides the host and port, - * as documented for plaintext connections here: - * https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener - * and for TLS connections here: - * https://nodejs.org/api/tls.html#tls_tls_connect_options_callback. In - * earlier versions of Node, http2.connect passes these options to - * tls.connect but not net.connect, so in the insecure case we still need - * to set the createConnection option above to create the connection - * explicitly. We cannot do that in the TLS case because http2.connect - * passes necessary additional options to tls.connect. - * The first argument just needs to be parseable as a URL and the scheme - * determines whether the connection will be established over TLS or not. - */ - const session = http2.connect( - addressScheme + targetAuthority, - connectionOptions - ); - this.session = session; - this.channelzSocketRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo()!, this.channelzEnabled); - if (this.channelzEnabled) { - this.childrenTracker.refChild(this.channelzSocketRef); + private startConnectingInternal() { + let options = this.options; + if (options['grpc.keepalive_time_ms']) { + const adjustedKeepaliveTime = Math.min(options['grpc.keepalive_time_ms'] * this.keepaliveTimeMultiplier, KEEPALIVE_MAX_TIME_MS); + options = {...options, 'grpc.keepalive_time_ms': adjustedKeepaliveTime}; } - session.unref(); - /* For all of these events, check if the session at the time of the event - * is the same one currently attached to this subchannel, to ensure that - * old events from previous connection attempts cannot cause invalid state - * transitions. */ - session.once('connect', () => { - if (this.session === session) { - this.transitionToState( - [ConnectivityState.CONNECTING], - ConnectivityState.READY - ); - } - }); - session.once('close', () => { - if (this.session === session) { - this.trace('connection closed'); - this.transitionToState( - [ConnectivityState.CONNECTING], - ConnectivityState.TRANSIENT_FAILURE - ); - /* Transitioning directly to IDLE here should be OK because we are not - * doing any backoff, because a connection was established at some - * point */ - this.transitionToState( - [ConnectivityState.READY], - ConnectivityState.IDLE - ); - } - }); - session.once( - 'goaway', - (errorCode: number, lastStreamID: number, opaqueData: Buffer) => { - if (this.session === session) { - /* See the last paragraph of - * https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */ - if ( - errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM && - opaqueData.equals(tooManyPingsData) - ) { - this.keepaliveTimeMs = Math.min( - 2 * this.keepaliveTimeMs, - KEEPALIVE_MAX_TIME_MS - ); - logging.log( - LogVerbosity.ERROR, - `Connection to ${uriToString(this.channelTarget)} at ${ - this.subchannelAddressString - } rejected by server because of excess pings. Increasing ping interval to ${ - this.keepaliveTimeMs - } ms` - ); + this.connector.connect(this.subchannelAddress, this.credentials, options).then( + transport => { + if (this.transitionToState([ConnectivityState.CONNECTING], ConnectivityState.READY)) { + this.transport = transport; + if (this.channelzEnabled) { + this.childrenTracker.refChild(transport.getChannelzRef()); } - this.trace( - 'connection closed by GOAWAY with code ' + - errorCode - ); - this.transitionToState( - [ConnectivityState.CONNECTING, ConnectivityState.READY], - ConnectivityState.IDLE - ); - } - } - ); - session.once('error', (error) => { - /* Do nothing here. Any error should also trigger a close event, which is - * where we want to handle that. */ - this.trace( - 'connection closed with error ' + - (error as Error).message - ); - }); - if (logging.isTracerEnabled(TRACER_NAME)) { - session.on('remoteSettings', (settings: http2.Settings) => { - this.trace( - 'new settings received' + - (this.session !== session ? ' on the old connection' : '') + - ': ' + - JSON.stringify(settings) - ); - }); - session.on('localSettings', (settings: http2.Settings) => { - this.trace( - 'local settings acknowledged by remote' + - (this.session !== session ? ' on the old connection' : '') + - ': ' + - JSON.stringify(settings) - ); - }); - } - } - - private startConnectingInternal() { - /* Pass connection options through to the proxy so that it's able to - * upgrade it's connection to support tls if needed. - * This is a workaround for https://github.com/nodejs/node/issues/32922 - * See https://github.com/grpc/grpc-node/pull/1369 for more info. */ - const connectionOptions: ConnectionOptions = - this.credentials._getConnectionOptions() || {}; - - if ('secureContext' in connectionOptions) { - connectionOptions.ALPNProtocols = ['h2']; - // If provided, the value of grpc.ssl_target_name_override should be used - // to override the target hostname when checking server identity. - // This option is used for testing only. - if (this.options['grpc.ssl_target_name_override']) { - const sslTargetNameOverride = this.options[ - 'grpc.ssl_target_name_override' - ]!; - connectionOptions.checkServerIdentity = ( - host: string, - cert: PeerCertificate - ): Error | undefined => { - return checkServerIdentity(sslTargetNameOverride, cert); - }; - connectionOptions.servername = sslTargetNameOverride; - } else { - if ('grpc.http_connect_target' in this.options) { - /* This is more or less how servername will be set in createSession - * if a connection is successfully established through the proxy. - * If the proxy is not used, these connectionOptions are discarded - * anyway */ - const targetPath = getDefaultAuthority( - parseUri(this.options['grpc.http_connect_target'] as string) ?? { - path: 'localhost', + transport.addDisconnectListener((tooManyPings) => { + this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); + if (tooManyPings) { + this.keepaliveTimeMultiplier *= 2; + logging.log( + LogVerbosity.ERROR, + `Connection to ${uriToString(this.channelTarget)} at ${ + this.subchannelAddressString + } rejected by server because of excess pings. Increasing ping interval multiplier to ${ + this.keepaliveTimeMultiplier + } ms` + ); } - ); - const hostPort = splitHostPort(targetPath); - connectionOptions.servername = hostPort?.host ?? targetPath; + }); } - } - } - - getProxiedConnection( - this.subchannelAddress, - this.options, - connectionOptions - ).then( - (result) => { - this.createSession(result); }, - (reason) => { - this.transitionToState( - [ConnectivityState.CONNECTING], - ConnectivityState.TRANSIENT_FAILURE - ); + error => { + this.transitionToState([ConnectivityState.CONNECTING], ConnectivityState.TRANSIENT_FAILURE); } - ); - } - - private handleDisconnect() { - this.transitionToState( - [ConnectivityState.READY], - ConnectivityState.TRANSIENT_FAILURE); - for (const listener of this.disconnectListeners.values()) { - listener(); - } + ) } /** @@ -676,15 +228,6 @@ export class Subchannel { switch (newState) { case ConnectivityState.READY: this.stopBackoff(); - const session = this.session!; - session.socket.once('close', () => { - if (this.session === session) { - this.handleDisconnect(); - } - }); - if (this.keepaliveWithoutCalls) { - this.startKeepalivePings(); - } break; case ConnectivityState.CONNECTING: this.startBackoff(); @@ -692,12 +235,11 @@ export class Subchannel { this.continueConnecting = false; break; case ConnectivityState.TRANSIENT_FAILURE: - if (this.session) { - this.session.close(); + if (this.channelzEnabled && this.transport) { + this.childrenTracker.unrefChild(this.transport.getChannelzRef()); } - this.session = null; - this.resetChannelzSocketInfo(); - this.stopKeepalivePings(); + this.transport?.shutdown(); + this.transport = null; /* If the backoff timer has already ended by the time we get to the * TRANSIENT_FAILURE state, we want to immediately transition out of * TRANSIENT_FAILURE as though the backoff timer is ending right now */ @@ -708,12 +250,11 @@ export class Subchannel { } break; case ConnectivityState.IDLE: - if (this.session) { - this.session.close(); + if (this.channelzEnabled && this.transport) { + this.childrenTracker.unrefChild(this.transport.getChannelzRef()); } - this.session = null; - this.resetChannelzSocketInfo(); - this.stopKeepalivePings(); + this.transport?.shutdown(); + this.transport = null; break; default: throw new Error(`Invalid state: unknown ConnectivityState ${newState}`); @@ -726,66 +267,6 @@ export class Subchannel { return true; } - /** - * Check if the subchannel associated with zero calls and with zero channels. - * If so, shut it down. - */ - private checkBothRefcounts() { - /* If no calls, channels, or subchannel pools have any more references to - * this subchannel, we can be sure it will never be used again. */ - if (this.callRefcount === 0 && this.refcount === 0) { - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); - } - this.transitionToState( - [ConnectivityState.CONNECTING, ConnectivityState.READY], - ConnectivityState.IDLE - ); - if (this.channelzEnabled) { - unregisterChannelzRef(this.channelzRef); - } - } - } - - callRef() { - this.refTrace( - 'callRefcount ' + - this.callRefcount + - ' -> ' + - (this.callRefcount + 1) - ); - if (this.callRefcount === 0) { - if (this.session) { - this.session.ref(); - } - this.backoffTimeout.ref(); - if (!this.keepaliveWithoutCalls) { - this.startKeepalivePings(); - } - } - this.callRefcount += 1; - } - - callUnref() { - this.refTrace( - 'callRefcount ' + - this.callRefcount + - ' -> ' + - (this.callRefcount - 1) - ); - this.callRefcount -= 1; - if (this.callRefcount === 0) { - if (this.session) { - this.session.unref(); - } - this.backoffTimeout.unref(); - if (!this.keepaliveWithoutCalls) { - clearInterval(this.keepaliveIntervalId); - } - this.checkBothRefcounts(); - } - } - ref() { this.refTrace( 'refcount ' + @@ -804,7 +285,18 @@ export class Subchannel { (this.refcount - 1) ); this.refcount -= 1; - this.checkBothRefcounts(); + if (this.refcount === 0) { + if (this.channelzEnabled) { + this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); + } + this.transitionToState( + [ConnectivityState.CONNECTING, ConnectivityState.READY], + ConnectivityState.IDLE + ); + if (this.channelzEnabled) { + unregisterChannelzRef(this.channelzRef); + } + } } unrefIfOneRef(): boolean { @@ -816,83 +308,26 @@ export class Subchannel { } createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener): SubchannelCall { - const headers = metadata.toHttp2Headers(); - headers[HTTP2_HEADER_AUTHORITY] = host; - headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; - headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; - headers[HTTP2_HEADER_METHOD] = 'POST'; - headers[HTTP2_HEADER_PATH] = method; - headers[HTTP2_HEADER_TE] = 'trailers'; - let http2Stream: http2.ClientHttp2Stream; - /* In theory, if an error is thrown by session.request because session has - * become unusable (e.g. because it has received a goaway), this subchannel - * should soon see the corresponding close or goaway event anyway and leave - * READY. But we have seen reports that this does not happen - * (https://github.com/googleapis/nodejs-firestore/issues/1023#issuecomment-653204096) - * so for defense in depth, we just discard the session when we see an - * error here. - */ - try { - http2Stream = this.session!.request(headers); - } catch (e) { - this.transitionToState( - [ConnectivityState.READY], - ConnectivityState.TRANSIENT_FAILURE - ); - throw e; + if (!this.transport) { + throw new Error('Cannot create call, subchannel not READY'); } - this.flowControlTrace( - 'local window size: ' + - this.session!.state.localWindowSize + - ' remote window size: ' + - this.session!.state.remoteWindowSize - ); - const streamSession = this.session; - this.internalsTrace( - 'session.closed=' + - streamSession!.closed + - ' session.destroyed=' + - streamSession!.destroyed + - ' session.socket.destroyed=' + - streamSession!.socket.destroyed); - let statsTracker: SubchannelCallStatsTracker; + let statsTracker: Partial; if (this.channelzEnabled) { this.callTracker.addCallStarted(); this.streamTracker.addCallStarted(); statsTracker = { - addMessageSent: () => { - this.messagesSent += 1; - this.lastMessageSentTimestamp = new Date(); - }, - addMessageReceived: () => { - this.messagesReceived += 1; - }, onCallEnd: status => { if (status.code === Status.OK) { this.callTracker.addCallSucceeded(); } else { this.callTracker.addCallFailed(); } - }, - onStreamEnd: success => { - if (streamSession === this.session) { - if (success) { - this.streamTracker.addCallSucceeded(); - } else { - this.streamTracker.addCallFailed(); - } - } } } } else { - statsTracker = { - addMessageSent: () => {}, - addMessageReceived: () => {}, - onCallEnd: () => {}, - onStreamEnd: () => {} - } + statsTracker = {}; } - return new Http2SubchannelCall(http2Stream, statsTracker, listener, this, getNextCallNumber()); + return this.transport.createCall(metadata, host, method, listener, statsTracker); } /** @@ -946,14 +381,6 @@ export class Subchannel { } } - addDisconnectListener(listener: () => void) { - this.disconnectListeners.add(listener); - } - - removeDisconnectListener(listener: () => void) { - this.disconnectListeners.delete(listener); - } - /** * Reset the backoff timeout, and immediately start connecting if in backoff. */ diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts new file mode 100644 index 000000000..e713ed6ef --- /dev/null +++ b/packages/grpc-js/src/transport.ts @@ -0,0 +1,634 @@ +/* + * 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. + * + */ + +import * as http2 from 'http2'; +import { checkServerIdentity, CipherNameAndProtocol, ConnectionOptions, PeerCertificate, TLSSocket } from 'tls'; +import { StatusObject } from './call-interface'; +import { ChannelCredentials } from './channel-credentials'; +import { ChannelOptions } from './channel-options'; +import { ChannelzCallTracker, registerChannelzSocket, SocketInfo, SocketRef, TlsInfo } from './channelz'; +import { LogVerbosity } from './constants'; +import { getProxiedConnection, ProxyConnectionResult } from './http_proxy'; +import * as logging from './logging'; +import { getDefaultAuthority } from './resolver'; +import { stringToSubchannelAddress, SubchannelAddress, subchannelAddressToString } from './subchannel-address'; +import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; +import * as net from 'net'; +import { Http2SubchannelCall, SubchannelCall, SubchannelCallInterceptingListener } from './subchannel-call'; +import { Metadata } from './metadata'; +import { getNextCallNumber } from './call-number'; + +const TRACER_NAME = 'transport'; +const FLOW_CONTROL_TRACER_NAME = 'transport_flowctrl'; + +const clientVersion = require('../../package.json').version; + +const { + HTTP2_HEADER_AUTHORITY, + HTTP2_HEADER_CONTENT_TYPE, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_PATH, + HTTP2_HEADER_TE, + HTTP2_HEADER_USER_AGENT, +} = http2.constants; + +/* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't + * have a constant for the max signed 32 bit integer, so this is a simple way + * to calculate it */ +const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); +const KEEPALIVE_TIMEOUT_MS = 20000; + +export interface CallEventTracker { + addMessageSent(): void; + addMessageReceived(): void; + onCallEnd(status: StatusObject): void; + onStreamEnd(success: boolean): void; +} + +export interface TransportDisconnectListener { + (tooManyPings: boolean): void; +} + +export interface Transport { + getChannelzRef(): SocketRef; + createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener, subchannelCallStatsTracker: Partial): SubchannelCall; + addDisconnectListener(listener: TransportDisconnectListener): void; + shutdown(): void; +} + +const tooManyPingsData: Buffer = Buffer.from('too_many_pings', 'ascii'); + +class Http2Transport implements Transport { + /** + * The amount of time in between sending pings + */ + private keepaliveTimeMs: number = KEEPALIVE_MAX_TIME_MS; + /** + * The amount of time to wait for an acknowledgement after sending a ping + */ + private keepaliveTimeoutMs: number = KEEPALIVE_TIMEOUT_MS; + /** + * Timer reference for timeout that indicates when to send the next ping + */ + private keepaliveIntervalId: NodeJS.Timer; + /** + * Timer reference tracking when the most recent ping will be considered lost + */ + private keepaliveTimeoutId: NodeJS.Timer | null = null; + /** + * Indicates whether keepalive pings should be sent without any active calls + */ + private keepaliveWithoutCalls = false; + + private userAgent: string; + + private activeCalls: Set = new Set(); + + private subchannelAddressString: string; + + private disconnectListeners: TransportDisconnectListener[] = []; + + private disconnectHandled = false; + + // Channelz info + private channelzRef: SocketRef; + private readonly channelzEnabled: boolean = true; + /** + * Name of the remote server, if it is not the same as the subchannel + * address, i.e. if connecting through an HTTP CONNECT proxy. + */ + private remoteName: string | null = null; + private streamTracker = new ChannelzCallTracker(); + private keepalivesSent = 0; + private messagesSent = 0; + private messagesReceived = 0; + private lastMessageSentTimestamp: Date | null = null; + private lastMessageReceivedTimestamp: Date | null = null; + + constructor( + private session: http2.ClientHttp2Session, + subchannelAddress: SubchannelAddress, + options: ChannelOptions + ) { + // Build user-agent string. + this.userAgent = [ + options['grpc.primary_user_agent'], + `grpc-node-js/${clientVersion}`, + options['grpc.secondary_user_agent'], + ] + .filter((e) => e) + .join(' '); // remove falsey values first + + if ('grpc.keepalive_time_ms' in options) { + this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!; + } + if ('grpc.keepalive_timeout_ms' in options) { + this.keepaliveTimeoutMs = options['grpc.keepalive_timeout_ms']!; + } + if ('grpc.keepalive_permit_without_calls' in options) { + this.keepaliveWithoutCalls = + options['grpc.keepalive_permit_without_calls'] === 1; + } else { + this.keepaliveWithoutCalls = false; + } + this.keepaliveIntervalId = setTimeout(() => {}, 0); + clearTimeout(this.keepaliveIntervalId); + if (this.keepaliveWithoutCalls) { + this.startKeepalivePings(); + } + + this.subchannelAddressString = subchannelAddressToString(subchannelAddress); + + if (options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + this.channelzRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); + + session.once('close', () => { + this.trace('session closed'); + this.stopKeepalivePings(); + this.handleDisconnect(false); + }); + session.once('goaway', (errorCode: number, lastStreamID: number, opaqueData: Buffer) => { + let tooManyPings = false; + /* See the last paragraph of + * https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */ + if ( + errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM && + opaqueData.equals(tooManyPingsData) + ) { + tooManyPings = true; + } + this.trace( + 'connection closed by GOAWAY with code ' + + errorCode + ); + this.handleDisconnect(tooManyPings); + }); + session.once('error', error => { + /* Do nothing here. Any error should also trigger a close event, which is + * where we want to handle that. */ + this.trace( + 'connection closed with error ' + + (error as Error).message + ); + }); + if (logging.isTracerEnabled(TRACER_NAME)) { + session.on('remoteSettings', (settings: http2.Settings) => { + this.trace( + 'new settings received' + + (this.session !== session ? ' on the old connection' : '') + + ': ' + + JSON.stringify(settings) + ); + }); + session.on('localSettings', (settings: http2.Settings) => { + this.trace( + 'local settings acknowledged by remote' + + (this.session !== session ? ' on the old connection' : '') + + ': ' + + JSON.stringify(settings) + ); + }); + } + } + + private getChannelzInfo(): SocketInfo { + const sessionSocket = this.session.socket; + const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; + const localAddress = sessionSocket.localAddress ? stringToSubchannelAddress(sessionSocket.localAddress, sessionSocket.localPort) : null; + let tlsInfo: TlsInfo | null; + if (this.session.encrypted) { + const tlsSocket: TLSSocket = sessionSocket as TLSSocket; + const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); + const certificate = tlsSocket.getCertificate(); + const peerCertificate = tlsSocket.getPeerCertificate(); + tlsInfo = { + cipherSuiteStandardName: cipherInfo.standardName ?? null, + cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, + localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, + remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null + }; + } else { + tlsInfo = null; + } + const socketInfo: SocketInfo = { + remoteAddress: remoteAddress, + localAddress: localAddress, + security: tlsInfo, + remoteName: this.remoteName, + streamsStarted: this.streamTracker.callsStarted, + streamsSucceeded: this.streamTracker.callsSucceeded, + streamsFailed: this.streamTracker.callsFailed, + messagesSent: this.messagesSent, + messagesReceived: this.messagesReceived, + keepAlivesSent: this.keepalivesSent, + lastLocalStreamCreatedTimestamp: this.streamTracker.lastCallStartedTimestamp, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: this.lastMessageSentTimestamp, + lastMessageReceivedTimestamp: this.lastMessageReceivedTimestamp, + localFlowControlWindow: this.session.state.localWindowSize ?? null, + remoteFlowControlWindow: this.session.state.remoteWindowSize ?? null + }; + return socketInfo; + } + + private trace(text: string): void { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private keepaliveTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'keepalive', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private flowControlTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private internalsTrace(text: string): void { + logging.trace(LogVerbosity.DEBUG, 'transport_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + } + + private handleDisconnect(tooManyPings: boolean) { + if (this.disconnectHandled) { + return; + } + this.disconnectHandled = true; + this.disconnectListeners.forEach(listener => listener(tooManyPings)); + for (const call of this.activeCalls) { + call.onDisconnect(); + } + } + + addDisconnectListener(listener: TransportDisconnectListener): void { + this.disconnectListeners.push(listener); + } + + private clearKeepaliveTimeout() { + if (!this.keepaliveTimeoutId) { + return; + } + clearTimeout(this.keepaliveTimeoutId); + this.keepaliveTimeoutId = null; + } + + private sendPing() { + if (this.channelzEnabled) { + this.keepalivesSent += 1; + } + this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); + if (!this.keepaliveTimeoutId) { + this.keepaliveTimeoutId = setTimeout(() => { + this.keepaliveTrace('Ping timeout passed without response'); + this.handleDisconnect(false); + }, this.keepaliveTimeoutMs); + this.keepaliveTimeoutId.unref?.(); + } + try { + this.session!.ping( + (err: Error | null, duration: number, payload: Buffer) => { + this.keepaliveTrace('Received ping response'); + this.clearKeepaliveTimeout(); + } + ); + } catch (e) { + /* If we fail to send a ping, the connection is no longer functional, so + * we should discard it. */ + this.handleDisconnect(false); + } + } + + private startKeepalivePings() { + this.keepaliveIntervalId = setInterval(() => { + this.sendPing(); + }, this.keepaliveTimeMs); + this.keepaliveIntervalId.unref?.(); + /* Don't send a ping immediately because whatever caused us to start + * sending pings should also involve some network activity. */ + } + + /** + * Stop keepalive pings when terminating a connection. This discards the + * outstanding ping timeout, so it should not be called if the same + * connection will still be used. + */ + private stopKeepalivePings() { + clearInterval(this.keepaliveIntervalId); + this.clearKeepaliveTimeout(); + } + + private removeActiveCall(call: Http2SubchannelCall) { + this.activeCalls.delete(call); + if (this.activeCalls.size === 0 && !this.keepaliveWithoutCalls) { + this.stopKeepalivePings(); + } + } + + private addActiveCall(call: Http2SubchannelCall) { + if (this.activeCalls.size === 0 && !this.keepaliveWithoutCalls) { + this.startKeepalivePings(); + } + this.activeCalls.add(call); + } + + createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener, subchannelCallStatsTracker: Partial): Http2SubchannelCall { + const headers = metadata.toHttp2Headers(); + headers[HTTP2_HEADER_AUTHORITY] = host; + headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; + headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc'; + headers[HTTP2_HEADER_METHOD] = 'POST'; + headers[HTTP2_HEADER_PATH] = method; + headers[HTTP2_HEADER_TE] = 'trailers'; + let http2Stream: http2.ClientHttp2Stream; + /* In theory, if an error is thrown by session.request because session has + * become unusable (e.g. because it has received a goaway), this subchannel + * should soon see the corresponding close or goaway event anyway and leave + * READY. But we have seen reports that this does not happen + * (https://github.com/googleapis/nodejs-firestore/issues/1023#issuecomment-653204096) + * so for defense in depth, we just discard the session when we see an + * error here. + */ + try { + http2Stream = this.session!.request(headers); + } catch (e) { + this.handleDisconnect(false); + throw e; + } + this.flowControlTrace( + 'local window size: ' + + this.session.state.localWindowSize + + ' remote window size: ' + + this.session.state.remoteWindowSize + ); + this.internalsTrace( + 'session.closed=' + + this.session.closed + + ' session.destroyed=' + + this.session.destroyed + + ' session.socket.destroyed=' + + this.session.socket.destroyed); + let eventTracker: CallEventTracker; + let call: Http2SubchannelCall; + if (this.channelzEnabled) { + this.streamTracker.addCallStarted(); + eventTracker = { + addMessageSent: () => { + this.messagesSent += 1; + this.lastMessageSentTimestamp = new Date(); + subchannelCallStatsTracker.addMessageSent?.(); + }, + addMessageReceived: () => { + this.messagesReceived += 1; + this.lastMessageReceivedTimestamp = new Date(); + subchannelCallStatsTracker.addMessageReceived?.(); + }, + onCallEnd: status => { + subchannelCallStatsTracker.onCallEnd?.(status); + }, + onStreamEnd: success => { + if (success) { + this.streamTracker.addCallSucceeded(); + } else { + this.streamTracker.addCallFailed(); + } + this.removeActiveCall(call); + subchannelCallStatsTracker.onStreamEnd?.(success); + } + } + } else { + eventTracker = { + addMessageSent: () => { + subchannelCallStatsTracker.addMessageSent?.(); + }, + addMessageReceived: () => { + subchannelCallStatsTracker.addMessageReceived?.(); + }, + onCallEnd: (status) => { + subchannelCallStatsTracker.onCallEnd?.(status); + this.removeActiveCall(call); + }, + onStreamEnd: (success) => { + subchannelCallStatsTracker.onStreamEnd?.(success); + } + } + } + call = new Http2SubchannelCall(http2Stream, eventTracker, listener, this.subchannelAddressString, getNextCallNumber()); + this.addActiveCall(call); + return call; + } + + getChannelzRef(): SocketRef { + return this.channelzRef; + } + + shutdown() { + this.session.close(); + } +} + +export interface SubchannelConnector { + connect(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions): Promise; + shutdown(): void; +} + +export class Http2SubchannelConnector implements SubchannelConnector { + private session: http2.ClientHttp2Session | null = null; + private isShutdown = false; + constructor(private channelTarget: GrpcUri) {} + private trace(text: string) { + + } + private createSession(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions, proxyConnectionResult: ProxyConnectionResult): Promise { + if (this.isShutdown) { + return Promise.reject(); + } + return new Promise((resolve, reject) => { + let remoteName: string | null; + if (proxyConnectionResult.realTarget) { + remoteName = uriToString(proxyConnectionResult.realTarget); + this.trace('creating HTTP/2 session through proxy to ' + uriToString(proxyConnectionResult.realTarget)); + } else { + remoteName = null; + this.trace('creating HTTP/2 session to ' + subchannelAddressToString(address)); + } + const targetAuthority = getDefaultAuthority( + proxyConnectionResult.realTarget ?? this.channelTarget + ); + let connectionOptions: http2.SecureClientSessionOptions = + credentials._getConnectionOptions() || {}; + connectionOptions.maxSendHeaderBlockLength = Number.MAX_SAFE_INTEGER; + if ('grpc-node.max_session_memory' in options) { + connectionOptions.maxSessionMemory = options[ + 'grpc-node.max_session_memory' + ]; + } else { + /* By default, set a very large max session memory limit, to effectively + * disable enforcement of the limit. Some testing indicates that Node's + * behavior degrades badly when this limit is reached, so we solve that + * by disabling the check entirely. */ + connectionOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; + } + let addressScheme = 'http://'; + if ('secureContext' in connectionOptions) { + addressScheme = 'https://'; + // If provided, the value of grpc.ssl_target_name_override should be used + // to override the target hostname when checking server identity. + // This option is used for testing only. + if (options['grpc.ssl_target_name_override']) { + const sslTargetNameOverride = options[ + 'grpc.ssl_target_name_override' + ]!; + connectionOptions.checkServerIdentity = ( + host: string, + cert: PeerCertificate + ): Error | undefined => { + return checkServerIdentity(sslTargetNameOverride, cert); + }; + connectionOptions.servername = sslTargetNameOverride; + } else { + const authorityHostname = + splitHostPort(targetAuthority)?.host ?? 'localhost'; + // We want to always set servername to support SNI + connectionOptions.servername = authorityHostname; + } + if (proxyConnectionResult.socket) { + /* This is part of the workaround for + * https://github.com/nodejs/node/issues/32922. Without that bug, + * proxyConnectionResult.socket would always be a plaintext socket and + * this would say + * connectionOptions.socket = proxyConnectionResult.socket; */ + connectionOptions.createConnection = (authority, option) => { + return proxyConnectionResult.socket!; + }; + } + } else { + /* In all but the most recent versions of Node, http2.connect does not use + * the options when establishing plaintext connections, so we need to + * establish that connection explicitly. */ + connectionOptions.createConnection = (authority, option) => { + if (proxyConnectionResult.socket) { + return proxyConnectionResult.socket; + } else { + /* net.NetConnectOpts is declared in a way that is more restrictive + * than what net.connect will actually accept, so we use the type + * assertion to work around that. */ + return net.connect(address); + } + }; + } + + connectionOptions = { + ...connectionOptions, + ...address, + }; + + /* http2.connect uses the options here: + * https://github.com/nodejs/node/blob/70c32a6d190e2b5d7b9ff9d5b6a459d14e8b7d59/lib/internal/http2/core.js#L3028-L3036 + * The spread operator overides earlier values with later ones, so any port + * or host values in the options will be used rather than any values extracted + * from the first argument. In addition, the path overrides the host and port, + * as documented for plaintext connections here: + * https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener + * and for TLS connections here: + * https://nodejs.org/api/tls.html#tls_tls_connect_options_callback. In + * earlier versions of Node, http2.connect passes these options to + * tls.connect but not net.connect, so in the insecure case we still need + * to set the createConnection option above to create the connection + * explicitly. We cannot do that in the TLS case because http2.connect + * passes necessary additional options to tls.connect. + * The first argument just needs to be parseable as a URL and the scheme + * determines whether the connection will be established over TLS or not. + */ + const session = http2.connect( + addressScheme + targetAuthority, + connectionOptions + ); + this.session = session; + session.unref(); + session.once('connect', () => { + session.removeAllListeners(); + resolve(new Http2Transport(session, address, options)); + this.session = null; + }); + session.once('close', () => { + this.session = null; + reject(); + }); + session.once('error', error => { + this.trace('connection failed with error ' + (error as Error).message) + }); + }); + } + connect(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions): Promise { + if (this.isShutdown) { + return Promise.reject(); + } + /* Pass connection options through to the proxy so that it's able to + * upgrade it's connection to support tls if needed. + * This is a workaround for https://github.com/nodejs/node/issues/32922 + * See https://github.com/grpc/grpc-node/pull/1369 for more info. */ + const connectionOptions: ConnectionOptions = + credentials._getConnectionOptions() || {}; + + if ('secureContext' in connectionOptions) { + connectionOptions.ALPNProtocols = ['h2']; + // If provided, the value of grpc.ssl_target_name_override should be used + // to override the target hostname when checking server identity. + // This option is used for testing only. + if (options['grpc.ssl_target_name_override']) { + const sslTargetNameOverride = options[ + 'grpc.ssl_target_name_override' + ]!; + connectionOptions.checkServerIdentity = ( + host: string, + cert: PeerCertificate + ): Error | undefined => { + return checkServerIdentity(sslTargetNameOverride, cert); + }; + connectionOptions.servername = sslTargetNameOverride; + } else { + if ('grpc.http_connect_target' in options) { + /* This is more or less how servername will be set in createSession + * if a connection is successfully established through the proxy. + * If the proxy is not used, these connectionOptions are discarded + * anyway */ + const targetPath = getDefaultAuthority( + parseUri(options['grpc.http_connect_target'] as string) ?? { + path: 'localhost', + } + ); + const hostPort = splitHostPort(targetPath); + connectionOptions.servername = hostPort?.host ?? targetPath; + } + } + } + + return getProxiedConnection( + address, + options, + connectionOptions + ).then( + result => this.createSession(address, credentials, options, result) + ); + } + + shutdown(): void { + this.isShutdown = true; + this.session?.close(); + this.session = null; + } +} \ No newline at end of file From 5812cad19ece511ebcf6f05d9f31df7b908ece4d Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 6 Jan 2023 13:58:59 -0800 Subject: [PATCH 407/694] grpc-js-xds: Reduce xDS GCE interop framework to ping_pong and circuit_breaking The migration of other tests to the new framework have been completed around Aug 2022: - https://github.com/grpc/grpc-node/commits/81083bd22912bd3272d193dcc14077d0c4f50399/packages/grpc-js-xds/scripts/xds_k8s_lb.sh - https://github.com/grpc/grpc-node/commits/81083bd22912bd3272d193dcc14077d0c4f50399/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 615b2a7c7..d4490c5ef 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -54,7 +54,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ - --test_case="all,timeout,circuit_breaking,fault_injection,csds" \ + --test_case="ping_pong,circuit_breaking" \ --project_id=grpc-testing \ --source_image=projects/grpc-testing/global/images/xds-test-server-5 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ From b72e1fc665824d096255e4a95e541517b7822df2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 6 Jan 2023 14:31:23 -0800 Subject: [PATCH 408/694] Merge pull request #2310 from grpc/reduce-gce-xds-interop-tests grpc-js-xds: Reduce GCE xDS interop tests to ping_pong and circuit_breaking --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 615b2a7c7..d4490c5ef 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -54,7 +54,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ - --test_case="all,timeout,circuit_breaking,fault_injection,csds" \ + --test_case="ping_pong,circuit_breaking" \ --project_id=grpc-testing \ --source_image=projects/grpc-testing/global/images/xds-test-server-5 \ --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ From 2d37686a1a0e0f028acb455a630fa65e122db7ad Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 9 Jan 2023 10:18:43 -0800 Subject: [PATCH 409/694] grpc-js: Ensure ordering between status and final message --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel-call.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 06707c2c4..c11b4dc42 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.1", + "version": "1.8.2", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 6556bc460..a560ed4d7 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -87,6 +87,7 @@ export class Http2SubchannelCall implements SubchannelCall { private decoder = new StreamDecoder(); private isReadFilterPending = false; + private isPushPending = false; private canPush = false; /** * Indicates that an 'end' event has come from the http2 stream, so there @@ -360,7 +361,8 @@ export class Http2SubchannelCall implements SubchannelCall { this.finalStatus.code !== Status.OK || (this.readsClosed && this.unpushedReadMessages.length === 0 && - !this.isReadFilterPending) + !this.isReadFilterPending && + !this.isPushPending) ) { this.outputStatus(); } @@ -373,7 +375,9 @@ export class Http2SubchannelCall implements SubchannelCall { (message instanceof Buffer ? message.length : null) ); this.canPush = false; + this.isPushPending = true; process.nextTick(() => { + this.isPushPending = false; /* If we have already output the status any later messages should be * ignored, and can cause out-of-order operation errors higher up in the * stack. Checking as late as possible here to avoid any race conditions. From b3b6310f041aef2770f4a7945453e4db2b5cd113 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 10 Jan 2023 15:24:22 -0800 Subject: [PATCH 410/694] grpc-js: Don't end calls when receiving GOAWAY --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/transport.ts | 38 ++++++++++++---- packages/grpc-js/test/test-server.ts | 67 +++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index c11b4dc42..700c7b773 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.2", + "version": "1.8.3", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index e713ed6ef..64f770945 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -161,7 +161,7 @@ class Http2Transport implements Transport { session.once('close', () => { this.trace('session closed'); this.stopKeepalivePings(); - this.handleDisconnect(false); + this.handleDisconnect(); }); session.once('goaway', (errorCode: number, lastStreamID: number, opaqueData: Buffer) => { let tooManyPings = false; @@ -177,7 +177,7 @@ class Http2Transport implements Transport { 'connection closed by GOAWAY with code ' + errorCode ); - this.handleDisconnect(tooManyPings); + this.reportDisconnectToOwner(tooManyPings); }); session.once('error', error => { /* Do nothing here. Any error should also trigger a close event, which is @@ -263,15 +263,35 @@ class Http2Transport implements Transport { logging.trace(LogVerbosity.DEBUG, 'transport_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); } - private handleDisconnect(tooManyPings: boolean) { + /** + * Indicate to the owner of this object that this transport should no longer + * be used. That happens if the connection drops, or if the server sends a + * GOAWAY. + * @param tooManyPings If true, this was triggered by a GOAWAY with data + * indicating that the session was closed becaues the client sent too many + * pings. + * @returns + */ + private reportDisconnectToOwner(tooManyPings: boolean) { if (this.disconnectHandled) { return; } this.disconnectHandled = true; this.disconnectListeners.forEach(listener => listener(tooManyPings)); - for (const call of this.activeCalls) { - call.onDisconnect(); - } + } + + /** + * Handle connection drops, but not GOAWAYs. + */ + private handleDisconnect() { + this.reportDisconnectToOwner(false); + /* Give calls an event loop cycle to finish naturally before reporting the + * disconnnection to them. */ + setImmediate(() => { + for (const call of this.activeCalls) { + call.onDisconnect(); + } + }); } addDisconnectListener(listener: TransportDisconnectListener): void { @@ -294,7 +314,7 @@ class Http2Transport implements Transport { if (!this.keepaliveTimeoutId) { this.keepaliveTimeoutId = setTimeout(() => { this.keepaliveTrace('Ping timeout passed without response'); - this.handleDisconnect(false); + this.handleDisconnect(); }, this.keepaliveTimeoutMs); this.keepaliveTimeoutId.unref?.(); } @@ -308,7 +328,7 @@ class Http2Transport implements Transport { } catch (e) { /* If we fail to send a ping, the connection is no longer functional, so * we should discard it. */ - this.handleDisconnect(false); + this.handleDisconnect(); } } @@ -365,7 +385,7 @@ class Http2Transport implements Transport { try { http2Stream = this.session!.request(headers); } catch (e) { - this.handleDisconnect(false); + this.handleDisconnect(); throw e; } this.flowControlTrace( diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 0c0ba168e..c67ebc4d6 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -27,9 +27,9 @@ import * as grpc from '../src'; import { Server, ServerCredentials } from '../src'; import { ServiceError } from '../src/call'; import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; -import { sendUnaryData, ServerUnaryCall } from '../src/server-call'; +import { sendUnaryData, ServerUnaryCall, ServerDuplexStream } from '../src/server-call'; -import { loadProtoFile } from './common'; +import { assert2, loadProtoFile } from './common'; import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; import { Request__Output } from './generated/Request'; @@ -458,18 +458,28 @@ describe('Server', () => { describe('Echo service', () => { let server: Server; let client: ServiceClient; + const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); + const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; + + const serviceImplementation = { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, call.request); + }, + echoBidiStream(call: ServerDuplexStream) { + call.on('data', data => { + call.write(data); + }); + call.on('end', () => { + call.end(); + }); + } + }; before(done => { - const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); - const echoService = loadProtoFile(protoFile) - .EchoService as ServiceClientConstructor; server = new Server(); - server.addService(echoService.service, { - echo(call: ServerUnaryCall, callback: sendUnaryData) { - callback(null, call.request); - }, - }); + server.addService(echoService.service, serviceImplementation); server.bindAsync( 'localhost:0', @@ -501,6 +511,43 @@ describe('Echo service', () => { } ); }); + + /* This test passes on Node 18 but fails on Node 16. The failure appears to + * be caused by https://github.com/nodejs/node/issues/42713 */ + it.skip('should continue a stream after server shutdown', done => { + const server2 = new Server(); + server2.addService(echoService.service, serviceImplementation); + server2.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { + if (err) { + done(err); + return; + } + const client2 = new echoService(`localhost:${port}`, grpc.credentials.createInsecure()); + server2.start(); + const stream = client2.echoBidiStream(); + const totalMessages = 5; + let messagesSent = 0; + stream.write({ value: 'test value', value2: messagesSent}); + messagesSent += 1; + stream.on('data', () => { + if (messagesSent === 1) { + server2.tryShutdown(assert2.mustCall(() => {})); + } + if (messagesSent >= totalMessages) { + stream.end(); + } else { + stream.write({ value: 'test value', value2: messagesSent}); + messagesSent += 1; + } + }); + stream.on('status', assert2.mustCall((status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.OK); + assert.strictEqual(messagesSent, totalMessages); + })); + stream.on('error', () => {}); + assert2.afterMustCallsSatisfied(done); + }); + }); }); describe('Generic client and server', () => { From c0182608a85a390da0b78d8ee38a8625f255c359 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 10 Jan 2023 13:43:27 -0800 Subject: [PATCH 411/694] grpc-js-xds: Add aggregate and logical_dns clusters --- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/generated/cluster.ts | 9 + .../clusters/aggregate/v3/ClusterConfig.ts | 28 + packages/grpc-js-xds/src/load-balancer-cds.ts | 213 +++++-- packages/grpc-js-xds/src/load-balancer-eds.ts | 547 ------------------ .../grpc-js-xds/src/load-balancer-priority.ts | 33 +- packages/grpc-js-xds/src/resources.ts | 16 +- packages/grpc-js-xds/src/xds-cluster-impl.ts | 272 +++++++++ .../grpc-js-xds/src/xds-cluster-resolver.ts | 466 +++++++++++++++ packages/grpc-js/src/experimental.ts | 1 + .../src/load-balancer-child-handler.ts | 8 +- 11 files changed, 972 insertions(+), 623 deletions(-) create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig.ts delete mode 100644 packages/grpc-js-xds/src/load-balancer-eds.ts create mode 100644 packages/grpc-js-xds/src/xds-cluster-impl.ts create mode 100644 packages/grpc-js-xds/src/xds-cluster-resolver.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index c0c3200f5..5f07a3b65 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", + "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/clusters/aggregate/v3/cluster.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" }, "repository": { diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index 78ac3bbd3..681bc5a2d 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -97,6 +97,15 @@ export interface ProtoGrpcType { } } } + extensions: { + clusters: { + aggregate: { + v3: { + ClusterConfig: MessageTypeDefinition + } + } + } + } type: { matcher: { v3: { diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig.ts new file mode 100644 index 000000000..49245ac7d --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig.ts @@ -0,0 +1,28 @@ +// Original file: deps/envoy-api/envoy/extensions/clusters/aggregate/v3/cluster.proto + + +/** + * Configuration for the aggregate cluster. See the :ref:`architecture overview + * ` for more information. + * [#extension: envoy.clusters.aggregate] + */ +export interface ClusterConfig { + /** + * Load balancing clusters in aggregate cluster. Clusters are prioritized based on the order they + * appear in this list. + */ + 'clusters'?: (string)[]; +} + +/** + * Configuration for the aggregate cluster. See the :ref:`architecture overview + * ` for more information. + * [#extension: envoy.clusters.aggregate] + */ +export interface ClusterConfig__Output { + /** + * Load balancing clusters in aggregate cluster. Clusters are prioritized based on the order they + * appear in this list. + */ + 'clusters': (string)[]; +} diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 4d47b2546..95eb9c39a 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -28,11 +28,12 @@ import LoadBalancingConfig = experimental.LoadBalancingConfig; import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; -import { EdsLoadBalancingConfig } from './load-balancer-eds'; import { Watcher } from './xds-stream-state/xds-stream-state'; import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; import { Duration__Output } from './generated/google/protobuf/Duration'; import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; +import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler, XdsClusterResolverLoadBalancingConfig } from './xds-cluster-resolver'; +import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources'; const TRACER_NAME = 'cds_balancer'; @@ -115,56 +116,147 @@ function translateOutlierDetectionConfig(outlierDetection: OutlierDetection__Out ); } +interface ClusterEntry { + watcher: Watcher; + latestUpdate?: Cluster__Output; + children: string[]; +} + +interface ClusterTree { + [name: string]: ClusterEntry; +} + +function isClusterTreeFullyUpdated(tree: ClusterTree, root: string): boolean { + const toCheck: string[] = [root]; + const visited = new Set(); + while (toCheck.length > 0) { + const next = toCheck.shift()!; + if (visited.has(next)) { + continue; + } + visited.add(next); + if (!tree[next] || !tree[next].latestUpdate) { + return false; + } + toCheck.push(...tree[next].children); + } + return true; +} + +function generateDiscoveryMechanismForCluster(config: Cluster__Output): DiscoveryMechanism { + let maxConcurrentRequests: number | undefined = undefined; + for (const threshold of config.circuit_breakers?.thresholds ?? []) { + if (threshold.priority === 'DEFAULT') { + maxConcurrentRequests = threshold.max_requests?.value; + } + } + if (config.type === 'EDS') { + // EDS cluster + return { + cluster: config.name, + lrs_load_reporting_server_name: config.lrs_server?.self ? '' : undefined, + max_concurrent_requests: maxConcurrentRequests, + type: 'EDS', + eds_service_name: config.eds_cluster_config!.service_name === '' ? undefined : config.eds_cluster_config!.service_name + }; + } else { + // Logical DNS cluster + const socketAddress = config.load_assignment!.endpoints[0].lb_endpoints[0].endpoint!.address!.socket_address!; + return { + cluster: config.name, + lrs_load_reporting_server_name: config.lrs_server?.self ? '' : undefined, + max_concurrent_requests: maxConcurrentRequests, + type: 'LOGICAL_DNS', + dns_hostname: `${socketAddress.address}:${socketAddress.port_value}` + }; + } +} + +const RECURSION_DEPTH_LIMIT = 16; + +/** + * Prerequisite: isClusterTreeFullyUpdated(tree, root) + * @param tree + * @param root + */ +function getDiscoveryMechanismList(tree: ClusterTree, root: string): DiscoveryMechanism[] { + const visited = new Set(); + function getDiscoveryMechanismListHelper(node: string, depth: number): DiscoveryMechanism[] { + if (visited.has(node) || depth > RECURSION_DEPTH_LIMIT) { + return []; + } + visited.add(node); + if (tree[root].children.length > 0) { + // Aggregate cluster + const result = []; + for (const child of tree[root].children) { + result.push(...getDiscoveryMechanismListHelper(child, depth + 1)); + } + return result; + } else { + // individual cluster + const config = tree[root].latestUpdate!; + return [generateDiscoveryMechanismForCluster(config)]; + } + } + return getDiscoveryMechanismListHelper(root, 0); +} + export class CdsLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; - private watcher: Watcher; - - private isWatcherActive = false; private latestCdsUpdate: Cluster__Output | null = null; private latestConfig: CdsLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown } = {}; + private clusterTree: ClusterTree = {}; + + private updatedChild = false; + constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper); - this.watcher = { + this.childBalancer = new XdsClusterResolverChildPolicyHandler(channelControlHelper); + } + + private addCluster(cluster: string) { + if (cluster in this.clusterTree) { + return; + } + trace('Adding watcher for cluster ' + cluster); + const watcher: Watcher = { onValidUpdate: (update) => { - this.latestCdsUpdate = update; - let maxConcurrentRequests: number | undefined = undefined; - for (const threshold of update.circuit_breakers?.thresholds ?? []) { - if (threshold.priority === 'DEFAULT') { - maxConcurrentRequests = threshold.max_requests?.value; - } + this.clusterTree[cluster].latestUpdate = update; + if (update.cluster_discovery_type === 'cluster_type') { + const children = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, update.cluster_type!.typed_config!.value).clusters + this.clusterTree[cluster].children = children; + children.forEach(child => this.addCluster(child)); + } + if (isClusterTreeFullyUpdated(this.clusterTree, this.latestConfig!.getCluster())) { + const clusterResolverConfig = new XdsClusterResolverLoadBalancingConfig( + getDiscoveryMechanismList(this.clusterTree, this.latestConfig!.getCluster()), + [], + [] + ); + trace('Child update EDS config: ' + JSON.stringify(clusterResolverConfig)); + this.updatedChild = true; + this.childBalancer.updateAddressList( + [], + clusterResolverConfig, + this.latestAttributes + ); } - /* the lrs_server.self field indicates that the same server should be - * used for load reporting as for other xDS operations. Setting - * lrsLoadReportingServerName to the empty string sets that behavior. - * Otherwise, if the field is omitted, load reporting is disabled. */ - const edsConfig: EdsLoadBalancingConfig = new EdsLoadBalancingConfig( - /* cluster= */ update.name, - /* localityPickingPolicy= */ [], - /* endpointPickingPolicy= */ [], - /* edsServiceName= */ update.eds_cluster_config!.service_name === '' ? undefined : update.eds_cluster_config!.service_name, - /* lrsLoadReportingServerName= */update.lrs_server?.self ? '' : undefined, - /* maxConcurrentRequests= */ maxConcurrentRequests, - /* outlierDetection= */ translateOutlierDetectionConfig(update.outlier_detection) - ); - trace('Child update EDS config: ' + JSON.stringify(edsConfig)); - this.childBalancer.updateAddressList( - [], - edsConfig, - this.latestAttributes - ); }, onResourceDoesNotExist: () => { - this.isWatcherActive = false; - this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: 'CDS resource does not exist', metadata: new Metadata()})); + if (cluster in this.clusterTree) { + this.clusterTree[cluster].latestUpdate = undefined; + this.clusterTree[cluster].children = []; + } + this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `CDS resource ${cluster} does not exist`, metadata: new Metadata()})); this.childBalancer.destroy(); }, onTransientError: (statusObj) => { - if (this.latestCdsUpdate === null) { - channelControlHelper.updateState( + if (!this.updatedChild) { + this.channelControlHelper.updateState( connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({ code: status.UNAVAILABLE, @@ -173,8 +265,27 @@ export class CdsLoadBalancer implements LoadBalancer { }) ); } - }, + } + }; + this.clusterTree[cluster] = { + watcher: watcher, + children: [] }; + getSingletonXdsClient().addClusterWatcher(cluster, watcher); + } + + private removeCluster(cluster: string) { + if (!(cluster in this.clusterTree)) { + return; + } + getSingletonXdsClient().removeClusterWatcher(cluster, this.clusterTree[cluster].watcher); + delete this.clusterTree[cluster]; + } + + private clearClusterTree() { + for (const cluster of Object.keys(this.clusterTree)) { + this.removeCluster(cluster); + } } updateAddressList( @@ -192,29 +303,16 @@ export class CdsLoadBalancer implements LoadBalancer { /* If the cluster is changing, disable the old watcher before adding the new * one */ if ( - this.isWatcherActive && this.latestConfig?.getCluster() !== lbConfig.getCluster() ) { - trace('Removing old cluster watcher for cluster name ' + this.latestConfig!.getCluster()); - getSingletonXdsClient().removeClusterWatcher( - this.latestConfig!.getCluster(), - this.watcher - ); - /* Setting isWatcherActive to false here lets us have one code path for - * calling addClusterWatcher */ - this.isWatcherActive = false; - /* If we have a new name, the latestCdsUpdate does not correspond to - * the new config, so it is no longer valid */ - this.latestCdsUpdate = null; + trace('Removing old cluster watchers rooted at ' + this.latestConfig!.getCluster()); + this.clearClusterTree(); + this.updatedChild = false; } this.latestConfig = lbConfig; - if (!this.isWatcherActive) { - trace('Adding new cluster watcher for cluster name ' + lbConfig.getCluster()); - getSingletonXdsClient().addClusterWatcher(lbConfig.getCluster(), this.watcher); - this.isWatcherActive = true; - } + this.addCluster(lbConfig.getCluster()); } exitIdle(): void { this.childBalancer.exitIdle(); @@ -223,14 +321,9 @@ export class CdsLoadBalancer implements LoadBalancer { this.childBalancer.resetBackoff(); } destroy(): void { - trace('Destroying load balancer with cluster name ' + this.latestConfig?.getCluster()); + trace('Destroying load balancer rooted at cluster named ' + this.latestConfig?.getCluster()); this.childBalancer.destroy(); - if (this.isWatcherActive) { - getSingletonXdsClient().removeClusterWatcher( - this.latestConfig!.getCluster(), - this.watcher - ); - } + this.clearClusterTree(); } getTypeName(): string { return TYPE_NAME; diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts deleted file mode 100644 index b0bd3f030..000000000 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Copyright 2020 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 { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, StatusObject } from '@grpc/grpc-js'; -import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from './xds-client'; -import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; -import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; -import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from './load-balancer-priority'; -import LoadBalancer = experimental.LoadBalancer; -import ChannelControlHelper = experimental.ChannelControlHelper; -import registerLoadBalancerType = experimental.registerLoadBalancerType; -import LoadBalancingConfig = experimental.LoadBalancingConfig; -import SubchannelAddress = experimental.SubchannelAddress; -import subchannelAddressToString = experimental.subchannelAddressToString; -import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; -import UnavailablePicker = experimental.UnavailablePicker; -import Picker = experimental.Picker; -import PickResultType = experimental.PickResultType; -import { validateLoadBalancingConfig } from '@grpc/grpc-js/build/src/experimental'; -import { WeightedTarget, WeightedTargetLoadBalancingConfig } from './load-balancer-weighted-target'; -import { LrsLoadBalancingConfig } from './load-balancer-lrs'; -import { Watcher } from './xds-stream-state/xds-stream-state'; -import Filter = experimental.Filter; -import BaseFilter = experimental.BaseFilter; -import FilterFactory = experimental.FilterFactory; -import CallStream = experimental.CallStream; -import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; -import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; - -const TRACER_NAME = 'eds_balancer'; - -function trace(text: string): void { - experimental.trace(LogVerbosity.DEBUG, TRACER_NAME, text); -} - -const TYPE_NAME = 'eds'; - -function localityToName(locality: Locality__Output) { - return `{region=${locality.region},zone=${locality.zone},sub_zone=${locality.sub_zone}}`; -} - -const DEFAULT_MAX_CONCURRENT_REQUESTS = 1024; - -export class EdsLoadBalancingConfig implements LoadBalancingConfig { - private maxConcurrentRequests: number; - getLoadBalancerName(): string { - return TYPE_NAME; - } - toJsonObject(): object { - const jsonObj: {[key: string]: any} = { - cluster: this.cluster, - locality_picking_policy: this.localityPickingPolicy.map(policy => policy.toJsonObject()), - endpoint_picking_policy: this.endpointPickingPolicy.map(policy => policy.toJsonObject()), - max_concurrent_requests: this.maxConcurrentRequests - }; - if (this.edsServiceName !== undefined) { - jsonObj.eds_service_name = this.edsServiceName; - } - if (this.lrsLoadReportingServerName !== undefined) { - jsonObj.lrs_load_reporting_server_name = this.lrsLoadReportingServerName; - } - if (this.outlierDetection !== undefined) { - jsonObj.outlier_detection = this.outlierDetection.toJsonObject(); - } - return { - [TYPE_NAME]: jsonObj - }; - } - - constructor(private cluster: string, private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string, maxConcurrentRequests?: number, private outlierDetection?: OutlierDetectionLoadBalancingConfig) { - this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; - } - - getCluster() { - return this.cluster; - } - - getLocalityPickingPolicy() { - return this.localityPickingPolicy; - } - - getEndpointPickingPolicy() { - return this.endpointPickingPolicy; - } - - getEdsServiceName() { - return this.edsServiceName; - } - - getLrsLoadReportingServerName() { - return this.lrsLoadReportingServerName; - } - - getMaxConcurrentRequests() { - return this.maxConcurrentRequests; - } - - getOutlierDetection() { - return this.outlierDetection; - } - - static createFromJson(obj: any): EdsLoadBalancingConfig { - if (!('cluster' in obj && typeof obj.cluster === 'string')) { - throw new Error('eds config must have a string field cluster'); - } - if (!('locality_picking_policy' in obj && Array.isArray(obj.locality_picking_policy))) { - throw new Error('eds config must have a locality_picking_policy array'); - } - if (!('endpoint_picking_policy' in obj && Array.isArray(obj.endpoint_picking_policy))) { - throw new Error('eds config must have an endpoint_picking_policy array'); - } - if ('eds_service_name' in obj && !(obj.eds_service_name === undefined || typeof obj.eds_service_name === 'string')) { - throw new Error('eds config eds_service_name field must be a string if provided'); - } - if ('lrs_load_reporting_server_name' in obj && (!obj.lrs_load_reporting_server_name === undefined || typeof obj.lrs_load_reporting_server_name === 'string')) { - throw new Error('eds config lrs_load_reporting_server_name must be a string if provided'); - } - if ('max_concurrent_requests' in obj && (!obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { - throw new Error('eds config max_concurrent_requests must be a number if provided'); - } - let validatedOutlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined = undefined; - if (EXPERIMENTAL_OUTLIER_DETECTION) { - if ('outlier_detection' in obj) { - const outlierDetectionConfig = validateLoadBalancingConfig(obj.outlier_detection); - if (!(outlierDetectionConfig instanceof OutlierDetectionLoadBalancingConfig)) { - throw new Error('eds config outlier_detection must be a valid outlier detection config if provided'); - } - validatedOutlierDetectionConfig = outlierDetectionConfig; - } - } - return new EdsLoadBalancingConfig(obj.cluster, obj.locality_picking_policy.map(validateLoadBalancingConfig), obj.endpoint_picking_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name, obj.max_concurrent_requests, validatedOutlierDetectionConfig); - } -} - -/** - * This class load balances over a cluster by making an EDS request and then - * transforming the result into a configuration for another load balancing - * policy. - */ -export class EdsLoadBalancer implements LoadBalancer { - /** - * The child load balancer that will handle balancing the results of the EDS - * requests. - */ - private childBalancer: ChildLoadBalancerHandler; - private edsServiceName: string | null = null; - private watcher: Watcher; - /** - * Indicates whether the watcher has already been passed to the xdsClient - * and is getting updates. - */ - private isWatcherActive = false; - - private lastestConfig: EdsLoadBalancingConfig | null = null; - private latestAttributes: { [key: string]: unknown } = {}; - private latestEdsUpdate: ClusterLoadAssignment__Output | null = null; - - /** - * The priority of each locality the last time we got an update. - */ - private localityPriorities: Map = new Map(); - /** - * The name we assigned to each priority number the last time we got an - * update. - */ - private priorityNames: string[] = []; - - private nextPriorityChildNumber = 0; - - private clusterDropStats: XdsClusterDropStats | null = null; - - private concurrentRequests: number = 0; - - constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.channelControlHelper, { - updateState: (connectivityState, originalPicker) => { - if (this.latestEdsUpdate === null) { - return; - } - const edsPicker: Picker = { - pick: (pickArgs) => { - const dropCategory = this.checkForDrop(); - /* If we drop the call, it ends with an UNAVAILABLE status. - * Otherwise, delegate picking the subchannel to the child - * balancer. */ - if (dropCategory === null) { - const originalPick = originalPicker.pick(pickArgs); - return { - pickResultType: originalPick.pickResultType, - status: originalPick.status, - subchannel: originalPick.subchannel, - onCallStarted: () => { - originalPick.onCallStarted?.(); - this.concurrentRequests += 1; - }, - onCallEnded: status => { - originalPick.onCallEnded?.(status); - this.concurrentRequests -= 1; - } - }; - } else { - let details: string; - if (dropCategory === true) { - details = 'Call dropped by load balancing policy.'; - this.clusterDropStats?.addUncategorizedCallDropped(); - } else { - details = `Call dropped by load balancing policy. Category: ${dropCategory}`; - this.clusterDropStats?.addCallDropped(dropCategory); - } - return { - pickResultType: PickResultType.DROP, - status: { - code: Status.UNAVAILABLE, - details: details, - metadata: new Metadata(), - }, - subchannel: null, - onCallEnded: null, - onCallStarted: null - }; - } - }, - }; - this.channelControlHelper.updateState(connectivityState, edsPicker); - }, - })); - this.watcher = { - onValidUpdate: (update) => { - trace('Received EDS update for ' + this.edsServiceName + ': ' + JSON.stringify(update, undefined, 2)); - this.latestEdsUpdate = update; - this.updateChild(); - }, - onResourceDoesNotExist: () => { - this.isWatcherActive = false; - this.channelControlHelper.updateState(ConnectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: Status.UNAVAILABLE, details: 'EDS resource does not exist', metadata: new Metadata()})); - this.childBalancer.destroy(); - }, - onTransientError: (status) => { - if (this.latestEdsUpdate === null) { - channelControlHelper.updateState( - ConnectivityState.TRANSIENT_FAILURE, - new UnavailablePicker({ - code: Status.UNAVAILABLE, - details: `xDS request failed with error ${status.details}`, - metadata: new Metadata(), - }) - ); - } - }, - }; - } - - /** - * Check whether a single call should be dropped according to the current - * policy, based on randomly chosen numbers. Returns the drop category if - * the call should be dropped, and null otherwise. true is a valid - * output, as a sentinel value indicating a drop with no category. - */ - private checkForDrop(): string | true | null { - if (this.lastestConfig && this.concurrentRequests >= this.lastestConfig.getMaxConcurrentRequests()) { - return true; - } - if (!this.latestEdsUpdate?.policy) { - return null; - } - /* The drop_overloads policy is a list of pairs of category names and - * probabilities. For each one, if the random number is within that - * probability range, we drop the call citing that category. Otherwise, the - * call proceeds as usual. */ - for (const dropOverload of this.latestEdsUpdate.policy.drop_overloads) { - if (!dropOverload.drop_percentage) { - continue; - } - let randNum: number; - switch (dropOverload.drop_percentage.denominator) { - case 'HUNDRED': - randNum = Math.random() * 100; - break; - case 'TEN_THOUSAND': - randNum = Math.random() * 10_000; - break; - case 'MILLION': - randNum = Math.random() * 1_000_000; - break; - default: - continue; - } - if (randNum < dropOverload.drop_percentage.numerator) { - return dropOverload.category; - } - } - return null; - } - - /** - * Should be called when this balancer gets a new config and when the - * XdsClient returns a new ClusterLoadAssignment. - */ - private updateChild() { - if (!(this.lastestConfig && this.latestEdsUpdate)) { - return; - } - /** - * Maps each priority number to the list of localities with that priority, - * and the list of addresses associated with each locality. - */ - const priorityList: { - locality: Locality__Output; - weight: number; - addresses: SubchannelAddress[]; - }[][] = []; - /** - * New replacement for this.localityPriorities, mapping locality names to - * priority values. The replacement occurrs at the end of this method. - */ - const newLocalityPriorities: Map = new Map< - string, - number - >(); - /* We are given a list of localities, each of which has a priority. This - * loop consolidates localities into buckets by priority, while also - * simplifying the data structure to make the later steps simpler */ - for (const endpoint of this.latestEdsUpdate.endpoints) { - if (!endpoint.load_balancing_weight) { - continue; - } - const addresses: SubchannelAddress[] = endpoint.lb_endpoints.filter(lbEndpoint => lbEndpoint.health_status === 'UNKNOWN' || lbEndpoint.health_status === 'HEALTHY').map( - (lbEndpoint) => { - /* The validator in the XdsClient class ensures that each endpoint has - * a socket_address with an IP address and a port_value. */ - const socketAddress = lbEndpoint.endpoint!.address!.socket_address!; - return { - host: socketAddress.address!, - port: socketAddress.port_value!, - }; - } - ); - if (addresses.length > 0) { - let localityArray = priorityList[endpoint.priority]; - if (localityArray === undefined) { - localityArray = []; - priorityList[endpoint.priority] = localityArray; - } - localityArray.push({ - locality: endpoint.locality!, - addresses: addresses, - weight: endpoint.load_balancing_weight.value, - }); - newLocalityPriorities.set( - localityToName(endpoint.locality!), - endpoint.priority - ); - } - } - - const newPriorityNames: string[] = []; - const addressList: LocalitySubchannelAddress[] = []; - const priorityChildren: Map = new Map< - string, - PriorityChild - >(); - /* The algorithm here is as follows: for each priority we are given, from - * high to low: - * - If the previous mapping had any of the same localities at the same or - * a lower priority, use the matching name from the highest such - * priority, unless the new mapping has already used that name. - * - Otherwise, construct a new name using this.nextPriorityChildNumber. - */ - for (const [priority, localityArray] of priorityList.entries()) { - // Skip priorities that have no localities with healthy endpoints - if (localityArray === undefined) { - continue; - } - /** - * Highest (smallest number) priority value that any of the localities in - * this locality array had a in the previous mapping. - */ - let highestOldPriority = Infinity; - for (const localityObj of localityArray) { - const oldPriority = this.localityPriorities.get( - localityToName(localityObj.locality) - ); - if ( - oldPriority !== undefined && - oldPriority >= priority && - oldPriority < highestOldPriority - ) { - highestOldPriority = oldPriority; - } - } - let newPriorityName: string; - if (highestOldPriority === Infinity) { - /* No existing priority at or below the same number as the priority we - * are looking at had any of the localities in this priority. So, we - * use a new name. */ - newPriorityName = `child${this.nextPriorityChildNumber++}`; - } else { - const newName = this.priorityNames[highestOldPriority]; - if (newPriorityNames.indexOf(newName) < 0) { - newPriorityName = newName; - } else { - newPriorityName = `child${this.nextPriorityChildNumber++}`; - } - } - newPriorityNames[priority] = newPriorityName; - - const childTargets: Map = new Map< - string, - WeightedTarget - >(); - for (const localityObj of localityArray) { - /* Use the endpoint picking policy from the config, default to - * round_robin. */ - const endpointPickingPolicy: LoadBalancingConfig[] = [ - ...this.lastestConfig.getEndpointPickingPolicy(), - validateLoadBalancingConfig({ round_robin: {} }), - ]; - let childPolicy: LoadBalancingConfig[]; - if (this.lastestConfig.getLrsLoadReportingServerName() !== undefined) { - childPolicy = [new LrsLoadBalancingConfig(this.lastestConfig.getCluster(), this.lastestConfig.getEdsServiceName() ?? '', this.lastestConfig.getLrsLoadReportingServerName()!, localityObj.locality, endpointPickingPolicy)]; - } else { - childPolicy = endpointPickingPolicy; - } - childTargets.set(localityToName(localityObj.locality), { - weight: localityObj.weight, - child_policy: childPolicy, - }); - for (const address of localityObj.addresses) { - addressList.push({ - localityPath: [ - newPriorityName, - localityToName(localityObj.locality), - ], - ...address, - }); - } - } - - const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); - let outlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined; - if (EXPERIMENTAL_OUTLIER_DETECTION) { - outlierDetectionConfig = this.lastestConfig.getOutlierDetection()?.copyWithChildPolicy([weightedTargetConfig]); - } - const priorityChildConfig = outlierDetectionConfig ?? weightedTargetConfig; - - priorityChildren.set(newPriorityName, { - config: [priorityChildConfig], - }); - } - /* Contract the priority names array if it is sparse. This config only - * cares about the order of priorities, not their specific numbers */ - const childConfig: PriorityLoadBalancingConfig = new PriorityLoadBalancingConfig(priorityChildren, newPriorityNames.filter((value) => value !== undefined)); - trace('Child update addresses: ' + addressList.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); - trace('Child update priority config: ' + JSON.stringify(childConfig.toJsonObject(), undefined, 2)); - this.childBalancer.updateAddressList( - addressList, - childConfig, - this.latestAttributes - ); - - this.localityPriorities = newLocalityPriorities; - this.priorityNames = newPriorityNames; - } - - updateAddressList( - addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, - attributes: { [key: string]: unknown } - ): void { - if (!(lbConfig instanceof EdsLoadBalancingConfig)) { - trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); - return; - } - trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); - this.lastestConfig = lbConfig; - this.latestAttributes = attributes; - const newEdsServiceName = lbConfig.getEdsServiceName() ?? lbConfig.getCluster(); - - /* If the name is changing, disable the old watcher before adding the new - * one */ - if (this.isWatcherActive && this.edsServiceName !== newEdsServiceName) { - trace('Removing old endpoint watcher for edsServiceName ' + this.edsServiceName) - getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName!, this.watcher); - /* Setting isWatcherActive to false here lets us have one code path for - * calling addEndpointWatcher */ - this.isWatcherActive = false; - /* If we have a new name, the latestEdsUpdate does not correspond to - * the new config, so it is no longer valid */ - this.latestEdsUpdate = null; - } - - this.edsServiceName = newEdsServiceName; - - if (!this.isWatcherActive) { - trace('Adding new endpoint watcher for edsServiceName ' + this.edsServiceName); - getSingletonXdsClient().addEndpointWatcher(this.edsServiceName, this.watcher); - this.isWatcherActive = true; - } - - if (lbConfig.getLrsLoadReportingServerName()) { - this.clusterDropStats = getSingletonXdsClient().addClusterDropStats( - lbConfig.getLrsLoadReportingServerName()!, - lbConfig.getCluster(), - lbConfig.getEdsServiceName() ?? '' - ); - } - - /* If updateAddressList is called after receiving an update and the update - * is still valid, we want to update the child config with the information - * in the new EdsLoadBalancingConfig. */ - this.updateChild(); - } - exitIdle(): void { - this.childBalancer.exitIdle(); - } - resetBackoff(): void { - this.childBalancer.resetBackoff(); - } - destroy(): void { - trace('Destroying load balancer with edsServiceName ' + this.edsServiceName); - if (this.edsServiceName) { - getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName, this.watcher); - } - this.childBalancer.destroy(); - } - getTypeName(): string { - return TYPE_NAME; - } -} - -export function setup() { - registerLoadBalancerType(TYPE_NAME, EdsLoadBalancer, EdsLoadBalancingConfig); -} diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index 12037a777..a9d03d0a6 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -52,6 +52,7 @@ export function isLocalitySubchannelAddress( export interface PriorityChild { config: LoadBalancingConfig[]; + ignore_reresolution_requests: boolean; } export class PriorityLoadBalancingConfig implements LoadBalancingConfig { @@ -97,8 +98,12 @@ export class PriorityLoadBalancingConfig implements LoadBalancingConfig { if (!('config' in childObj && Array.isArray(childObj.config))) { throw new Error(`Priority child ${childName} must have a config list`); } + if (!('ignore_reresolution_requests' in childObj && typeof childObj.ignore_reresolution_requests === 'boolean')) { + throw new Error(`Priority child ${childName} must have a boolean field ignore_reresolution_requests`); + } childrenMap.set(childName, { - config: childObj.config.map(validateLoadBalancingConfig) + config: childObj.config.map(validateLoadBalancingConfig), + ignore_reresolution_requests: childObj.ignore_reresolution_requests }); } return new PriorityLoadBalancingConfig(childrenMap, obj.priorities); @@ -125,6 +130,7 @@ interface PriorityChildBalancer { interface UpdateArgs { subchannelAddress: SubchannelAddress[]; lbConfig: LoadBalancingConfig; + ignoreReresolutionRequests: boolean; } export class PriorityLoadBalancer implements LoadBalancer { @@ -138,11 +144,16 @@ export class PriorityLoadBalancer implements LoadBalancer { private failoverTimer: NodeJS.Timer | null = null; private deactivationTimer: NodeJS.Timer | null = null; private seenReadyOrIdleSinceTransientFailure = false; - constructor(private parent: PriorityLoadBalancer, private name: string) { + constructor(private parent: PriorityLoadBalancer, private name: string, ignoreReresolutionRequests: boolean) { this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(this.parent.channelControlHelper, { updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.updateState(connectivityState, picker); }, + requestReresolution: () => { + if (!ignoreReresolutionRequests) { + this.parent.channelControlHelper.requestReresolution(); + } + } })); this.picker = new QueuePicker(this.childBalancer); this.startFailoverTimer(); @@ -329,16 +340,17 @@ export class PriorityLoadBalancer implements LoadBalancer { let child = this.children.get(childName); /* If the child doesn't already exist, create it and update it. */ if (child === undefined) { - child = new this.PriorityChildImpl(this, childName); - this.children.set(childName, child); const childUpdate = this.latestUpdates.get(childName); - if (childUpdate !== undefined) { - child.updateAddressList( - childUpdate.subchannelAddress, - childUpdate.lbConfig, - this.latestAttributes - ); + if (childUpdate === undefined) { + continue; } + child = new this.PriorityChildImpl(this, childName, childUpdate.ignoreReresolutionRequests); + this.children.set(childName, child); + child.updateAddressList( + childUpdate.subchannelAddress, + childUpdate.lbConfig, + this.latestAttributes + ); } else { /* We're going to try to use this child, so reactivate it if it has been * deactivated */ @@ -426,6 +438,7 @@ export class PriorityLoadBalancer implements LoadBalancer { this.latestUpdates.set(childName, { subchannelAddress: childAddresses, lbConfig: chosenChildConfig, + ignoreReresolutionRequests: childConfig.ignore_reresolution_requests }); const existingChild = this.children.get(childName); if (existingChild !== undefined) { diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts index 0972ce97d..e4d464b6d 100644 --- a/packages/grpc-js-xds/src/resources.ts +++ b/packages/grpc-js-xds/src/resources.ts @@ -21,6 +21,7 @@ import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; +import { ClusterConfig__Output } from './generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig'; import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; export const EDS_TYPE_URL = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; @@ -40,10 +41,14 @@ export const HTTP_CONNECTION_MANGER_TYPE_URL = export type HttpConnectionManagerTypeUrl = 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager'; +export const CLUSTER_CONFIG_TYPE_URL = 'type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig'; + +export type ClusterConfigTypeUrl = 'type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig'; + /** * Map type URLs to their corresponding message types */ -export type AdsOutputType = T extends EdsTypeUrl +export type AdsOutputType = T extends EdsTypeUrl ? ClusterLoadAssignment__Output : T extends CdsTypeUrl ? Cluster__Output @@ -51,14 +56,17 @@ export type AdsOutputType = ? RouteConfiguration__Output : T extends LdsTypeUrl ? Listener__Output - : HttpConnectionManager__Output; + : T extends HttpConnectionManagerTypeUrl + ? HttpConnectionManager__Output + : ClusterConfig__Output; const resourceRoot = loadProtosWithOptionsSync([ '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'], { + 'envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto', + 'envoy/extensions/clusters/aggregate/v3/cluster.proto'], { keepCase: true, includeDirs: [ // Paths are relative to src/build @@ -77,7 +85,7 @@ const toObjectOptions = { oneofs: true } -export function decodeSingleResource(targetTypeUrl: T, message: Buffer): AdsOutputType { +export function decodeSingleResource(targetTypeUrl: T, message: Buffer): AdsOutputType { const name = targetTypeUrl.substring(targetTypeUrl.lastIndexOf('/') + 1); const type = resourceRoot.lookup(name); if (type) { diff --git a/packages/grpc-js-xds/src/xds-cluster-impl.ts b/packages/grpc-js-xds/src/xds-cluster-impl.ts new file mode 100644 index 000000000..8f64dfa84 --- /dev/null +++ b/packages/grpc-js-xds/src/xds-cluster-impl.ts @@ -0,0 +1,272 @@ +/* + * 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. + * + */ + +import { experimental, logVerbosity, status as Status, Metadata, connectivityState } from "@grpc/grpc-js"; +import { getSingletonXdsClient, XdsClusterDropStats } from "./xds-client"; + +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import LoadBalancer = experimental.LoadBalancer; +import registerLoadBalancerType = experimental.registerLoadBalancerType; +import SubchannelAddress = experimental.SubchannelAddress; +import Picker = experimental.Picker; +import PickArgs = experimental.PickArgs; +import PickResult = experimental.PickResult; +import PickResultType = experimental.PickResultType; +import ChannelControlHelper = experimental.ChannelControlHelper; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import createChildChannelControlHelper = experimental.createChildChannelControlHelper; +import getFirstUsableConfig = experimental.getFirstUsableConfig; + +const TRACER_NAME = 'xds_cluster_impl'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'xds_cluster_impl'; + +const DEFAULT_MAX_CONCURRENT_REQUESTS = 1024; + +export interface DropCategory { + category: string; + requests_per_million: number; +} + +function validateDropCategory(obj: any): DropCategory { + if (!('category' in obj && typeof obj.category === 'string')) { + throw new Error('xds_cluster_impl config drop_categories entry must have a string field category'); + } + if (!('requests_per_million' in obj && typeof obj.requests_per_million === 'number')) { + throw new Error('xds_cluster_impl config drop_categories entry must have a number field requests_per_million'); + } + return obj; +} + +export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { + private maxConcurrentRequests: number; + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + const jsonObj: {[key: string]: any} = { + cluster: this.cluster, + drop_categories: this.dropCategories, + child_policy: this.childPolicy.map(policy => policy.toJsonObject()), + max_concurrent_requests: this.maxConcurrentRequests + }; + if (this.edsServiceName !== undefined) { + jsonObj.eds_service_name = this.edsServiceName; + } + if (this.lrsLoadReportingServerName !== undefined) { + jsonObj.lrs_load_reporting_server_name = this.lrsLoadReportingServerName; + } + return { + [TYPE_NAME]: jsonObj + }; + } + + constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string, maxConcurrentRequests?: number) { + this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; + } + + getCluster() { + return this.cluster; + } + + getEdsServiceName() { + return this.edsServiceName; + } + + getLrsLoadReportingServerName() { + return this.lrsLoadReportingServerName; + } + + getMaxConcurrentRequests() { + return this.maxConcurrentRequests; + } + + getDropCategories() { + return this.dropCategories; + } + + getChildPolicy() { + return this.childPolicy; + } + + static createFromJson(obj: any): XdsClusterImplLoadBalancingConfig { + if (!('cluster' in obj && typeof obj.cluster === 'string')) { + throw new Error('xds_cluster_impl config must have a string field cluster'); + } + if ('eds_service_name' in obj && !(obj.eds_service_name === undefined || typeof obj.eds_service_name === 'string')) { + throw new Error('xds_cluster_impl config eds_service_name field must be a string if provided'); + } + if ('lrs_load_reporting_server_name' in obj && (!obj.lrs_load_reporting_server_name === undefined || typeof obj.lrs_load_reporting_server_name === 'string')) { + throw new Error('xds_cluster_impl config lrs_load_reporting_server_name must be a string if provided'); + } + if ('max_concurrent_requests' in obj && (!obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { + throw new Error('xds_cluster_impl config max_concurrent_requests must be a number if provided'); + } + if (!('drop_categories' in obj && Array.isArray(obj.drop_categories))) { + throw new Error('xds_cluster_impl config must have an array field drop_categories'); + } + if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { + throw new Error('xds_cluster_impl config must have an array field child_policy'); + } + return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), obj.child_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name, obj.max_concurrent_requests); + } +} + +class CallCounterMap { + private callCounters = new Map(); + + startCall(key: string) { + const currentValue = this.callCounters.get(key) ?? 0; + this.callCounters.set(key, currentValue + 1); + } + + endCall(key: string) { + const currentValue = this.callCounters.get(key) ?? 0; + if (currentValue - 1 <= 0) { + this.callCounters.delete(key); + } else { + this.callCounters.set(key, currentValue - 1); + } + } + + getConcurrentRequests(key: string) { + return this.callCounters.get(key) ?? 0; + } +} + +const callCounterMap = new CallCounterMap(); + +class DropPicker implements Picker { + constructor(private originalPicker: Picker, private callCounterMapKey: string, private maxConcurrentRequests: number, private dropCategories: DropCategory[], private clusterDropStats: XdsClusterDropStats | null) {} + + private checkForMaxConcurrentRequestsDrop(): boolean { + return callCounterMap.getConcurrentRequests(this.callCounterMapKey) >= this.maxConcurrentRequests; + } + + private checkForDrop(): string | null { + for (const dropCategory of this.dropCategories) { + if (Math.random() * 1_000_000 < dropCategory.requests_per_million) { + return dropCategory.category; + } + } + return null; + } + + pick(pickArgs: PickArgs): PickResult { + let details: string | null = null; + if (this.checkForMaxConcurrentRequestsDrop()) { + details = 'Call dropped by load balancing policy.'; + this.clusterDropStats?.addUncategorizedCallDropped(); + } else { + const category = this.checkForDrop(); + if (category !== null) { + details = `Call dropped by load balancing policy. Category: ${category}`; + this.clusterDropStats?.addCallDropped(category); + } + } + if (details === null) { + const originalPick = this.originalPicker.pick(pickArgs); + return { + pickResultType: originalPick.pickResultType, + status: originalPick.status, + subchannel: originalPick.subchannel, + onCallStarted: () => { + originalPick.onCallStarted?.(); + callCounterMap.startCall(this.callCounterMapKey); + }, + onCallEnded: status => { + originalPick.onCallEnded?.(status); + callCounterMap.endCall(this.callCounterMapKey); + } + }; + } else { + return { + pickResultType: PickResultType.DROP, + status: { + code: Status.UNAVAILABLE, + details: details, + metadata: new Metadata(), + }, + subchannel: null, + onCallEnded: null, + onCallStarted: null + }; + } + } +} + +function getCallCounterMapKey(cluster: string, edsServiceName?: string): string { + return `{${cluster},${edsServiceName ?? ''}}`; +} + +class XdsClusterImplBalancer implements LoadBalancer { + private childBalancer: ChildLoadBalancerHandler; + private latestConfig: XdsClusterImplLoadBalancingConfig | null = null; + private clusterDropStats: XdsClusterDropStats | null = null; + + constructor(private readonly channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { + updateState: (connectivityState, originalPicker) => { + if (this.latestConfig === null) { + channelControlHelper.updateState(connectivityState, originalPicker); + } else { + const picker = new DropPicker(originalPicker, getCallCounterMapKey(this.latestConfig.getCluster(), this.latestConfig.getEdsServiceName()), this.latestConfig.getMaxConcurrentRequests(), this.latestConfig.getDropCategories(), this.clusterDropStats); + channelControlHelper.updateState(connectivityState, picker); + } + } + })); + } + updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof XdsClusterImplLoadBalancingConfig)) { + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + return; + } + trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); + this.latestConfig = lbConfig; + + if (lbConfig.getLrsLoadReportingServerName()) { + this.clusterDropStats = getSingletonXdsClient().addClusterDropStats( + lbConfig.getLrsLoadReportingServerName()!, + lbConfig.getCluster(), + lbConfig.getEdsServiceName() ?? '' + ); + } + + this.childBalancer.updateAddressList(addressList, getFirstUsableConfig(lbConfig.getChildPolicy(), true), attributes); + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + this.childBalancer.destroy(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, XdsClusterImplBalancer, XdsClusterImplLoadBalancingConfig); +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-cluster-resolver.ts b/packages/grpc-js-xds/src/xds-cluster-resolver.ts new file mode 100644 index 000000000..543d619dc --- /dev/null +++ b/packages/grpc-js-xds/src/xds-cluster-resolver.ts @@ -0,0 +1,466 @@ +/* + * 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. + * + */ + +import { experimental, logVerbosity } from "@grpc/grpc-js"; +import { EXPERIMENTAL_OUTLIER_DETECTION } from "./environment"; +import { Locality__Output } from "./generated/envoy/config/core/v3/Locality"; +import { ClusterLoadAssignment__Output } from "./generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { LrsLoadBalancingConfig } from "./load-balancer-lrs"; +import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from "./load-balancer-priority"; +import { WeightedTarget, WeightedTargetLoadBalancingConfig } from "./load-balancer-weighted-target"; +import { getSingletonXdsClient } from "./xds-client"; +import { DropCategory, XdsClusterImplLoadBalancingConfig } from "./xds-cluster-impl"; +import { Watcher } from "./xds-stream-state/xds-stream-state"; + +import LoadBalancingConfig = experimental.LoadBalancingConfig; +import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import LoadBalancer = experimental.LoadBalancer; +import Resolver = experimental.Resolver; +import SubchannelAddress = experimental.SubchannelAddress; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import createResolver = experimental.createResolver; +import ChannelControlHelper = experimental.ChannelControlHelper; +import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; +import subchannelAddressToString = experimental.subchannelAddressToString; + +const TRACER_NAME = 'xds_cluster_resolver'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +export interface DiscoveryMechanism { + cluster: string; + lrs_load_reporting_server_name?: string; + max_concurrent_requests?: number; + type: 'EDS' | 'LOGICAL_DNS'; + eds_service_name?: string; + dns_hostname?: string; + outlier_detection?: OutlierDetectionLoadBalancingConfig; +} + +function validateDiscoveryMechanism(obj: any): DiscoveryMechanism { + if (!('cluster' in obj && typeof obj.cluster === 'string')) { + throw new Error('discovery_mechanisms entry must have a string field cluster'); + } + if (!('type' in obj && (obj.type === 'EDS' || obj.type === 'LOGICAL_DNS'))) { + throw new Error('discovery_mechanisms entry must have a field "type" with the value "EDS" or "LOGICAL_DNS"'); + } + if ('lrs_load_reporting_server_name' in obj && typeof obj.lrs_load_reporting_server_name !== 'string') { + throw new Error('discovery_mechanisms entry lrs_load_reporting_server_name field must be a string if provided'); + } + if ('max_concurrent_requests' in obj && typeof obj.max_concurrent_requests !== "number") { + throw new Error('discovery_mechanisms entry max_concurrent_requests field must be a number if provided'); + } + if ('eds_service_name' in obj && typeof obj.eds_service_name !== 'string') { + throw new Error('discovery_mechanisms entry eds_service_name field must be a string if provided'); + } + if ('dns_hostname' in obj && typeof obj.dns_hostname !== 'string') { + throw new Error('discovery_mechanisms entry dns_hostname field must be a string if provided'); + } + if (EXPERIMENTAL_OUTLIER_DETECTION) { + const outlierDetectionConfig = validateLoadBalancingConfig(obj.outlier_detection); + if (!(outlierDetectionConfig instanceof OutlierDetectionLoadBalancingConfig)) { + throw new Error('eds config outlier_detection must be a valid outlier detection config if provided'); + } + return {...obj, outlier_detection: outlierDetectionConfig}; + } + return obj; +} + +const TYPE_NAME = 'xds_cluster_resolver'; + +export class XdsClusterResolverLoadBalancingConfig implements LoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + return { + [TYPE_NAME]: { + discovery_mechanisms: this.discoveryMechanisms.map(mechanism => ({...mechanism, outlier_detection: mechanism.outlier_detection?.toJsonObject()})), + locality_picking_policy: this.localityPickingPolicy.map(policy => policy.toJsonObject()), + endpoint_picking_policy: this.endpointPickingPolicy.map(policy => policy.toJsonObject()) + } + } + } + + constructor(private discoveryMechanisms: DiscoveryMechanism[], private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[]) {} + + getDiscoveryMechanisms() { + return this.discoveryMechanisms; + } + + getLocalityPickingPolicy() { + return this.localityPickingPolicy; + } + + getEndpointPickingPolicy() { + return this.endpointPickingPolicy; + } + + static createFromJson(obj: any): XdsClusterResolverLoadBalancingConfig { + if (!('discovery_mechanisms' in obj && Array.isArray(obj.discovery_mechanisms))) { + throw new Error('xds_cluster_resolver config must have a discovery_mechanisms array'); + } + if (!('locality_picking_policy' in obj && Array.isArray(obj.locality_picking_policy))) { + throw new Error('xds_cluster_resolver config must have a locality_picking_policy array'); + } + if (!('endpoint_picking_policy' in obj && Array.isArray(obj.endpoint_picking_policy))) { + throw new Error('xds_cluster_resolver config must have a endpoint_picking_policy array'); + } + return new XdsClusterResolverLoadBalancingConfig( + obj.discovery_mechanisms.map(validateDiscoveryMechanism), + obj.locality_picking_policy.map(validateLoadBalancingConfig), + obj.endpoint_picking_policy.map(validateLoadBalancingConfig) + ); + } +} + +interface LocalityEntry { + locality: Locality__Output; + weight: number; + addresses: SubchannelAddress[]; +} + +interface PriorityEntry { + localities: LocalityEntry[]; + dropCategories: DropCategory[]; +} + +interface DiscoveryMechanismEntry { + discoveryMechanism: DiscoveryMechanism; + localityPriorities: Map; + priorityNames: string[]; + nextPriorityChildNumber: number; + watcher?: Watcher; + resolver?: Resolver; + latestUpdate?: PriorityEntry[]; +} + +function getEdsPriorities(edsUpdate: ClusterLoadAssignment__Output): PriorityEntry[] { + const result: PriorityEntry[] = []; + const dropCategories: DropCategory[] = []; + if (edsUpdate.policy) { + for (const dropOverload of edsUpdate.policy.drop_overloads) { + if (!dropOverload.drop_percentage) { + continue; + } + let requestsPerMillion: number; + switch (dropOverload.drop_percentage.denominator) { + case 'HUNDRED': + requestsPerMillion = dropOverload.drop_percentage.numerator * 10_000; + break; + case 'TEN_THOUSAND': + requestsPerMillion = dropOverload.drop_percentage.numerator * 100; + break; + case 'MILLION': + requestsPerMillion = dropOverload.drop_percentage.numerator; + break; + } + dropCategories.push({ + category: dropOverload.category, + requests_per_million: requestsPerMillion + }); + } + } + for (const endpoint of edsUpdate.endpoints) { + if (!endpoint.load_balancing_weight) { + continue; + } + const addresses: SubchannelAddress[] = endpoint.lb_endpoints.filter(lbEndpoint => lbEndpoint.health_status === 'UNKNOWN' || lbEndpoint.health_status === 'HEALTHY').map( + (lbEndpoint) => { + /* The validator in the XdsClient class ensures that each endpoint has + * a socket_address with an IP address and a port_value. */ + const socketAddress = lbEndpoint.endpoint!.address!.socket_address!; + return { + host: socketAddress.address!, + port: socketAddress.port_value!, + }; + } + ); + if (addresses.length === 0) { + continue; + } + let priorityEntry: PriorityEntry; + if (result[endpoint.priority]) { + priorityEntry = result[endpoint.priority]; + } else { + priorityEntry = { + localities: [], + dropCategories: dropCategories + }; + result[endpoint.priority] = priorityEntry; + } + priorityEntry.localities.push({ + locality: endpoint.locality!, + addresses: addresses, + weight: endpoint.load_balancing_weight.value + }); + } + // Collapse spaces in sparse array + return result.filter(priority => priority); +} + +function getDnsPriorities(addresses: SubchannelAddress[]): PriorityEntry[] { + return [{ + localities: [{ + locality: { + region: '', + zone: '', + sub_zone: '' + }, + weight: 1, + addresses: addresses + }], + dropCategories: [] + }]; +} + +function localityToName(locality: Locality__Output) { + return `{region=${locality.region},zone=${locality.zone},sub_zone=${locality.sub_zone}}`; +} + +function getNextPriorityName(entry: DiscoveryMechanismEntry): string { + return `cluster=${entry.discoveryMechanism.cluster}, child_number=${entry.nextPriorityChildNumber++}`; +} + +export class XdsClusterResolver implements LoadBalancer { + private discoveryMechanismList: DiscoveryMechanismEntry[] = []; + private latestConfig: XdsClusterResolverLoadBalancingConfig | null = null; + private latestAttributes: { [key: string]: unknown; } = {}; + private childBalancer: ChildLoadBalancerHandler; + + constructor(private readonly channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(channelControlHelper, { + requestReresolution: () => { + for (const entry of this.discoveryMechanismList) { + entry.resolver?.updateResolution(); + } + } + })); + } + + private maybeUpdateChild() { + if (!this.latestConfig) { + return; + } + for (const entry of this.discoveryMechanismList) { + if (!entry.latestUpdate) { + return; + } + } + const newPriorityNames: string[] = []; + const priorityChildren = new Map(); + const newLocalityPriorities = new Map(); + const addressList: LocalitySubchannelAddress[] = []; + for (const entry of this.discoveryMechanismList) { + const defaultEndpointPickingPolicy = entry.discoveryMechanism.type === 'EDS' ? validateLoadBalancingConfig({ round_robin: {} }) : validateLoadBalancingConfig({ pick_first: {} }); + const endpointPickingPolicy: LoadBalancingConfig[] = [ + ...this.latestConfig.getEndpointPickingPolicy(), + defaultEndpointPickingPolicy + ]; + for (const [priority, priorityEntry] of entry.latestUpdate!.entries()) { + /** + * Highest (smallest number) priority value that any of the localities in + * this locality array had a in the previous mapping. + */ + let highestOldPriority = Infinity; + for (const localityObj of priorityEntry.localities) { + const oldPriority = entry.localityPriorities.get( + localityToName(localityObj.locality) + ); + if ( + oldPriority !== undefined && + oldPriority >= priority && + oldPriority < highestOldPriority + ) { + highestOldPriority = oldPriority; + } + } + let newPriorityName: string; + if (highestOldPriority === Infinity) { + /* No existing priority at or below the same number as the priority we + * are looking at had any of the localities in this priority. So, we + * use a new name. */ + newPriorityName = getNextPriorityName(entry); + } else { + const newName = entry.priorityNames[highestOldPriority]; + if (newPriorityNames.indexOf(newName) < 0) { + newPriorityName = newName; + } else { + newPriorityName = getNextPriorityName(entry); + } + } + newPriorityNames[priority] = newPriorityName; + + const childTargets = new Map(); + for (const localityObj of priorityEntry.localities) { + let childPolicy: LoadBalancingConfig[]; + if (entry.discoveryMechanism.lrs_load_reporting_server_name !== undefined) { + childPolicy = [new LrsLoadBalancingConfig(entry.discoveryMechanism.cluster, entry.discoveryMechanism.eds_service_name ?? '', entry.discoveryMechanism.lrs_load_reporting_server_name!, localityObj.locality, endpointPickingPolicy)]; + } else { + childPolicy = endpointPickingPolicy; + } + childTargets.set(localityToName(localityObj.locality), { + weight: localityObj.weight, + child_policy: childPolicy, + }); + for (const address of localityObj.addresses) { + addressList.push({ + localityPath: [ + newPriorityName, + localityToName(localityObj.locality), + ], + ...address, + }); + } + } + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! INSERT xds_cluster_impl CONFIG !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); + const xdsClusterImplConfig = new XdsClusterImplLoadBalancingConfig(entry.discoveryMechanism.cluster, priorityEntry.dropCategories, [weightedTargetConfig], entry.discoveryMechanism.eds_service_name, entry.discoveryMechanism.lrs_load_reporting_server_name, entry.discoveryMechanism.max_concurrent_requests); + let outlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined; + if (EXPERIMENTAL_OUTLIER_DETECTION) { + outlierDetectionConfig = entry.discoveryMechanism.outlier_detection?.copyWithChildPolicy([xdsClusterImplConfig]); + } + const priorityChildConfig = outlierDetectionConfig ?? xdsClusterImplConfig; + + priorityChildren.set(newPriorityName, { + config: [priorityChildConfig], + ignore_reresolution_requests: entry.discoveryMechanism.type === 'EDS' + }); + } + entry.localityPriorities = newLocalityPriorities; + entry.priorityNames = newPriorityNames; + } + const childConfig: PriorityLoadBalancingConfig = new PriorityLoadBalancingConfig(priorityChildren, newPriorityNames); + trace('Child update addresses: ' + addressList.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); + trace('Child update priority config: ' + JSON.stringify(childConfig.toJsonObject(), undefined, 2)); + this.childBalancer.updateAddressList( + addressList, + childConfig, + this.latestAttributes + ); + } + + updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof XdsClusterResolverLoadBalancingConfig)) { + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2)); + return; + } + trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); + this.latestAttributes = attributes; + if (this.discoveryMechanismList.length === 0) { + for (const mechanism of lbConfig.getDiscoveryMechanisms()) { + const mechanismEntry: DiscoveryMechanismEntry = { + discoveryMechanism: mechanism, + localityPriorities: new Map(), + priorityNames: [], + nextPriorityChildNumber: 0 + }; + if (mechanism.type === 'EDS') { + const edsServiceName = mechanism.eds_service_name ?? mechanism.cluster; + const watcher: Watcher = { + onValidUpdate: update => { + mechanismEntry.latestUpdate = getEdsPriorities(update); + this.maybeUpdateChild(); + }, + onResourceDoesNotExist: () => { + trace('Resource does not exist: ' + edsServiceName); + mechanismEntry.latestUpdate = [{localities: [], dropCategories: []}]; + }, + onTransientError: error => { + if (!mechanismEntry.latestUpdate) { + trace('xDS request failed with error ' + error); + mechanismEntry.latestUpdate = [{localities: [], dropCategories: []}]; + } + } + }; + mechanismEntry.watcher = watcher; + getSingletonXdsClient().addEndpointWatcher(edsServiceName, watcher); + } else { + const resolver = createResolver({scheme: 'dns', path: mechanism.dns_hostname!}, { + onSuccessfulResolution: addressList => { + mechanismEntry.latestUpdate = getDnsPriorities(addressList); + this.maybeUpdateChild(); + }, + onError: error => { + if (!mechanismEntry.latestUpdate) { + trace('DNS resolution for ' + mechanism.dns_hostname + ' failed with error ' + error); + mechanismEntry.latestUpdate = [{localities: [], dropCategories: []}]; + } + } + }, {'grpc.service_config_disable_resolution': 1}); + mechanismEntry.resolver = resolver; + resolver.updateResolution(); + } + this.discoveryMechanismList.push(mechanismEntry); + } + } else { + /* The ChildLoadBalancerHandler subclass guarantees that each discovery + * mechanism in the new update corresponds to the same entry in the + * existing discoveryMechanismList, and that any differences will not + * result in changes to the watcher/resolver. */ + for (let i = 0; i < this.discoveryMechanismList.length; i++) { + this.discoveryMechanismList[i].discoveryMechanism = lbConfig.getDiscoveryMechanisms()[i]; + } + this.maybeUpdateChild(); + } + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + for (const mechanismEntry of this.discoveryMechanismList) { + if (mechanismEntry.watcher) { + const edsServiceName = mechanismEntry.discoveryMechanism.eds_service_name ?? mechanismEntry.discoveryMechanism.cluster; + getSingletonXdsClient().removeEndpointWatcher(edsServiceName, mechanismEntry.watcher); + } + mechanismEntry.resolver?.destroy(); + } + this.discoveryMechanismList = []; + this.childBalancer.destroy(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export class XdsClusterResolverChildPolicyHandler extends ChildLoadBalancerHandler { + protected configUpdateRequiresNewPolicyInstance(oldConfig: LoadBalancingConfig, newConfig: LoadBalancingConfig): boolean { + if (!(oldConfig instanceof XdsClusterResolverLoadBalancingConfig && newConfig instanceof XdsClusterResolverLoadBalancingConfig)) { + return super.configUpdateRequiresNewPolicyInstance(oldConfig, newConfig); + } + if (oldConfig.getDiscoveryMechanisms().length !== newConfig.getDiscoveryMechanisms().length) { + return true; + } + for (let i = 0; i < oldConfig.getDiscoveryMechanisms().length; i++) { + const oldDiscoveryMechanism = oldConfig.getDiscoveryMechanisms()[i]; + const newDiscoveryMechanism = newConfig.getDiscoveryMechanisms()[i]; + if (oldDiscoveryMechanism.type !== newDiscoveryMechanism.type || + oldDiscoveryMechanism.cluster !== newDiscoveryMechanism.cluster || + oldDiscoveryMechanism.eds_service_name !== newDiscoveryMechanism.eds_service_name || + oldDiscoveryMechanism.dns_hostname !== newDiscoveryMechanism.dns_hostname || + oldDiscoveryMechanism.lrs_load_reporting_server_name !== newDiscoveryMechanism.lrs_load_reporting_server_name) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index a7c28219b..26c9596ed 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -4,6 +4,7 @@ export { ResolverListener, registerResolver, ConfigSelector, + createResolver } from './resolver'; export { GrpcUri, uriToString } from './uri-parser'; export { Duration, durationToMs } from './duration'; diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 64b341810..86f29e2dc 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -33,6 +33,7 @@ const TYPE_NAME = 'child_load_balancer_helper'; export class ChildLoadBalancerHandler implements LoadBalancer { private currentChild: LoadBalancer | null = null; private pendingChild: LoadBalancer | null = null; + private latestConfig: LoadBalancingConfig | null = null; private ChildPolicyHelper = class { private child: LoadBalancer | null = null; @@ -85,6 +86,10 @@ export class ChildLoadBalancerHandler implements LoadBalancer { constructor(private readonly channelControlHelper: ChannelControlHelper) {} + protected configUpdateRequiresNewPolicyInstance(oldConfig: LoadBalancingConfig, newConfig: LoadBalancingConfig): boolean { + return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName(); + } + /** * Prerequisites: lbConfig !== null and lbConfig.name is registered * @param addressList @@ -99,7 +104,8 @@ export class ChildLoadBalancerHandler implements LoadBalancer { let childToUpdate: LoadBalancer; if ( this.currentChild === null || - this.currentChild.getTypeName() !== lbConfig.getLoadBalancerName() + this.latestConfig === null || + this.configUpdateRequiresNewPolicyInstance(this.latestConfig, lbConfig) ) { const newHelper = new this.ChildPolicyHelper(this); const newChild = createLoadBalancer(lbConfig, newHelper)!; From b342001b38237f337af23d9f81fed25b3e6507d6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 12 Jan 2023 09:24:21 -0800 Subject: [PATCH 412/694] grpc-js: Reference session in transport when there are active calls --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/transport.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 700c7b773..d9ef213db 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.3", + "version": "1.8.4", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 64f770945..314971029 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -354,12 +354,14 @@ class Http2Transport implements Transport { private removeActiveCall(call: Http2SubchannelCall) { this.activeCalls.delete(call); if (this.activeCalls.size === 0 && !this.keepaliveWithoutCalls) { + this.session.unref(); this.stopKeepalivePings(); } } private addActiveCall(call: Http2SubchannelCall) { if (this.activeCalls.size === 0 && !this.keepaliveWithoutCalls) { + this.session.ref(); this.startKeepalivePings(); } this.activeCalls.add(call); From fade30bd0a27bfa32395a35a1bf61ab15cb62f8e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 12 Jan 2023 09:47:19 -0800 Subject: [PATCH 413/694] grpc-js: Make call and stream tracking more consistent --- packages/grpc-js/src/subchannel-call.ts | 3 +-- packages/grpc-js/src/transport.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index a560ed4d7..16ac35b5e 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -198,6 +198,7 @@ export class Http2SubchannelCall implements SubchannelCall { * we can bubble up the error message from that event. */ process.nextTick(() => { this.trace('HTTP/2 stream closed with code ' + http2Stream.rstCode); + this.callEventTracker.onStreamEnd(this.finalStatus?.code === Status.OK); /* If we have a final status with an OK status code, that means that * we have received all of the messages and we have processed the * trailers and the call completed successfully, so it doesn't matter @@ -288,7 +289,6 @@ export class Http2SubchannelCall implements SubchannelCall { ); this.internalError = err; } - this.callEventTracker.onStreamEnd(false); }); } @@ -403,7 +403,6 @@ export class Http2SubchannelCall implements SubchannelCall { } private handleTrailers(headers: http2.IncomingHttpHeaders) { - this.callEventTracker.onStreamEnd(true); let headersString = ''; for (const header of Object.keys(headers)) { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 314971029..fc9042278 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -420,6 +420,7 @@ class Http2Transport implements Transport { }, onCallEnd: status => { subchannelCallStatsTracker.onCallEnd?.(status); + this.removeActiveCall(call); }, onStreamEnd: success => { if (success) { @@ -427,7 +428,6 @@ class Http2Transport implements Transport { } else { this.streamTracker.addCallFailed(); } - this.removeActiveCall(call); subchannelCallStatsTracker.onStreamEnd?.(success); } } From 7eaebaf1ed601b16c988702cf420a9aeb24a7130 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 12 Jan 2023 10:00:28 -0800 Subject: [PATCH 414/694] grpc-js: Undo changes to stream tracking --- packages/grpc-js/src/subchannel-call.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 16ac35b5e..a560ed4d7 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -198,7 +198,6 @@ export class Http2SubchannelCall implements SubchannelCall { * we can bubble up the error message from that event. */ process.nextTick(() => { this.trace('HTTP/2 stream closed with code ' + http2Stream.rstCode); - this.callEventTracker.onStreamEnd(this.finalStatus?.code === Status.OK); /* If we have a final status with an OK status code, that means that * we have received all of the messages and we have processed the * trailers and the call completed successfully, so it doesn't matter @@ -289,6 +288,7 @@ export class Http2SubchannelCall implements SubchannelCall { ); this.internalError = err; } + this.callEventTracker.onStreamEnd(false); }); } @@ -403,6 +403,7 @@ export class Http2SubchannelCall implements SubchannelCall { } private handleTrailers(headers: http2.IncomingHttpHeaders) { + this.callEventTracker.onStreamEnd(true); let headersString = ''; for (const header of Object.keys(headers)) { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; From a23dc843af98a91e96eec3d980bcb7e60a11b764 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Thu, 12 Jan 2023 17:18:00 -0800 Subject: [PATCH 415/694] xds interop: Fix buildscripts not continuing on a failed test suite Apparently there's a difference between bash 3 and bash 4. OSX comes with bash 3 out-of-box, so for whoever wrote this logic it "worked on my machine". --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index ca747f7ea..b654909f2 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -167,7 +167,7 @@ main() { local failed_tests=0 test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection_test") for test in "${test_suites[@]}"; do - run_test $test || (( failed_tests++ )) + run_test $test || (( failed_tests++ )) && true done echo "Failed test suites: ${failed_tests}" if (( failed_tests > 0 )); then From 466bc3cdd052e0c86826a068a05b6a5b032bf9e0 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 13 Jan 2023 20:39:32 -0500 Subject: [PATCH 416/694] Address the feedback: use pre-increment instead of `&& true` --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index b654909f2..b87e3306c 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -167,7 +167,7 @@ main() { local failed_tests=0 test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection_test") for test in "${test_suites[@]}"; do - run_test $test || (( failed_tests++ )) && true + run_test $test || (( ++failed_tests )) done echo "Failed test suites: ${failed_tests}" if (( failed_tests > 0 )); then From d441aa687d52032877ab46b4a44efd2251d8fe05 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 17 Jan 2023 09:58:24 -0800 Subject: [PATCH 417/694] Merge pull request #2323 from sergiitk/xds-interop-fix-buildscript-suites xds interop: Fix buildscripts not continuing on a failed test suite --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index ca747f7ea..b87e3306c 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -167,7 +167,7 @@ main() { local failed_tests=0 test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection_test") for test in "${test_suites[@]}"; do - run_test $test || (( failed_tests++ )) + run_test $test || (( ++failed_tests )) done echo "Failed test suites: ${failed_tests}" if (( failed_tests > 0 )); then From 7a6fa275fe5204f54a606a34f97cd6df7161be00 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 18 Jan 2023 10:54:07 -0800 Subject: [PATCH 418/694] grpc-js-xds: weighted clusters: stop checking total_weight, check weight sum <= uint32 max --- packages/grpc-js-xds/src/xds-stream-state/rds-state.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index 891eb7c8e..fef694517 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -32,6 +32,8 @@ const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ 'suffix_match']; const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; +const UINT32_MAX = 0xFFFFFFFF; + function durationToMs(duration: Duration__Output | null): number | null { if (duration === null) { return null; @@ -130,14 +132,11 @@ export class RdsState extends BaseXdsStreamState imp } } if (route.route!.cluster_specifier === 'weighted_clusters') { - if (route.route.weighted_clusters!.total_weight?.value === 0) { - return false; - } let weightSum = 0; for (const clusterWeight of route.route.weighted_clusters!.clusters) { weightSum += clusterWeight.weight?.value ?? 0; } - if (weightSum !== route.route.weighted_clusters!.total_weight?.value ?? 100) { + if (weightSum === 0 || weightSum > UINT32_MAX) { return false; } if (EXPERIMENTAL_FAULT_INJECTION) { From 1924d4a9fd5e23374c817bdb22421d0bac022cfc Mon Sep 17 00:00:00 2001 From: Jacob Sapoznikow Date: Sun, 22 Jan 2023 22:54:12 +0000 Subject: [PATCH 419/694] Begin aarch64 support --- .gitignore | 2 + packages/grpc-tools/.gitignore | 7 ++ packages/grpc-tools/CMakeLists.txt | 83 ++++++++++--------- packages/grpc-tools/build_binaries.sh | 76 +++++++++-------- packages/grpc-tools/linux.toolchain.cmake | 28 +++++++ .../grpc-tools/linux_32bit.toolchain.cmake | 3 - .../grpc-tools/linux_64bit.toolchain.cmake | 3 - tools/release/native/Dockerfile | 11 ++- 8 files changed, 131 insertions(+), 82 deletions(-) create mode 100644 packages/grpc-tools/.gitignore create mode 100644 packages/grpc-tools/linux.toolchain.cmake delete mode 100644 packages/grpc-tools/linux_32bit.toolchain.cmake delete mode 100644 packages/grpc-tools/linux_64bit.toolchain.cmake diff --git a/.gitignore b/.gitignore index fce837e45..8e63d8bf0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules/ npm-debug.log yarn-error.log yarn.lock +artifacts # Emacs temp files *~ @@ -15,6 +16,7 @@ yarn.lock reports/ package-lock.json +pnpm-lock.yaml # Test generated files coverage diff --git a/packages/grpc-tools/.gitignore b/packages/grpc-tools/.gitignore new file mode 100644 index 000000000..5be16bd8a --- /dev/null +++ b/packages/grpc-tools/.gitignore @@ -0,0 +1,7 @@ +CMakeFiles/ +cmake_install.cmake +CMakeCache.txt +Makefile +grpc_node_plugin +protoc +deps/protobuf diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt index 60dcdb675..2b54eac2b 100644 --- a/packages/grpc-tools/CMakeLists.txt +++ b/packages/grpc-tools/CMakeLists.txt @@ -1,55 +1,60 @@ -cmake_minimum_required(VERSION 3.7) -set(CMAKE_OSX_DEPLOYMENT_TARGET "11.7" CACHE STRING "Minimum OS X deployment version" FORCE) -if(COMMAND cmake_policy) - cmake_policy(SET CMP0003 NEW) -endif(COMMAND cmake_policy) +CMAKE_MINIMUM_REQUIRED(VERSION 3.7) +PROJECT("grpc-tools") + +SET(CMAKE_OSX_DEPLOYMENT_TARGET "11.7" CACHE STRING "Minimum OS X deployment version" FORCE) + +if(COMMAND CMAKE_POLICY) + CMAKE_POLICY(SET CMP0003 NEW) +ENDIF(COMMAND CMAKE_POLICY) # MSVC runtime library flags are selected by an abstraction. -if(COMMAND cmake_policy AND POLICY CMP0091) - cmake_policy(SET CMP0091 NEW) -endif() +if(COMMAND CMAKE_POLICY AND POLICY CMP0091) + CMAKE_POLICY(SET CMP0091 NEW) +ENDIF() + +SET(CMAKE_CXX_STANDARD 11) +SET(CMAKE_CXX_STANDARD_REQUIRED ON) +SET(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +SET(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf tests") +SET(protobuf_WITH_ZLIB OFF CACHE BOOL "Build protobuf with zlib.") +SET(PROTOBUF_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/protobuf) -set(protobuf_BUILD_TESTS OFF CACHE BOOL "Build protobuf tests") -set(protobuf_WITH_ZLIB OFF CACHE BOOL "Build protobuf with zlib.") -set(PROTOBUF_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/protobuf) -add_subdirectory(${PROTOBUF_ROOT_DIR}/cmake deps/protobuf) +ADD_SUBDIRECTORY(${PROTOBUF_ROOT_DIR}/cmake deps/protobuf) -set(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector") +SET(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector") -add_executable(grpc_node_plugin - src/node_generator.cc - src/node_plugin.cc +ADD_EXECUTABLE(grpc_node_plugin + src/node_generator.cc + src/node_plugin.cc ) -if (MSVC) - if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.15) - set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) - else () - foreach (flag_var - CMAKE_CXX_FLAGS - CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL - CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if (${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif (${flag_var} MATCHES "/MD") - endforeach (flag_var) - endif () -endif (MVC) - -target_include_directories(grpc_node_plugin +IF(MSVC) + IF(CMAKE_VERSION VERSION_GREATER_EQUAL 3.15) + SET(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) + ELSE() + FOREACH(flag_var + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELWITHDEBINFO + ) + IF(${flag_var} MATCHES "/MD") + STRING(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + ENDIF(${flag_var} MATCHES "/MD") + ENDFOREACH(flag_var) + ENDIF() +ENDIF(MSVC) + +TARGET_INCLUDE_DIRECTORIES(grpc_node_plugin PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src PRIVATE ${PROTOBUF_ROOT_DIR}/include ) -target_link_libraries(grpc_node_plugin +TARGET_LINK_LIBRARIES(grpc_node_plugin libprotoc libprotobuf ) diff --git a/packages/grpc-tools/build_binaries.sh b/packages/grpc-tools/build_binaries.sh index d22bbc4ff..e98743ba1 100755 --- a/packages/grpc-tools/build_binaries.sh +++ b/packages/grpc-tools/build_binaries.sh @@ -17,29 +17,33 @@ set -e uname -a -cd $(dirname $0) +cd "$(dirname "$0")" base=$(pwd) protobuf_base=$base/deps/protobuf -tools_version=$(jq '.version' < package.json | tr -d '"') +tools_version=$(jq '.version' Date: Sun, 22 Jan 2023 23:00:38 +0000 Subject: [PATCH 420/694] Fix "file" command not found --- tools/release/native/Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/release/native/Dockerfile b/tools/release/native/Dockerfile index 588e7e2f1..4d14292fd 100644 --- a/tools/release/native/Dockerfile +++ b/tools/release/native/Dockerfile @@ -6,9 +6,10 @@ RUN apt-get install -y cmake curl build-essential \ libc6-dev-arm64-cross lib32stdc++6-amd64-cross jq \ lib32stdc++6-x32-cross libstdc++6-amd64-cross \ libstdc++6-arm64-cross libstdc++6-i386-cross \ - gcc-i686-linux-gnu g++-i686-linux-gnu \ - gcc-x86-64-linux-gnu g++-x86-64-linux-gnu \ - gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + gcc-i686-linux-gnu g++-i686-linux-gnu tar file \ + gcc-x86-64-linux-gnu g++-x86-64-linux-gnu binutils \ + gcc-aarch64-linux-gnu g++-aarch64-linux-gnu make \ + gcc g++ gzip bash RUN mkdir /usr/local/nvm ENV NVM_DIR /usr/local/nvm From ba405cf35e8f96390889f383189dd4bbe014e85f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 23 Jan 2023 11:36:24 -0800 Subject: [PATCH 421/694] grpc-js: Clear deadline timer when call ends --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/resolving-call.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index d9ef213db..e9a181795 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.4", + "version": "1.8.5", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index fe29a4f7a..f1fecd1d2 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -103,6 +103,7 @@ export class ResolvingCall implements Call { if (!this.filterStack) { this.filterStack = this.filterStackFactory.createFilter(); } + clearTimeout(this.deadlineTimer); const filteredStatus = this.filterStack.receiveTrailers(status); this.trace('ended with status: code=' + filteredStatus.code + ' details="' + filteredStatus.details + '"'); this.statusWatchers.forEach(watcher => watcher(filteredStatus)); From 6d98dc5bbfe0d01025c210c80e4f88533679b34a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 25 Jan 2023 10:01:45 -0800 Subject: [PATCH 422/694] grpc-js: Hold a reference to transport in SubchannelCall --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel-call.ts | 15 +++------------ packages/grpc-js/src/transport.ts | 7 ++++++- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index e9a181795..c17d5e6ba 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.5", + "version": "1.8.6", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index a560ed4d7..969282e19 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -26,7 +26,7 @@ import { LogVerbosity } from './constants'; import { ServerSurfaceCall } from './server-call'; import { Deadline } from './deadline'; import { InterceptingListener, MessageContext, StatusObject, WriteCallback } from './call-interface'; -import { CallEventTracker } from './transport'; +import { CallEventTracker, Transport } from './transport'; const TRACER_NAME = 'subchannel_call'; @@ -105,24 +105,15 @@ export class Http2SubchannelCall implements SubchannelCall { // This is populated (non-null) if and only if the call has ended private finalStatus: StatusObject | null = null; - private disconnectListener: () => void; - private internalError: SystemError | null = null; constructor( private readonly http2Stream: http2.ClientHttp2Stream, private readonly callEventTracker: CallEventTracker, private readonly listener: SubchannelCallInterceptingListener, - private readonly peerName: string, + private readonly transport: Transport, private readonly callId: number ) { - this.disconnectListener = () => { - this.endCall({ - code: Status.UNAVAILABLE, - details: 'Connection dropped', - metadata: new Metadata(), - }); - }; http2Stream.on('response', (headers, flags) => { let headersString = ''; for (const header of Object.keys(headers)) { @@ -475,7 +466,7 @@ export class Http2SubchannelCall implements SubchannelCall { } getPeer(): string { - return this.peerName; + return this.transport.getPeerName(); } getCallNumber(): number { diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index fc9042278..a5bf59b3f 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -65,6 +65,7 @@ export interface TransportDisconnectListener { export interface Transport { getChannelzRef(): SocketRef; + getPeerName(): string; createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener, subchannelCallStatsTracker: Partial): SubchannelCall; addDisconnectListener(listener: TransportDisconnectListener): void; shutdown(): void; @@ -448,7 +449,7 @@ class Http2Transport implements Transport { } } } - call = new Http2SubchannelCall(http2Stream, eventTracker, listener, this.subchannelAddressString, getNextCallNumber()); + call = new Http2SubchannelCall(http2Stream, eventTracker, listener, this, getNextCallNumber()); this.addActiveCall(call); return call; } @@ -457,6 +458,10 @@ class Http2Transport implements Transport { return this.channelzRef; } + getPeerName() { + return this.subchannelAddressString; + } + shutdown() { this.session.close(); } From 0d177a818f83cd9de6fc1f5e4bd343de4538e35a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 25 Jan 2023 11:52:24 -0800 Subject: [PATCH 423/694] grpc-js: Fix tracking of active calls in transport --- packages/grpc-js/src/transport.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index a5bf59b3f..a9308471b 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -354,16 +354,20 @@ class Http2Transport implements Transport { private removeActiveCall(call: Http2SubchannelCall) { this.activeCalls.delete(call); - if (this.activeCalls.size === 0 && !this.keepaliveWithoutCalls) { + if (this.activeCalls.size === 0) { this.session.unref(); - this.stopKeepalivePings(); + if (!this.keepaliveWithoutCalls) { + this.stopKeepalivePings(); + } } } private addActiveCall(call: Http2SubchannelCall) { - if (this.activeCalls.size === 0 && !this.keepaliveWithoutCalls) { + if (this.activeCalls.size === 0) { this.session.ref(); - this.startKeepalivePings(); + if (!this.keepaliveWithoutCalls) { + this.startKeepalivePings(); + } } this.activeCalls.add(call); } From 3efdc7b58c0910075659982603e850cc883e019b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 25 Jan 2023 11:56:09 -0800 Subject: [PATCH 424/694] grpc-js: Bump version to 1.8.7 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index c17d5e6ba..9e1dfcba6 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.6", + "version": "1.8.7", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 05bebcd4e241959289d0c6c9f4ace5b3c432ca9c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 25 Jan 2023 09:50:49 -0800 Subject: [PATCH 425/694] grpc-js-xds: Add unit test framework --- packages/grpc-js-xds/package.json | 3 +- .../grpc-js-xds/proto/grpc/testing/echo.proto | 70 ++++ .../proto/grpc/testing/echo_messages.proto | 74 ++++ .../proto/grpc/testing/simple_messages.proto | 26 ++ .../testing/xds/v3/orca_load_report.proto | 44 +++ packages/grpc-js-xds/test/backend.ts | 105 ++++++ packages/grpc-js-xds/test/client.ts | 72 ++++ packages/grpc-js-xds/test/framework.ts | 208 +++++++++++ packages/grpc-js-xds/test/generated/echo.ts | 46 +++ .../test/generated/grpc/testing/DebugInfo.ts | 18 + .../generated/grpc/testing/EchoRequest.ts | 13 + .../generated/grpc/testing/EchoResponse.ts | 13 + .../grpc/testing/EchoTest1Service.ts | 117 ++++++ .../grpc/testing/EchoTest2Service.ts | 117 ++++++ .../generated/grpc/testing/EchoTestService.ts | 150 ++++++++ .../generated/grpc/testing/ErrorStatus.ts | 20 + .../generated/grpc/testing/NoRpcService.ts | 19 + .../generated/grpc/testing/RequestParams.ts | 75 ++++ .../generated/grpc/testing/ResponseParams.ts | 15 + .../generated/grpc/testing/SimpleRequest.ts | 8 + .../generated/grpc/testing/SimpleResponse.ts | 8 + .../generated/grpc/testing/StringValue.ts | 10 + .../grpc/testing/UnimplementedEchoService.ts | 27 ++ .../xds/data/orca/v3/OrcaLoadReport.ts | 59 +++ packages/grpc-js-xds/test/test-core.ts | 63 ++++ packages/grpc-js-xds/test/xds-server.ts | 342 ++++++++++++++++++ 26 files changed, 1721 insertions(+), 1 deletion(-) create mode 100644 packages/grpc-js-xds/proto/grpc/testing/echo.proto create mode 100644 packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto create mode 100644 packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto create mode 100644 packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto create mode 100644 packages/grpc-js-xds/test/backend.ts create mode 100644 packages/grpc-js-xds/test/client.ts create mode 100644 packages/grpc-js-xds/test/framework.ts create mode 100644 packages/grpc-js-xds/test/generated/echo.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts create mode 100644 packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts create mode 100644 packages/grpc-js-xds/test/test-core.ts create mode 100644 packages/grpc-js-xds/test/xds-server.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index c0c3200f5..bd1511e5a 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -13,7 +13,8 @@ "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", - "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-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" }, "repository": { "type": "git", diff --git a/packages/grpc-js-xds/proto/grpc/testing/echo.proto b/packages/grpc-js-xds/proto/grpc/testing/echo.proto new file mode 100644 index 000000000..7f444b43f --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/echo.proto @@ -0,0 +1,70 @@ + +// Copyright 2015 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. + +syntax = "proto3"; + +package grpc.testing; + +import "grpc/testing/echo_messages.proto"; +import "grpc/testing/simple_messages.proto"; + +service EchoTestService { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + rpc CheckDeadlineUpperBound(SimpleRequest) returns (StringValue); + rpc CheckDeadlineSet(SimpleRequest) returns (StringValue); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); + rpc UnimplementedBidi(stream EchoRequest) returns (stream EchoResponse); +} + +service EchoTest1Service { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +service EchoTest2Service { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +service UnimplementedEchoService { + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +// A service without any rpc defined to test coverage. +service NoRpcService {} diff --git a/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto b/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto new file mode 100644 index 000000000..44f22133e --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto @@ -0,0 +1,74 @@ + +// Copyright 2015 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. + +syntax = "proto3"; + +package grpc.testing; + +option cc_enable_arenas = true; + +import "grpc/testing/xds/v3/orca_load_report.proto"; + +// Message to be echoed back serialized in trailer. +message DebugInfo { + repeated string stack_entries = 1; + string detail = 2; +} + +// Error status client expects to see. +message ErrorStatus { + int32 code = 1; + string error_message = 2; + string binary_error_details = 3; +} + +message RequestParams { + bool echo_deadline = 1; + int32 client_cancel_after_us = 2; + int32 server_cancel_after_us = 3; + bool echo_metadata = 4; + bool check_auth_context = 5; + int32 response_message_length = 6; + bool echo_peer = 7; + string expected_client_identity = 8; // will force check_auth_context. + bool skip_cancelled_check = 9; + string expected_transport_security_type = 10; + DebugInfo debug_info = 11; + bool server_die = 12; // Server should not see a request with this set. + string binary_error_details = 13; + ErrorStatus expected_error = 14; + int32 server_sleep_us = 15; // sleep when invoking server for deadline tests + int32 backend_channel_idx = 16; // which backend to send request to + bool echo_metadata_initially = 17; + bool server_notify_client_when_started = 18; + xds.data.orca.v3.OrcaLoadReport backend_metrics = 19; + bool echo_host_from_authority_header = 20; +} + +message EchoRequest { + string message = 1; + RequestParams param = 2; +} + +message ResponseParams { + int64 request_deadline = 1; + string host = 2; + string peer = 3; +} + +message EchoResponse { + string message = 1; + ResponseParams param = 2; +} diff --git a/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto b/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto new file mode 100644 index 000000000..3afe236b4 --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto @@ -0,0 +1,26 @@ + +// Copyright 2018 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. + +syntax = "proto3"; + +package grpc.testing; + +message SimpleRequest {} + +message SimpleResponse {} + +message StringValue { + string message = 1; +} diff --git a/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto b/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto new file mode 100644 index 000000000..033e64ba4 --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto @@ -0,0 +1,44 @@ +// Copyright 2020 The 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. + +// Local copy of Envoy xDS proto file, used for testing only. + +syntax = "proto3"; + +package xds.data.orca.v3; + +// See section `ORCA load report format` of the design document in +// :ref:`https://github.com/envoyproxy/envoy/issues/6614`. + +message OrcaLoadReport { + // CPU utilization expressed as a fraction of available CPU resources. This + // should be derived from the latest sample or measurement. + double cpu_utilization = 1; + + // Memory utilization expressed as a fraction of available memory + // resources. This should be derived from the latest sample or measurement. + double mem_utilization = 2; + + // Total RPS being served by an endpoint. This should cover all services that an endpoint is + // responsible for. + uint64 rps = 3; + + // Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + // storage) associated with the request. + map request_cost = 4; + + // Resource utilization values. Each value is expressed as a fraction of total resources + // available, derived from the latest sample or measurement. + map utilization = 5; +} diff --git a/packages/grpc-js-xds/test/backend.ts b/packages/grpc-js-xds/test/backend.ts new file mode 100644 index 000000000..ce509c556 --- /dev/null +++ b/packages/grpc-js-xds/test/backend.ts @@ -0,0 +1,105 @@ +/* + * 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. + * + */ + +import { loadPackageDefinition, sendUnaryData, Server, ServerCredentials, ServerUnaryCall, UntypedServiceImplementation } from "@grpc/grpc-js"; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType } from "./generated/echo"; +import { EchoRequest__Output } from "./generated/grpc/testing/EchoRequest"; +import { EchoResponse } from "./generated/grpc/testing/EchoResponse"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + 'grpc/testing/echo.proto' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to build/test + __dirname + '/../../proto/' + ], + })) as unknown as ProtoGrpcType; + +export class Backend { + private server: Server; + private receivedCallCount = 0; + private callListeners: (() => void)[] = []; + private port: number | null = null; + constructor() { + this.server = new Server(); + this.server.addService(loadedProtos.grpc.testing.EchoTestService.service, this as unknown as UntypedServiceImplementation); + } + Echo(call: ServerUnaryCall, callback: sendUnaryData) { + // call.request.params is currently ignored + this.addCall(); + callback(null, {message: call.request.message}); + } + + addCall() { + this.receivedCallCount++; + this.callListeners.forEach(listener => listener()); + } + + onCall(listener: () => void) { + this.callListeners.push(listener); + } + + start(callback: (error: Error | null, port: number) => void) { + this.server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (error, port) => { + if (!error) { + this.port = port; + this.server.start(); + } + callback(error, port); + }) + } + + startAsync(): Promise { + return new Promise((resolve, reject) => { + this.start((error, port) => { + if (error) { + reject(error); + } else { + resolve(port); + } + }); + }); + } + + getPort(): number { + if (this.port === null) { + throw new Error('Port not set. Backend not yet started.'); + } + return this.port; + } + + getCallCount() { + return this.receivedCallCount; + } + + resetCallCount() { + this.receivedCallCount = 0; + } + + shutdown(callback: (error?: Error) => void) { + this.server.tryShutdown(callback); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/client.ts b/packages/grpc-js-xds/test/client.ts new file mode 100644 index 000000000..6404a3eb2 --- /dev/null +++ b/packages/grpc-js-xds/test/client.ts @@ -0,0 +1,72 @@ +/* + * 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. + * + */ + +import { credentials, loadPackageDefinition } from "@grpc/grpc-js"; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType } from "./generated/echo"; +import { EchoTestServiceClient } from "./generated/grpc/testing/EchoTestService"; +import { XdsServer } from "./xds-server"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + 'grpc/testing/echo.proto' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to build/test + __dirname + '/../../proto/' + ], + })) as unknown as ProtoGrpcType; + +const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config'; + +export class XdsTestClient { + private client: EchoTestServiceClient; + private callInterval: NodeJS.Timer; + + constructor(targetName: string, xdsServer: XdsServer) { + this.client = new loadedProtos.grpc.testing.EchoTestService(`xds:///${targetName}`, credentials.createInsecure(), {[BOOTSTRAP_CONFIG_KEY]: xdsServer.getBootstrapInfoString()}); + this.callInterval = setInterval(() => {}, 0); + clearInterval(this.callInterval); + } + + startCalls(interval: number) { + clearInterval(this.callInterval); + this.callInterval = setInterval(() => { + this.client.echo({message: 'test'}, (error, value) => { + if (error) { + throw error; + } + }); + }, interval); + } + + stopCalls() { + clearInterval(this.callInterval); + } + + close() { + this.stopCalls(); + this.client.close(); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts new file mode 100644 index 000000000..945b08a3a --- /dev/null +++ b/packages/grpc-js-xds/test/framework.ts @@ -0,0 +1,208 @@ +/* + * 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. + * + */ + +import { ClusterLoadAssignment } from "../src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { Cluster } from "../src/generated/envoy/config/cluster/v3/Cluster"; +import { Backend } from "./backend"; +import { Locality } from "../src/generated/envoy/config/core/v3/Locality"; +import { RouteConfiguration } from "../src/generated/envoy/config/route/v3/RouteConfiguration"; +import { Route } from "../src/generated/envoy/config/route/v3/Route"; +import { Listener } from "../src/generated/envoy/config/listener/v3/Listener"; +import { HttpConnectionManager } from "../src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager"; +import { AnyExtension } from "@grpc/proto-loader"; +import { HTTP_CONNECTION_MANGER_TYPE_URL } from "../src/resources"; +import { LocalityLbEndpoints } from "../src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints"; +import { LbEndpoint } from "../src/generated/envoy/config/endpoint/v3/LbEndpoint"; + +interface Endpoint { + locality: Locality; + backends: Backend[]; + weight?: number; + priority?: number; +} + +function getLbEndpoint(backend: Backend): LbEndpoint { + return { + health_status: "HEALTHY", + endpoint: { + address: { + socket_address: { + address: '::1', + port_value: backend.getPort() + } + } + } + }; +} + +function getLocalityLbEndpoints(endpoint: Endpoint): LocalityLbEndpoints { + return { + lb_endpoints: endpoint.backends.map(getLbEndpoint), + locality: endpoint.locality, + load_balancing_weight: {value: endpoint.weight ?? 1}, + priority: endpoint.priority ?? 0 + } +} + +export class FakeCluster { + constructor(private name: string, private endpoints: Endpoint[]) {} + + getEndpointConfig(): ClusterLoadAssignment { + return { + cluster_name: this.name, + endpoints: this.endpoints.map(getLocalityLbEndpoints) + }; + } + + getClusterConfig(): Cluster { + return { + name: this.name, + type: 'EDS', + eds_cluster_config: {eds_config: {ads: {}}}, + lb_policy: 'ROUND_ROBIN' + } + } + + getName() { + return this.name; + } + + startAllBackends(): Promise { + return Promise.all(this.endpoints.map(endpoint => Promise.all(endpoint.backends.map(backend => backend.startAsync())))); + } + + private haveAllBackendsReceivedTraffic(): boolean { + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + if (backend.getCallCount() < 1) { + return false; + } + } + } + return true; + } + + waitForAllBackendsToReceiveTraffic(): Promise { + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + backend.resetCallCount(); + } + } + return new Promise((resolve, reject) => { + let finishedPromise = false; + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + backend.onCall(() => { + if (finishedPromise) { + return; + } + if (this.haveAllBackendsReceivedTraffic()) { + finishedPromise = true; + resolve(); + } + }); + } + } + }); + } +} + +interface FakeRoute { + cluster?: FakeCluster; + weightedClusters?: [{cluster: FakeCluster, weight: number}]; +} + +function createRouteConfig(route: FakeRoute): Route { + if (route.cluster) { + return { + match: { + prefix: '' + }, + route: { + cluster: route.cluster.getName() + } + }; + } else { + return { + match: { + prefix: '' + }, + route: { + weighted_clusters: { + clusters: route.weightedClusters!.map(clusterWeight => ({ + name: clusterWeight.cluster.getName(), + weight: {value: clusterWeight.weight} + })) + } + } + } + } +} + +export class FakeRouteGroup { + constructor(private name: string, private routes: FakeRoute[]) {} + + getRouteConfiguration(): RouteConfiguration { + return { + name: this.name, + virtual_hosts: [{ + domains: ['*'], + routes: this.routes.map(createRouteConfig) + }] + }; + } + + getListener(): Listener { + const httpConnectionManager: HttpConnectionManager & AnyExtension = { + '@type': HTTP_CONNECTION_MANGER_TYPE_URL, + rds: { + route_config_name: this.name, + config_source: {ads: {}} + } + } + return { + name: this.name, + api_listener: { + api_listener: httpConnectionManager + } + }; + } + + startAllBackends(): Promise { + return Promise.all(this.routes.map(route => { + if (route.cluster) { + return route.cluster.startAllBackends(); + } else if (route.weightedClusters) { + return Promise.all(route.weightedClusters.map(clusterWeight => clusterWeight.cluster.startAllBackends())); + } else { + return Promise.resolve(); + } + })); + } + + waitForAllBackendsToReceiveTraffic(): Promise { + return Promise.all(this.routes.map(route => { + if (route.cluster) { + return route.cluster.waitForAllBackendsToReceiveTraffic(); + } else if (route.weightedClusters) { + return Promise.all(route.weightedClusters.map(clusterWeight => clusterWeight.cluster.waitForAllBackendsToReceiveTraffic())).then(() => {}); + } else { + return Promise.resolve(); + } + })); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/generated/echo.ts b/packages/grpc-js-xds/test/generated/echo.ts new file mode 100644 index 000000000..537a49cfa --- /dev/null +++ b/packages/grpc-js-xds/test/generated/echo.ts @@ -0,0 +1,46 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { EchoTest1ServiceClient as _grpc_testing_EchoTest1ServiceClient, EchoTest1ServiceDefinition as _grpc_testing_EchoTest1ServiceDefinition } from './grpc/testing/EchoTest1Service'; +import type { EchoTest2ServiceClient as _grpc_testing_EchoTest2ServiceClient, EchoTest2ServiceDefinition as _grpc_testing_EchoTest2ServiceDefinition } from './grpc/testing/EchoTest2Service'; +import type { EchoTestServiceClient as _grpc_testing_EchoTestServiceClient, EchoTestServiceDefinition as _grpc_testing_EchoTestServiceDefinition } from './grpc/testing/EchoTestService'; +import type { NoRpcServiceClient as _grpc_testing_NoRpcServiceClient, NoRpcServiceDefinition as _grpc_testing_NoRpcServiceDefinition } from './grpc/testing/NoRpcService'; +import type { UnimplementedEchoServiceClient as _grpc_testing_UnimplementedEchoServiceClient, UnimplementedEchoServiceDefinition as _grpc_testing_UnimplementedEchoServiceDefinition } from './grpc/testing/UnimplementedEchoService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + grpc: { + testing: { + DebugInfo: MessageTypeDefinition + EchoRequest: MessageTypeDefinition + EchoResponse: MessageTypeDefinition + EchoTest1Service: SubtypeConstructor & { service: _grpc_testing_EchoTest1ServiceDefinition } + EchoTest2Service: SubtypeConstructor & { service: _grpc_testing_EchoTest2ServiceDefinition } + EchoTestService: SubtypeConstructor & { service: _grpc_testing_EchoTestServiceDefinition } + ErrorStatus: MessageTypeDefinition + /** + * A service without any rpc defined to test coverage. + */ + NoRpcService: SubtypeConstructor & { service: _grpc_testing_NoRpcServiceDefinition } + RequestParams: MessageTypeDefinition + ResponseParams: MessageTypeDefinition + SimpleRequest: MessageTypeDefinition + SimpleResponse: MessageTypeDefinition + StringValue: MessageTypeDefinition + UnimplementedEchoService: SubtypeConstructor & { service: _grpc_testing_UnimplementedEchoServiceDefinition } + } + } + xds: { + data: { + orca: { + v3: { + OrcaLoadReport: MessageTypeDefinition + } + } + } + } +} + diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts b/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts new file mode 100644 index 000000000..123188fe3 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts @@ -0,0 +1,18 @@ +// Original file: proto/grpc/testing/echo_messages.proto + + +/** + * Message to be echoed back serialized in trailer. + */ +export interface DebugInfo { + 'stack_entries'?: (string)[]; + 'detail'?: (string); +} + +/** + * Message to be echoed back serialized in trailer. + */ +export interface DebugInfo__Output { + 'stack_entries': (string)[]; + 'detail': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts new file mode 100644 index 000000000..cadf04f7a --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts @@ -0,0 +1,13 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { RequestParams as _grpc_testing_RequestParams, RequestParams__Output as _grpc_testing_RequestParams__Output } from '../../grpc/testing/RequestParams'; + +export interface EchoRequest { + 'message'?: (string); + 'param'?: (_grpc_testing_RequestParams | null); +} + +export interface EchoRequest__Output { + 'message': (string); + 'param': (_grpc_testing_RequestParams__Output | null); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts new file mode 100644 index 000000000..d54beaf4f --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts @@ -0,0 +1,13 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { ResponseParams as _grpc_testing_ResponseParams, ResponseParams__Output as _grpc_testing_ResponseParams__Output } from '../../grpc/testing/ResponseParams'; + +export interface EchoResponse { + 'message'?: (string); + 'param'?: (_grpc_testing_ResponseParams | null); +} + +export interface EchoResponse__Output { + 'message': (string); + 'param': (_grpc_testing_ResponseParams__Output | null); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts new file mode 100644 index 000000000..a2b1947f6 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts @@ -0,0 +1,117 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; + +export interface EchoTest1ServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface EchoTest1ServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTest1ServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts new file mode 100644 index 000000000..033e70143 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts @@ -0,0 +1,117 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; + +export interface EchoTest2ServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface EchoTest2ServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTest2ServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts new file mode 100644 index 000000000..d1fa2d075 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts @@ -0,0 +1,150 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; +import type { StringValue as _grpc_testing_StringValue, StringValue__Output as _grpc_testing_StringValue__Output } from '../../grpc/testing/StringValue'; + +export interface EchoTestServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + UnimplementedBidi(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + UnimplementedBidi(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + unimplementedBidi(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + unimplementedBidi(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + +} + +export interface EchoTestServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + CheckDeadlineSet: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue>; + + CheckDeadlineUpperBound: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + UnimplementedBidi: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTestServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + CheckDeadlineSet: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_StringValue, _grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue__Output> + CheckDeadlineUpperBound: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_StringValue, _grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + UnimplementedBidi: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts b/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts new file mode 100644 index 000000000..42ff36d9a --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts @@ -0,0 +1,20 @@ +// Original file: proto/grpc/testing/echo_messages.proto + + +/** + * Error status client expects to see. + */ +export interface ErrorStatus { + 'code'?: (number); + 'error_message'?: (string); + 'binary_error_details'?: (string); +} + +/** + * Error status client expects to see. + */ +export interface ErrorStatus__Output { + 'code': (number); + 'error_message': (string); + 'binary_error_details': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts new file mode 100644 index 000000000..7427c8097 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts @@ -0,0 +1,19 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' + +/** + * A service without any rpc defined to test coverage. + */ +export interface NoRpcServiceClient extends grpc.Client { +} + +/** + * A service without any rpc defined to test coverage. + */ +export interface NoRpcServiceHandlers extends grpc.UntypedServiceImplementation { +} + +export interface NoRpcServiceDefinition extends grpc.ServiceDefinition { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts b/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts new file mode 100644 index 000000000..e8c5ef1d1 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts @@ -0,0 +1,75 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { DebugInfo as _grpc_testing_DebugInfo, DebugInfo__Output as _grpc_testing_DebugInfo__Output } from '../../grpc/testing/DebugInfo'; +import type { ErrorStatus as _grpc_testing_ErrorStatus, ErrorStatus__Output as _grpc_testing_ErrorStatus__Output } from '../../grpc/testing/ErrorStatus'; +import type { OrcaLoadReport as _xds_data_orca_v3_OrcaLoadReport, OrcaLoadReport__Output as _xds_data_orca_v3_OrcaLoadReport__Output } from '../../xds/data/orca/v3/OrcaLoadReport'; + +export interface RequestParams { + 'echo_deadline'?: (boolean); + 'client_cancel_after_us'?: (number); + 'server_cancel_after_us'?: (number); + 'echo_metadata'?: (boolean); + 'check_auth_context'?: (boolean); + 'response_message_length'?: (number); + 'echo_peer'?: (boolean); + /** + * will force check_auth_context. + */ + 'expected_client_identity'?: (string); + 'skip_cancelled_check'?: (boolean); + 'expected_transport_security_type'?: (string); + 'debug_info'?: (_grpc_testing_DebugInfo | null); + /** + * Server should not see a request with this set. + */ + 'server_die'?: (boolean); + 'binary_error_details'?: (string); + 'expected_error'?: (_grpc_testing_ErrorStatus | null); + /** + * sleep when invoking server for deadline tests + */ + 'server_sleep_us'?: (number); + /** + * which backend to send request to + */ + 'backend_channel_idx'?: (number); + 'echo_metadata_initially'?: (boolean); + 'server_notify_client_when_started'?: (boolean); + 'backend_metrics'?: (_xds_data_orca_v3_OrcaLoadReport | null); + 'echo_host_from_authority_header'?: (boolean); +} + +export interface RequestParams__Output { + 'echo_deadline': (boolean); + 'client_cancel_after_us': (number); + 'server_cancel_after_us': (number); + 'echo_metadata': (boolean); + 'check_auth_context': (boolean); + 'response_message_length': (number); + 'echo_peer': (boolean); + /** + * will force check_auth_context. + */ + 'expected_client_identity': (string); + 'skip_cancelled_check': (boolean); + 'expected_transport_security_type': (string); + 'debug_info': (_grpc_testing_DebugInfo__Output | null); + /** + * Server should not see a request with this set. + */ + 'server_die': (boolean); + 'binary_error_details': (string); + 'expected_error': (_grpc_testing_ErrorStatus__Output | null); + /** + * sleep when invoking server for deadline tests + */ + 'server_sleep_us': (number); + /** + * which backend to send request to + */ + 'backend_channel_idx': (number); + 'echo_metadata_initially': (boolean); + 'server_notify_client_when_started': (boolean); + 'backend_metrics': (_xds_data_orca_v3_OrcaLoadReport__Output | null); + 'echo_host_from_authority_header': (boolean); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts b/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts new file mode 100644 index 000000000..588e463c2 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts @@ -0,0 +1,15 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface ResponseParams { + 'request_deadline'?: (number | string | Long); + 'host'?: (string); + 'peer'?: (string); +} + +export interface ResponseParams__Output { + 'request_deadline': (string); + 'host': (string); + 'peer': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts new file mode 100644 index 000000000..292a2020c --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts @@ -0,0 +1,8 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface SimpleRequest { +} + +export interface SimpleRequest__Output { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts new file mode 100644 index 000000000..3e8735e5e --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts @@ -0,0 +1,8 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface SimpleResponse { +} + +export interface SimpleResponse__Output { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts b/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts new file mode 100644 index 000000000..4a779ae2b --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts @@ -0,0 +1,10 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface StringValue { + 'message'?: (string); +} + +export interface StringValue__Output { + 'message': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts new file mode 100644 index 000000000..48128976e --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts @@ -0,0 +1,27 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; + +export interface UnimplementedEchoServiceClient extends grpc.Client { + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface UnimplementedEchoServiceHandlers extends grpc.UntypedServiceImplementation { + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface UnimplementedEchoServiceDefinition extends grpc.ServiceDefinition { + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts b/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts new file mode 100644 index 000000000..d66c42713 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts @@ -0,0 +1,59 @@ +// Original file: proto/grpc/testing/xds/v3/orca_load_report.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface OrcaLoadReport { + /** + * CPU utilization expressed as a fraction of available CPU resources. This + * should be derived from the latest sample or measurement. + */ + 'cpu_utilization'?: (number | string); + /** + * Memory utilization expressed as a fraction of available memory + * resources. This should be derived from the latest sample or measurement. + */ + 'mem_utilization'?: (number | string); + /** + * Total RPS being served by an endpoint. This should cover all services that an endpoint is + * responsible for. + */ + 'rps'?: (number | string | Long); + /** + * Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + * storage) associated with the request. + */ + 'request_cost'?: ({[key: string]: number | string}); + /** + * Resource utilization values. Each value is expressed as a fraction of total resources + * available, derived from the latest sample or measurement. + */ + 'utilization'?: ({[key: string]: number | string}); +} + +export interface OrcaLoadReport__Output { + /** + * CPU utilization expressed as a fraction of available CPU resources. This + * should be derived from the latest sample or measurement. + */ + 'cpu_utilization': (number | string); + /** + * Memory utilization expressed as a fraction of available memory + * resources. This should be derived from the latest sample or measurement. + */ + 'mem_utilization': (number | string); + /** + * Total RPS being served by an endpoint. This should cover all services that an endpoint is + * responsible for. + */ + 'rps': (string); + /** + * Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + * storage) associated with the request. + */ + 'request_cost': ({[key: string]: number | string}); + /** + * Resource utilization values. Each value is expressed as a fraction of total resources + * available, derived from the latest sample or measurement. + */ + 'utilization': ({[key: string]: number | string}); +} diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts new file mode 100644 index 000000000..d0d1b6031 --- /dev/null +++ b/packages/grpc-js-xds/test/test-core.ts @@ -0,0 +1,63 @@ +/* + * 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. + * + */ + +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; + +import { register } from "../src"; +import assert = require("assert"); + +register(); + +describe('core xDS functionality', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }) + it('should route requests to the single backend', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('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 = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }, reason => done(reason)); + }, reason => done(reason)); + }); +}); \ No newline at end of file diff --git a/packages/grpc-js-xds/test/xds-server.ts b/packages/grpc-js-xds/test/xds-server.ts new file mode 100644 index 000000000..b82a3a673 --- /dev/null +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -0,0 +1,342 @@ +/* + * 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. + * + */ + +import { ServerDuplexStream, Server, UntypedServiceImplementation, ServerCredentials, loadPackageDefinition } from "@grpc/grpc-js"; +import { AnyExtension, loadSync } from "@grpc/proto-loader"; +import { EventEmitter } from "stream"; +import { Cluster } from "../src/generated/envoy/config/cluster/v3/Cluster"; +import { ClusterLoadAssignment } from "../src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { Listener } from "../src/generated/envoy/config/listener/v3/Listener"; +import { RouteConfiguration } from "../src/generated/envoy/config/route/v3/RouteConfiguration"; +import { AggregatedDiscoveryServiceHandlers } from "../src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService"; +import { DiscoveryRequest__Output } from "../src/generated/envoy/service/discovery/v3/DiscoveryRequest"; +import { DiscoveryResponse } from "../src/generated/envoy/service/discovery/v3/DiscoveryResponse"; +import { Any } from "../src/generated/google/protobuf/Any"; +import { LDS_TYPE_URL, RDS_TYPE_URL, CDS_TYPE_URL, EDS_TYPE_URL, LdsTypeUrl, RdsTypeUrl, CdsTypeUrl, EdsTypeUrl, AdsTypeUrl } from "../src/resources" +import * as adsTypes from '../src/generated/ads'; +import * as lrsTypes from '../src/generated/lrs'; +import { LoadStatsRequest__Output } from "../src/generated/envoy/service/load_stats/v3/LoadStatsRequest"; +import { LoadStatsResponse } from "../src/generated/envoy/service/load_stats/v3/LoadStatsResponse"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + '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' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/googleapis/', + __dirname + '/../../deps/protoc-gen-validate/', + ], + })) as unknown as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType; + +type AdsInputType = T extends EdsTypeUrl + ? ClusterLoadAssignment + : T extends CdsTypeUrl + ? Cluster + : T extends RdsTypeUrl + ? RouteConfiguration + : Listener; + +const ADS_TYPE_URLS = new Set([LDS_TYPE_URL, RDS_TYPE_URL, CDS_TYPE_URL, EDS_TYPE_URL]); + +interface ResponseState { + state: 'ACKED' | 'NACKED'; + errorMessage?: string; +} + +interface ResponseListener { + (typeUrl: AdsTypeUrl, responseState: ResponseState): void; +} + +type ResourceAny = AdsInputType & {'@type': T}; + +interface ResourceState { + resource?: ResourceAny; + resourceTypeVersion: number; + subscriptions: Set; +} + +interface ResourceTypeState { + resourceTypeVersion: number; + /** + * Key type is type URL + */ + resourceNameMap: Map>; +} + +interface ResourceMap { + [EDS_TYPE_URL]: ResourceTypeState; + [CDS_TYPE_URL]: ResourceTypeState; + [RDS_TYPE_URL]: ResourceTypeState; + [LDS_TYPE_URL]: ResourceTypeState; +} + +function isAdsTypeUrl(value: string): value is AdsTypeUrl { + return ADS_TYPE_URLS.has(value); +} + +export class XdsServer { + private resourceMap: ResourceMap = { + [EDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [CDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [RDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [LDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + }; + private responseListeners = new Set(); + private resourceTypesToIgnore = new Set(); + private clients = new Map>(); + private server: Server | null = null; + private port: number | null = null; + + addResponseListener(listener: ResponseListener) { + this.responseListeners.add(listener); + } + + removeResponseListener(listener: ResponseListener) { + this.responseListeners.delete(listener); + } + + setResource(resource: ResourceAny, name: string) { + const resourceTypeState = this.resourceMap[resource["@type"]] as ResourceTypeState; + resourceTypeState.resourceTypeVersion += 1; + let resourceState: ResourceState | undefined = resourceTypeState.resourceNameMap.get(name); + if (!resourceState) { + resourceState = { + resourceTypeVersion: 0, + subscriptions: new Set() + }; + resourceTypeState.resourceNameMap.set(name, resourceState); + } + resourceState.resourceTypeVersion = resourceTypeState.resourceTypeVersion; + resourceState.resource = resource; + this.sendResourceUpdates(resource['@type'], resourceState.subscriptions, new Set([name])); + } + + setLdsResource(resource: Listener) { + this.setResource({...resource, '@type': LDS_TYPE_URL}, resource.name!); + } + + setRdsResource(resource: RouteConfiguration) { + this.setResource({...resource, '@type': RDS_TYPE_URL}, resource.name!); + } + + setCdsResource(resource: Cluster) { + this.setResource({...resource, '@type': CDS_TYPE_URL}, resource.name!); + } + + setEdsResource(resource: ClusterLoadAssignment) { + this.setResource({...resource, '@type': EDS_TYPE_URL}, resource.cluster_name!); + } + + unsetResource(typeUrl: T, name: string) { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + resourceTypeState.resourceTypeVersion += 1; + let resourceState: ResourceState | undefined = resourceTypeState.resourceNameMap.get(name); + if (resourceState) { + resourceState.resourceTypeVersion = resourceTypeState.resourceTypeVersion; + delete resourceState.resource; + this.sendResourceUpdates(typeUrl, resourceState.subscriptions, new Set([name])); + } + } + + ignoreResourceType(typeUrl: AdsTypeUrl) { + this.resourceTypesToIgnore.add(typeUrl); + } + + private sendResourceUpdates(typeUrl: T, clients: Set, includeResources: Set) { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + const clientResources = new Map(); + for (const [resourceName, resourceState] of resourceTypeState.resourceNameMap) { + /* For RDS and EDS, only send updates for the listed updated resources. + * Otherwise include all resources. */ + if ((typeUrl === RDS_TYPE_URL || typeUrl === EDS_TYPE_URL) && !includeResources.has(resourceName)) { + continue; + } + if (!resourceState.resource) { + continue; + } + for (const clientName of clients) { + if (!resourceState.subscriptions.has(clientName)) { + continue; + } + let resourcesList = clientResources.get(clientName); + if (!resourcesList) { + resourcesList = []; + clientResources.set(clientName, resourcesList); + } + resourcesList.push(resourceState.resource); + } + } + for (const [clientName, resourceList] of clientResources) { + this.clients.get(clientName)?.write({ + resources: resourceList, + version_info: resourceTypeState.resourceTypeVersion.toString(), + nonce: resourceTypeState.resourceTypeVersion.toString(), + type_url: typeUrl + }); + } + } + + private updateResponseListeners(typeUrl: AdsTypeUrl, responseState: ResponseState) { + for (const listener of this.responseListeners) { + listener(typeUrl, responseState); + } + } + + private maybeSubscribe(typeUrl: T, client: string, resourceName: string): boolean { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + let resourceState = resourceTypeState.resourceNameMap.get(resourceName); + if (!resourceState) { + resourceState = { + resourceTypeVersion: 0, + subscriptions: new Set() + }; + resourceTypeState.resourceNameMap.set(resourceName, resourceState); + } + const newlySubscribed = !resourceState.subscriptions.has(client); + resourceState.subscriptions.add(client); + return newlySubscribed; + } + + private handleUnsubscriptions(typeUrl: AdsTypeUrl, client: string, requestedResourceNames?: Set) { + const resourceTypeState = this.resourceMap[typeUrl]; + for (const [resourceName, resourceState] of resourceTypeState.resourceNameMap) { + if (!requestedResourceNames || !requestedResourceNames.has(resourceName)) { + resourceState.subscriptions.delete(client); + if (!resourceState.resource && resourceState.subscriptions.size === 0) { + resourceTypeState.resourceNameMap.delete(resourceName) + } + } + } + } + + private handleRequest(clientName: string, request: DiscoveryRequest__Output) { + if (!isAdsTypeUrl(request.type_url)) { + console.error(`Received ADS request with unsupported type_url ${request.type_url}`); + return; + } + const clientResourceVersion = request.version_info === '' ? 0 : Number.parseInt(request.version_info); + if (request.error_detail) { + this.updateResponseListeners(request.type_url, {state: 'NACKED', errorMessage: request.error_detail.message}); + } else { + this.updateResponseListeners(request.type_url, {state: 'ACKED'}); + } + const requestedResourceNames = new Set(request.resource_names); + const resourceTypeState = this.resourceMap[request.type_url]; + const updatedResources = new Set(); + for (const resourceName of requestedResourceNames) { + if (this.maybeSubscribe(request.type_url, clientName, resourceName) || resourceTypeState.resourceNameMap.get(resourceName)!.resourceTypeVersion > clientResourceVersion) { + updatedResources.add(resourceName); + } + } + this.handleUnsubscriptions(request.type_url, clientName, requestedResourceNames); + if (updatedResources.size > 0) { + this.sendResourceUpdates(request.type_url, new Set([clientName]), updatedResources); + } + } + + StreamAggregatedResources(call: ServerDuplexStream) { + const clientName = call.getPeer(); + this.clients.set(clientName, call); + call.on('data', (request: DiscoveryRequest__Output) => { + this.handleRequest(clientName, request); + }); + call.on('end', () => { + this.clients.delete(clientName); + for (const typeUrl of ADS_TYPE_URLS) { + this.handleUnsubscriptions(typeUrl as AdsTypeUrl, clientName); + } + call.end(); + }); + } + + StreamLoadStats(call: ServerDuplexStream) { + const statsResponse = {load_reporting_interval: {seconds: 30}}; + call.write(statsResponse); + call.on('data', (request: LoadStatsRequest__Output) => { + call.write(statsResponse); + }); + call.on('end', () => { + call.end(); + }); + } + + startServer(callback: (error: Error | null, port: number) => void) { + if (this.server) { + return; + } + const server = new Server(); + server.addService(loadedProtos.envoy.service.discovery.v3.AggregatedDiscoveryService.service, this as unknown as UntypedServiceImplementation); + server.addService(loadedProtos.envoy.service.load_stats.v3.LoadReportingService.service, this as unknown as UntypedServiceImplementation); + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (error, port) => { + if (!error) { + this.server = server; + this.port = port; + server.start(); + } + callback(error, port); + }); + } + + shutdownServer() { + this.server?.forceShutdown(); + } + + getBootstrapInfoString(): string { + if (this.port === null) { + throw new Error('Bootstrap info unavailable; server not started'); + } + const bootstrapInfo = { + xds_servers: [{ + server_uri: `localhost:${this.port}`, + channel_creds: [{type: 'insecure'}] + }], + node: { + id: 'test', + locality: {} + } + } + return JSON.stringify(bootstrapInfo); + } +} \ No newline at end of file From 5732ff9e821ff609df88c327dc48329d794ace5a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 27 Jan 2023 13:39:44 -0800 Subject: [PATCH 426/694] grpc-js-xds: Allow tests to set bootstrap info in channel args --- packages/grpc-js-xds/src/load-balancer-cds.ts | 8 +++--- packages/grpc-js-xds/src/load-balancer-eds.ts | 10 ++++--- packages/grpc-js-xds/src/load-balancer-lrs.ts | 2 +- packages/grpc-js-xds/src/resolver-xds.ts | 26 ++++++++++++++----- packages/grpc-js-xds/src/xds-bootstrap.ts | 6 ++--- packages/grpc-js-xds/src/xds-client.ts | 14 +++++++--- .../src/xds-stream-state/eds-state.ts | 8 ++++++ 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 4d47b2546..243a1e967 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -125,6 +125,7 @@ export class CdsLoadBalancer implements LoadBalancer { private latestConfig: CdsLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown } = {}; + private xdsClient: XdsClient | null = null; constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper); @@ -188,6 +189,7 @@ export class CdsLoadBalancer implements LoadBalancer { } trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); this.latestAttributes = attributes; + this.xdsClient = attributes.xdsClient as XdsClient; /* If the cluster is changing, disable the old watcher before adding the new * one */ @@ -196,7 +198,7 @@ export class CdsLoadBalancer implements LoadBalancer { this.latestConfig?.getCluster() !== lbConfig.getCluster() ) { trace('Removing old cluster watcher for cluster name ' + this.latestConfig!.getCluster()); - getSingletonXdsClient().removeClusterWatcher( + this.xdsClient.removeClusterWatcher( this.latestConfig!.getCluster(), this.watcher ); @@ -212,7 +214,7 @@ export class CdsLoadBalancer implements LoadBalancer { if (!this.isWatcherActive) { trace('Adding new cluster watcher for cluster name ' + lbConfig.getCluster()); - getSingletonXdsClient().addClusterWatcher(lbConfig.getCluster(), this.watcher); + this.xdsClient.addClusterWatcher(lbConfig.getCluster(), this.watcher); this.isWatcherActive = true; } } @@ -226,7 +228,7 @@ export class CdsLoadBalancer implements LoadBalancer { trace('Destroying load balancer with cluster name ' + this.latestConfig?.getCluster()); this.childBalancer.destroy(); if (this.isWatcherActive) { - getSingletonXdsClient().removeClusterWatcher( + this.xdsClient?.removeClusterWatcher( this.latestConfig!.getCluster(), this.watcher ); diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index b0bd3f030..03a4078ac 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -167,6 +167,7 @@ export class EdsLoadBalancer implements LoadBalancer { private lastestConfig: EdsLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown } = {}; + private xdsClient: XdsClient | null = null; private latestEdsUpdate: ClusterLoadAssignment__Output | null = null; /** @@ -488,13 +489,14 @@ export class EdsLoadBalancer implements LoadBalancer { trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); this.lastestConfig = lbConfig; this.latestAttributes = attributes; + this.xdsClient = attributes.xdsClient as XdsClient; const newEdsServiceName = lbConfig.getEdsServiceName() ?? lbConfig.getCluster(); /* If the name is changing, disable the old watcher before adding the new * one */ if (this.isWatcherActive && this.edsServiceName !== newEdsServiceName) { trace('Removing old endpoint watcher for edsServiceName ' + this.edsServiceName) - getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName!, this.watcher); + this.xdsClient.removeEndpointWatcher(this.edsServiceName!, this.watcher); /* Setting isWatcherActive to false here lets us have one code path for * calling addEndpointWatcher */ this.isWatcherActive = false; @@ -507,12 +509,12 @@ export class EdsLoadBalancer implements LoadBalancer { if (!this.isWatcherActive) { trace('Adding new endpoint watcher for edsServiceName ' + this.edsServiceName); - getSingletonXdsClient().addEndpointWatcher(this.edsServiceName, this.watcher); + this.xdsClient.addEndpointWatcher(this.edsServiceName, this.watcher); this.isWatcherActive = true; } if (lbConfig.getLrsLoadReportingServerName()) { - this.clusterDropStats = getSingletonXdsClient().addClusterDropStats( + this.clusterDropStats = this.xdsClient.addClusterDropStats( lbConfig.getLrsLoadReportingServerName()!, lbConfig.getCluster(), lbConfig.getEdsServiceName() ?? '' @@ -533,7 +535,7 @@ export class EdsLoadBalancer implements LoadBalancer { destroy(): void { trace('Destroying load balancer with edsServiceName ' + this.edsServiceName); if (this.edsServiceName) { - getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName, this.watcher); + this.xdsClient?.removeEndpointWatcher(this.edsServiceName, this.watcher); } this.childBalancer.destroy(); } diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 745b21c5c..9610ea834 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -169,7 +169,7 @@ export class LrsLoadBalancer implements LoadBalancer { if (!(lbConfig instanceof LrsLoadBalancingConfig)) { return; } - this.localityStatsReporter = getSingletonXdsClient().addClusterLocalityStats( + this.localityStatsReporter = (attributes.xdsClient as XdsClient).addClusterLocalityStats( lbConfig.getLrsLoadReportingServerName(), lbConfig.getClusterName(), lbConfig.getEdsServiceName(), diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 401465be6..9879a2c6e 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -48,6 +48,7 @@ import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment' import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; import RetryPolicy = experimental.RetryPolicy; +import { validateBootstrapConfig } from './xds-bootstrap'; const TRACER_NAME = 'xds_resolver'; @@ -210,6 +211,8 @@ function getDefaultRetryMaxInterval(baseInterval: string): string { return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`; } +const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config'; + const RETRY_CODES: {[key: string]: status} = { 'cancelled': status.CANCELLED, 'deadline-exceeded': status.DEADLINE_EXCEEDED, @@ -238,11 +241,20 @@ class XdsResolver implements Resolver { private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = []; + private xdsClient: XdsClient; + constructor( private target: GrpcUri, private listener: ResolverListener, private channelOptions: ChannelOptions ) { + if (channelOptions[BOOTSTRAP_CONFIG_KEY]) { + const parsedConfig = JSON.parse(channelOptions[BOOTSTRAP_CONFIG_KEY]); + const validatedConfig = validateBootstrapConfig(parsedConfig); + this.xdsClient = new XdsClient(validatedConfig); + } else { + this.xdsClient = getSingletonXdsClient(); + } this.ldsWatcher = { onValidUpdate: (update: Listener__Output) => { const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, update.api_listener!.api_listener!.value); @@ -267,16 +279,16 @@ class XdsResolver implements Resolver { const routeConfigName = httpConnectionManager.rds!.route_config_name; if (this.latestRouteConfigName !== routeConfigName) { if (this.latestRouteConfigName !== null) { - getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } - getSingletonXdsClient().addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher); + this.xdsClient.addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher); this.latestRouteConfigName = routeConfigName; } break; } case 'route_config': if (this.latestRouteConfigName) { - getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } this.handleRouteConfig(httpConnectionManager.route_config!); break; @@ -546,7 +558,7 @@ class XdsResolver implements Resolver { methodConfig: [], loadBalancingConfig: [lbPolicyConfig] } - this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {}); + this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {xdsClient: this.xdsClient}); } private reportResolutionError(reason: string) { @@ -563,15 +575,15 @@ class XdsResolver implements Resolver { // Wait until updateResolution is called once to start the xDS requests if (!this.isLdsWatcherActive) { trace('Starting resolution for target ' + uriToString(this.target)); - getSingletonXdsClient().addListenerWatcher(this.target.path, this.ldsWatcher); + this.xdsClient.addListenerWatcher(this.target.path, this.ldsWatcher); this.isLdsWatcherActive = true; } } destroy() { - getSingletonXdsClient().removeListenerWatcher(this.target.path, this.ldsWatcher); + this.xdsClient.removeListenerWatcher(this.target.path, this.ldsWatcher); if (this.latestRouteConfigName) { - getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } } diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 876b6d958..72a0ca375 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -231,7 +231,7 @@ function validateNode(obj: any): Node { return result; } -function validateBootstrapFile(obj: any): BootstrapInfo { +export function validateBootstrapConfig(obj: any): BootstrapInfo { return { xdsServers: obj.xds_servers.map(validateXdsServerConfig), node: validateNode(obj.node), @@ -265,7 +265,7 @@ export async function loadBootstrapInfo(): Promise { } try { const parsedFile = JSON.parse(data); - resolve(validateBootstrapFile(parsedFile)); + resolve(validateBootstrapConfig(parsedFile)); } catch (e) { reject( new Error( @@ -290,7 +290,7 @@ export async function loadBootstrapInfo(): Promise { if (bootstrapConfig) { try { const parsedConfig = JSON.parse(bootstrapConfig); - const loadedBootstrapInfoValue = validateBootstrapFile(parsedConfig); + const loadedBootstrapInfoValue = validateBootstrapConfig(parsedConfig); loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue); } catch (e) { throw new Error( diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 2dfa41236..a2d33d1a5 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -21,7 +21,7 @@ import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel, connectivityState } from '@grpc/grpc-js'; import * as adsTypes from './generated/ads'; import * as lrsTypes from './generated/lrs'; -import { loadBootstrapInfo } from './xds-bootstrap'; +import { BootstrapInfo, loadBootstrapInfo } from './xds-bootstrap'; import { Node } from './generated/envoy/config/core/v3/Node'; import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; import { DiscoveryRequest } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; @@ -276,7 +276,7 @@ export class XdsClient { private adsBackoff: BackoffTimeout; private lrsBackoff: BackoffTimeout; - constructor() { + constructor(bootstrapInfoOverride?: BootstrapInfo) { const edsState = new EdsState(() => { this.updateNames('eds'); }); @@ -310,7 +310,15 @@ export class XdsClient { }); this.lrsBackoff.unref(); - Promise.all([loadBootstrapInfo(), loadAdsProtos()]).then( + async function getBootstrapInfo(): Promise { + if (bootstrapInfoOverride) { + return bootstrapInfoOverride; + } else { + return loadBootstrapInfo(); + } + } + + Promise.all([getBootstrapInfo(), loadAdsProtos()]).then( ([bootstrapInfo, protoDefinitions]) => { if (this.hasShutdown) { return; diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index cec6a4764..b043ebbc0 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -61,10 +61,12 @@ export class EdsState extends BaseXdsStreamState const priorityTotalWeights: Map = new Map(); for (const endpoint of message.endpoints) { if (!endpoint.locality) { + trace('EDS validation: endpoint locality unset'); return false; } for (const {locality, priority} of seenLocalities) { if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) { + trace('EDS validation: endpoint locality duplicated: ' + JSON.stringify(locality) + ', priority=' + priority); return false; } } @@ -72,16 +74,20 @@ export class EdsState extends BaseXdsStreamState for (const lb of endpoint.lb_endpoints) { const socketAddress = lb.endpoint?.address?.socket_address; if (!socketAddress) { + trace('EDS validation: endpoint socket_address not set'); return false; } if (socketAddress.port_specifier !== 'port_value') { + trace('EDS validation: socket_address.port_specifier !== "port_value"'); return false; } if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) { + trace('EDS validation: address not a valid IPv4 or IPv6 address: ' + socketAddress.address); return false; } for (const address of seenAddresses) { if (addressesEqual(socketAddress, address)) { + trace('EDS validation: duplicate address seen: ' + address); return false; } } @@ -91,11 +97,13 @@ export class EdsState extends BaseXdsStreamState } for (const totalWeight of priorityTotalWeights.values()) { if (totalWeight > UINT32_MAX) { + trace('EDS validation: total weight > UINT32_MAX') return false; } } for (const priority of priorityTotalWeights.keys()) { if (priority > 0 && !priorityTotalWeights.has(priority - 1)) { + trace('EDS validation: priorities not contiguous'); return false; } } From 71bfcd2afcc3472180843a4d68ce73d9ec51239e Mon Sep 17 00:00:00 2001 From: Jacob Sapoznikow Date: Mon, 30 Jan 2023 22:01:57 +0000 Subject: [PATCH 427/694] Fix static linking and dockerfile --- packages/grpc-tools/CMakeLists.txt | 8 ++++---- tools/release/native/Dockerfile | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt index 2b54eac2b..4cb9e14ae 100644 --- a/packages/grpc-tools/CMakeLists.txt +++ b/packages/grpc-tools/CMakeLists.txt @@ -3,12 +3,12 @@ PROJECT("grpc-tools") SET(CMAKE_OSX_DEPLOYMENT_TARGET "11.7" CACHE STRING "Minimum OS X deployment version" FORCE) -if(COMMAND CMAKE_POLICY) +IF(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0003 NEW) ENDIF(COMMAND CMAKE_POLICY) # MSVC runtime library flags are selected by an abstraction. -if(COMMAND CMAKE_POLICY AND POLICY CMP0091) +IF(COMMAND CMAKE_POLICY AND POLICY CMP0091) CMAKE_POLICY(SET CMP0091 NEW) ENDIF() @@ -22,8 +22,8 @@ SET(PROTOBUF_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/protobuf) ADD_SUBDIRECTORY(${PROTOBUF_ROOT_DIR}/cmake deps/protobuf) -SET(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++") -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector") +SET(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++ -static -Wl,-Bstatic") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector -static -Wl,-Bstatic") ADD_EXECUTABLE(grpc_node_plugin src/node_generator.cc diff --git a/tools/release/native/Dockerfile b/tools/release/native/Dockerfile index 4d14292fd..18198b8f3 100644 --- a/tools/release/native/Dockerfile +++ b/tools/release/native/Dockerfile @@ -1,3 +1,8 @@ +# NOTE: We don't have to worry about glibc versions +# because we use static linking during the +# compile step. +# (See packages/grpc-tools/CMakeLists.txt#L25) + FROM ubuntu:22.04 RUN apt-get update @@ -9,7 +14,8 @@ RUN apt-get install -y cmake curl build-essential \ gcc-i686-linux-gnu g++-i686-linux-gnu tar file \ gcc-x86-64-linux-gnu g++-x86-64-linux-gnu binutils \ gcc-aarch64-linux-gnu g++-aarch64-linux-gnu make \ - gcc g++ gzip bash + gcc g++ gzip bash libc6-amd64-i386-cross \ + libc6-dev-amd64-i386-cross RUN mkdir /usr/local/nvm ENV NVM_DIR /usr/local/nvm From 4b9e4019c3acbadef8a5e64d8083ea7e65aac42e Mon Sep 17 00:00:00 2001 From: Jacob Sapoznikow Date: Mon, 30 Jan 2023 23:10:07 +0000 Subject: [PATCH 428/694] Fix CMake args --- packages/grpc-tools/CMakeLists.txt | 4 ++-- packages/grpc-tools/linux.toolchain.cmake | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/grpc-tools/CMakeLists.txt b/packages/grpc-tools/CMakeLists.txt index 4cb9e14ae..e13d9a28d 100644 --- a/packages/grpc-tools/CMakeLists.txt +++ b/packages/grpc-tools/CMakeLists.txt @@ -22,8 +22,8 @@ SET(PROTOBUF_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps/protobuf) ADD_SUBDIRECTORY(${PROTOBUF_ROOT_DIR}/cmake deps/protobuf) -SET(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++ -static -Wl,-Bstatic") -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector -static -Wl,-Bstatic") +SET(CMAKE_EXE_LINKER_FLAGS "-static-libstdc++") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector") ADD_EXECUTABLE(grpc_node_plugin src/node_generator.cc diff --git a/packages/grpc-tools/linux.toolchain.cmake b/packages/grpc-tools/linux.toolchain.cmake index 974dc84a8..30cf63ac1 100644 --- a/packages/grpc-tools/linux.toolchain.cmake +++ b/packages/grpc-tools/linux.toolchain.cmake @@ -1,6 +1,10 @@ IF(UNIX AND NOT APPLE) SET(CMAKE_COMPILER_PREFIX "") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static -Wl,-Bstatic") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static -Wl,-Bstatic") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -Wl,-Bstatic") + IF(GRPC_TOOLS_TARGET STREQUAL "x86_64") SET(CMAKE_COMPILER_PREFIX "x86_64-linux-gnu-") From a86cb96e91fe99700344ae0c10d88e3253b9dfe9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 30 Jan 2023 16:46:39 -0800 Subject: [PATCH 429/694] grpc-tools: Bump version to 1.12.4 --- packages/grpc-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-tools/package.json b/packages/grpc-tools/package.json index d4ac74ea1..1ffbb5214 100644 --- a/packages/grpc-tools/package.json +++ b/packages/grpc-tools/package.json @@ -1,6 +1,6 @@ { "name": "grpc-tools", - "version": "1.12.3", + "version": "1.12.4", "author": "Google Inc.", "description": "Tools for developing with gRPC on Node.js", "homepage": "https://grpc.io/", From 2807127ca76f31457b98ac6a4605cb13ebeedf9f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 30 Jan 2023 16:41:56 -0800 Subject: [PATCH 430/694] Add tests and fix bugs --- packages/grpc-js-xds/src/index.ts | 6 +- packages/grpc-js-xds/src/load-balancer-cds.ts | 38 +++- .../grpc-js-xds/src/xds-cluster-resolver.ts | 15 +- .../src/xds-stream-state/cds-state.ts | 37 +++- .../src/xds-stream-state/xds-stream-state.ts | 12 +- packages/grpc-js-xds/test/backend.ts | 35 +++- packages/grpc-js-xds/test/client.ts | 27 ++- packages/grpc-js-xds/test/framework.ts | 91 ++++++++- .../grpc-js-xds/test/test-cluster-type.ts | 180 ++++++++++++++++++ packages/grpc-js-xds/test/test-core.ts | 4 +- packages/grpc-js-xds/test/xds-server.ts | 3 +- 11 files changed, 413 insertions(+), 35 deletions(-) create mode 100644 packages/grpc-js-xds/test/test-cluster-type.ts diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index ca1cf72ef..933fafb51 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -17,7 +17,8 @@ import * as resolver_xds from './resolver-xds'; import * as load_balancer_cds from './load-balancer-cds'; -import * as load_balancer_eds from './load-balancer-eds'; +import * as xds_cluster_resolver from './xds-cluster-resolver'; +import * as xds_cluster_impl from './xds-cluster-impl'; import * as load_balancer_lrs from './load-balancer-lrs'; import * as load_balancer_priority from './load-balancer-priority'; import * as load_balancer_weighted_target from './load-balancer-weighted-target'; @@ -32,7 +33,8 @@ import * as csds from './csds'; export function register() { resolver_xds.setup(); load_balancer_cds.setup(); - load_balancer_eds.setup(); + xds_cluster_resolver.setup(); + xds_cluster_impl.setup(); load_balancer_lrs.setup(); load_balancer_priority.setup(); load_balancer_weighted_target.setup(); diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 3416021c6..b29daffd9 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -28,6 +28,7 @@ import LoadBalancingConfig = experimental.LoadBalancingConfig; import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; +import QueuePicker = experimental.QueuePicker; import { Watcher } from './xds-stream-state/xds-stream-state'; import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; import { Duration__Output } from './generated/google/protobuf/Duration'; @@ -172,7 +173,7 @@ function generateDiscoveryMechanismForCluster(config: Cluster__Output): Discover } } -const RECURSION_DEPTH_LIMIT = 16; +const RECURSION_DEPTH_LIMIT = 15; /** * Prerequisite: isClusterTreeFullyUpdated(tree, root) @@ -182,20 +183,25 @@ const RECURSION_DEPTH_LIMIT = 16; function getDiscoveryMechanismList(tree: ClusterTree, root: string): DiscoveryMechanism[] { const visited = new Set(); function getDiscoveryMechanismListHelper(node: string, depth: number): DiscoveryMechanism[] { - if (visited.has(node) || depth > RECURSION_DEPTH_LIMIT) { + if (depth > RECURSION_DEPTH_LIMIT) { + throw new Error('aggregate cluster graph exceeds max depth'); + } + if (visited.has(node)) { return []; } visited.add(node); - if (tree[root].children.length > 0) { + if (tree[node].children.length > 0) { + trace('Visit ' + node + ' children: [' + tree[node].children + ']'); // Aggregate cluster const result = []; - for (const child of tree[root].children) { + for (const child of tree[node].children) { result.push(...getDiscoveryMechanismListHelper(child, depth + 1)); } return result; } else { + trace('Visit leaf ' + node); // individual cluster - const config = tree[root].latestUpdate!; + const config = tree[node].latestUpdate!; return [generateDiscoveryMechanismForCluster(config)]; } } @@ -228,17 +234,25 @@ export class CdsLoadBalancer implements LoadBalancer { onValidUpdate: (update) => { this.clusterTree[cluster].latestUpdate = update; if (update.cluster_discovery_type === 'cluster_type') { - const children = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, update.cluster_type!.typed_config!.value).clusters + const children = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, update.cluster_type!.typed_config!.value).clusters; + trace('Received update for aggregate cluster ' + cluster + ' with children [' + children + ']'); this.clusterTree[cluster].children = children; children.forEach(child => this.addCluster(child)); } if (isClusterTreeFullyUpdated(this.clusterTree, this.latestConfig!.getCluster())) { + let discoveryMechanismList: DiscoveryMechanism[]; + try { + discoveryMechanismList = getDiscoveryMechanismList(this.clusterTree, this.latestConfig!.getCluster()); + } catch (e) { + this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()})); + return; + } const clusterResolverConfig = new XdsClusterResolverLoadBalancingConfig( - getDiscoveryMechanismList(this.clusterTree, this.latestConfig!.getCluster()), + discoveryMechanismList, [], [] ); - trace('Child update EDS config: ' + JSON.stringify(clusterResolverConfig)); + trace('Child update config: ' + JSON.stringify(clusterResolverConfig)); this.updatedChild = true; this.childBalancer.updateAddressList( [], @@ -305,13 +319,17 @@ export class CdsLoadBalancer implements LoadBalancer { /* If the cluster is changing, disable the old watcher before adding the new * one */ if ( - this.latestConfig?.getCluster() !== lbConfig.getCluster() + this.latestConfig && this.latestConfig.getCluster() !== lbConfig.getCluster() ) { - trace('Removing old cluster watchers rooted at ' + this.latestConfig!.getCluster()); + trace('Removing old cluster watchers rooted at ' + this.latestConfig.getCluster()); this.clearClusterTree(); this.updatedChild = false; } + if (!this.latestConfig) { + this.channelControlHelper.updateState(connectivityState.CONNECTING, new QueuePicker(this)); + } + this.latestConfig = lbConfig; this.addCluster(lbConfig.getCluster()); diff --git a/packages/grpc-js-xds/src/xds-cluster-resolver.ts b/packages/grpc-js-xds/src/xds-cluster-resolver.ts index 08096a882..e3dea30b4 100644 --- a/packages/grpc-js-xds/src/xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/xds-cluster-resolver.ts @@ -16,6 +16,7 @@ */ import { experimental, logVerbosity } from "@grpc/grpc-js"; +import { registerLoadBalancerType } from "@grpc/grpc-js/build/src/load-balancer"; import { EXPERIMENTAL_OUTLIER_DETECTION } from "./environment"; import { Locality__Output } from "./generated/envoy/config/core/v3/Locality"; import { ClusterLoadAssignment__Output } from "./generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; @@ -264,11 +265,12 @@ export class XdsClusterResolver implements LoadBalancer { return; } } - const newPriorityNames: string[] = []; + const fullPriorityList: string[] = []; const priorityChildren = new Map(); - const newLocalityPriorities = new Map(); const addressList: LocalitySubchannelAddress[] = []; for (const entry of this.discoveryMechanismList) { + const newPriorityNames: string[] = []; + const newLocalityPriorities = new Map(); const defaultEndpointPickingPolicy = entry.discoveryMechanism.type === 'EDS' ? validateLoadBalancingConfig({ round_robin: {} }) : validateLoadBalancingConfig({ pick_first: {} }); const endpointPickingPolicy: LoadBalancingConfig[] = [ ...this.latestConfig.getEndpointPickingPolicy(), @@ -330,7 +332,6 @@ export class XdsClusterResolver implements LoadBalancer { }); } } - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! INSERT xds_cluster_impl CONFIG !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); const xdsClusterImplConfig = new XdsClusterImplLoadBalancingConfig(entry.discoveryMechanism.cluster, priorityEntry.dropCategories, [weightedTargetConfig], entry.discoveryMechanism.eds_service_name, entry.discoveryMechanism.lrs_load_reporting_server_name, entry.discoveryMechanism.max_concurrent_requests); let outlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined; @@ -346,8 +347,9 @@ export class XdsClusterResolver implements LoadBalancer { } entry.localityPriorities = newLocalityPriorities; entry.priorityNames = newPriorityNames; + fullPriorityList.push(...newPriorityNames); } - const childConfig: PriorityLoadBalancingConfig = new PriorityLoadBalancingConfig(priorityChildren, newPriorityNames); + const childConfig: PriorityLoadBalancingConfig = new PriorityLoadBalancingConfig(priorityChildren, fullPriorityList); trace('Child update addresses: ' + addressList.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); trace('Child update priority config: ' + JSON.stringify(childConfig.toJsonObject(), undefined, 2)); this.childBalancer.updateAddressList( @@ -363,6 +365,7 @@ export class XdsClusterResolver implements LoadBalancer { return; } trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); + this.latestConfig = lbConfig; this.latestAttributes = attributes; this.xdsClient = attributes.xdsClient as XdsClient; if (this.discoveryMechanismList.length === 0) { @@ -465,4 +468,8 @@ export class XdsClusterResolverChildPolicyHandler extends ChildLoadBalancerHandl } return false; } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, XdsClusterResolver, XdsClusterResolverLoadBalancingConfig); } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 9fd12d6ed..384a5da5b 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -19,6 +19,7 @@ import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; +import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from "../resources"; import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; export class CdsState extends BaseXdsStreamState implements XdsStreamState { @@ -53,11 +54,37 @@ export class CdsState extends BaseXdsStreamState implements Xds } public validateResponse(message: Cluster__Output): boolean { - if (message.type !== 'EDS') { - return false; - } - if (!message.eds_cluster_config?.eds_config?.ads) { - return false; + if (message.cluster_discovery_type === 'cluster_type') { + if (!(message.cluster_type?.typed_config && message.cluster_type.typed_config.type_url === CLUSTER_CONFIG_TYPE_URL)) { + return false; + } + const clusterConfig = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, message.cluster_type.typed_config.value); + if (clusterConfig.clusters.length === 0) { + return false; + } + } else { + if (message.type === 'EDS') { + if (!message.eds_cluster_config?.eds_config?.ads) { + return false; + } + } else if (message.type === 'LOGICAL_DNS') { + if (!message.load_assignment) { + return false; + } + if (message.load_assignment.endpoints.length !== 1) { + return false; + } + if (message.load_assignment.endpoints[0].lb_endpoints.length !== 1) { + return false; + } + const socketAddress = message.load_assignment.endpoints[0].lb_endpoints[0].endpoint?.address?.socket_address; + if (!socketAddress) { + return false; + } + if (socketAddress.port_specifier !== 'port_value') { + return false; + } + } } if (message.lb_policy !== 'ROUND_ROBIN') { return false; diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index e20bc7e9b..6e58f1b70 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -187,7 +187,11 @@ export abstract class BaseXdsStreamState implements XdsStreamState if (subscriptionEntry) { const watchers = subscriptionEntry.watchers; for (const watcher of watchers) { - watcher.onValidUpdate(resource); + /* Use process.nextTick to prevent errors from the watcher from + * bubbling up through here. */ + process.nextTick(() => { + watcher.onValidUpdate(resource); + }); } clearTimeout(subscriptionEntry.resourceTimer); subscriptionEntry.cachedResponse = resource; @@ -238,7 +242,11 @@ export abstract class BaseXdsStreamState implements XdsStreamState this.trace('Reporting resource does not exist named ' + resourceName); missingNames.push(resourceName); for (const watcher of subscriptionEntry.watchers) { - watcher.onResourceDoesNotExist(); + /* Use process.nextTick to prevent errors from the watcher from + * bubbling up through here. */ + process.nextTick(() => { + watcher.onResourceDoesNotExist(); + }); } subscriptionEntry.cachedResponse = null; } diff --git a/packages/grpc-js-xds/test/backend.ts b/packages/grpc-js-xds/test/backend.ts index ce509c556..3f20f1f39 100644 --- a/packages/grpc-js-xds/test/backend.ts +++ b/packages/grpc-js-xds/test/backend.ts @@ -39,13 +39,11 @@ const loadedProtos = loadPackageDefinition(loadSync( })) as unknown as ProtoGrpcType; export class Backend { - private server: Server; + private server: Server | null = null; private receivedCallCount = 0; private callListeners: (() => void)[] = []; private port: number | null = null; constructor() { - this.server = new Server(); - this.server.addService(loadedProtos.grpc.testing.EchoTestService.service, this as unknown as UntypedServiceImplementation); } Echo(call: ServerUnaryCall, callback: sendUnaryData) { // call.request.params is currently ignored @@ -63,10 +61,16 @@ export class Backend { } start(callback: (error: Error | null, port: number) => void) { - this.server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (error, port) => { + if (this.server) { + throw new Error("Backend already running"); + } + this.server = new Server(); + this.server.addService(loadedProtos.grpc.testing.EchoTestService.service, this as unknown as UntypedServiceImplementation); + const boundPort = this.port ?? 0; + this.server.bindAsync(`localhost:${boundPort}`, ServerCredentials.createInsecure(), (error, port) => { if (!error) { this.port = port; - this.server.start(); + this.server!.start(); } callback(error, port); }) @@ -100,6 +104,25 @@ export class Backend { } shutdown(callback: (error?: Error) => void) { - this.server.tryShutdown(callback); + if (this.server) { + this.server.tryShutdown(error => { + this.server = null; + callback(error); + }); + } else { + process.nextTick(callback); + } + } + + shutdownAsync(): Promise { + return new Promise((resolve, reject) => { + this.shutdown(error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); } } \ No newline at end of file diff --git a/packages/grpc-js-xds/test/client.ts b/packages/grpc-js-xds/test/client.ts index 6404a3eb2..bcc6f3cd8 100644 --- a/packages/grpc-js-xds/test/client.ts +++ b/packages/grpc-js-xds/test/client.ts @@ -15,7 +15,7 @@ * */ -import { credentials, loadPackageDefinition } from "@grpc/grpc-js"; +import { credentials, loadPackageDefinition, ServiceError } from "@grpc/grpc-js"; import { loadSync } from "@grpc/proto-loader"; import { ProtoGrpcType } from "./generated/echo"; import { EchoTestServiceClient } from "./generated/grpc/testing/EchoTestService"; @@ -69,4 +69,29 @@ export class XdsTestClient { this.stopCalls(); this.client.close(); } + + sendOneCall(callback: (error: ServiceError | null) => void) { + const deadline = new Date(); + deadline.setMilliseconds(deadline.getMilliseconds() + 500); + this.client.echo({message: 'test'}, {deadline}, (error, value) => { + callback(error); + }); + } + + sendNCalls(count: number, callback: (error: ServiceError| null) => void) { + const sendInner = (count: number, callback: (error: ServiceError| null) => void) => { + if (count === 0) { + callback(null); + return; + } + this.sendOneCall(error => { + if (error) { + callback(error); + return; + } + sendInner(count-1, callback); + }); + } + sendInner(count, callback); + } } \ No newline at end of file diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index 945b08a3a..bcc3cd1a5 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -24,9 +24,10 @@ import { Route } from "../src/generated/envoy/config/route/v3/Route"; import { Listener } from "../src/generated/envoy/config/listener/v3/Listener"; import { HttpConnectionManager } from "../src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager"; import { AnyExtension } from "@grpc/proto-loader"; -import { HTTP_CONNECTION_MANGER_TYPE_URL } from "../src/resources"; +import { CLUSTER_CONFIG_TYPE_URL, HTTP_CONNECTION_MANGER_TYPE_URL } from "../src/resources"; import { LocalityLbEndpoints } from "../src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints"; import { LbEndpoint } from "../src/generated/envoy/config/endpoint/v3/LbEndpoint"; +import { ClusterConfig } from "../src/generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig"; interface Endpoint { locality: Locality; @@ -58,7 +59,15 @@ function getLocalityLbEndpoints(endpoint: Endpoint): LocalityLbEndpoints { } } -export class FakeCluster { +export interface FakeCluster { + getClusterConfig(): Cluster; + getAllClusterConfigs(): Cluster[]; + getName(): string; + startAllBackends(): Promise; + waitForAllBackendsToReceiveTraffic(): Promise; +} + +export class FakeEdsCluster implements FakeCluster { constructor(private name: string, private endpoints: Endpoint[]) {} getEndpointConfig(): ClusterLoadAssignment { @@ -77,6 +86,10 @@ export class FakeCluster { } } + getAllClusterConfigs(): Cluster[] { + return [this.getClusterConfig()]; + } + getName() { return this.name; } @@ -121,6 +134,80 @@ export class FakeCluster { } } +export class FakeDnsCluster implements FakeCluster { + constructor(private name: string, private backend: Backend) {} + + getClusterConfig(): Cluster { + return { + name: this.name, + type: 'LOGICAL_DNS', + lb_policy: 'ROUND_ROBIN', + load_assignment: { + endpoints: [{ + lb_endpoints: [{ + endpoint: { + address: { + socket_address: { + address: 'localhost', + port_value: this.backend.getPort() + } + } + } + }] + }] + } + }; + } + getAllClusterConfigs(): Cluster[] { + return [this.getClusterConfig()]; + } + getName(): string { + return this.name; + } + startAllBackends(): Promise { + return this.backend.startAsync(); + } + waitForAllBackendsToReceiveTraffic(): Promise { + return new Promise((resolve, reject) => { + this.backend.onCall(resolve); + }); + } +} + +export class FakeAggregateCluster implements FakeCluster { + constructor(private name: string, private children: FakeCluster[]) {} + + getClusterConfig(): Cluster { + const clusterConfig: ClusterConfig & AnyExtension = { + '@type': CLUSTER_CONFIG_TYPE_URL, + clusters: this.children.map(child => child.getName()) + }; + return { + name: this.name, + lb_policy: 'ROUND_ROBIN', + cluster_type: { + typed_config: clusterConfig + } + } + } + getAllClusterConfigs(): Cluster[] { + const allConfigs = [this.getClusterConfig()]; + for (const child of this.children) { + allConfigs.push(...child.getAllClusterConfigs()); + } + return allConfigs; + } + getName(): string { + return this.name; + } + startAllBackends(): Promise { + return Promise.all(this.children.map(child => child.startAllBackends())); + } + waitForAllBackendsToReceiveTraffic(): Promise { + return Promise.all(this.children.map(child => child.waitForAllBackendsToReceiveTraffic())).then(() => {}); + } +} + interface FakeRoute { cluster?: FakeCluster; weightedClusters?: [{cluster: FakeCluster, weight: number}]; diff --git a/packages/grpc-js-xds/test/test-cluster-type.ts b/packages/grpc-js-xds/test/test-cluster-type.ts new file mode 100644 index 000000000..57f45ea31 --- /dev/null +++ b/packages/grpc-js-xds/test/test-cluster-type.ts @@ -0,0 +1,180 @@ +/* + * 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. + * + */ + +import { register } from "../src"; +import assert = require("assert"); +import { XdsServer } from "./xds-server"; +import { XdsTestClient } from "./client"; +import { FakeAggregateCluster, FakeDnsCluster, FakeEdsCluster, FakeRouteGroup } from "./framework"; +import { Backend } from "./backend"; + +register(); + +describe.only('Cluster types', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }); + describe('Logical DNS Clusters', () => { + it('Should successfully make RPCs', done => { + const cluster = new FakeDnsCluster('dnsCluster', new Backend()); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); + } + }); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.sendOneCall(error => { + done(error); + }); + }, reason => done(reason)); + }); + }); + /* These tests pass on Node 18 fail on Node 16, probably because of + * https://github.com/nodejs/node/issues/42713 */ + describe.skip('Aggregate DNS Clusters', () => { + it('Should result in prioritized clusters', () => { + const backend1 = new Backend(); + const backend2 = new Backend(); + const cluster1 = new FakeEdsCluster('cluster1', [{backends: [backend1], locality:{region: 'region1'}}]); + const cluster2 = new FakeEdsCluster('cluster2', [{backends: [backend2], locality:{region: 'region2'}}]); + const aggregateCluster = new FakeAggregateCluster('aggregateCluster', [cluster1, cluster2]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster}]); + return routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster1.getEndpointConfig()); + xdsServer.setCdsResource(cluster1.getClusterConfig()); + xdsServer.setEdsResource(cluster2.getEndpointConfig()); + xdsServer.setCdsResource(cluster2.getClusterConfig()); + xdsServer.setCdsResource(aggregateCluster.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 = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + return cluster1.waitForAllBackendsToReceiveTraffic(); + }).then(() => backend1.shutdownAsync() + ).then(() => cluster2.waitForAllBackendsToReceiveTraffic() + ).then(() => backend1.startAsync() + ).then(() => cluster1.waitForAllBackendsToReceiveTraffic()); + }); + it('Should handle a diamond dependency', () => { + const backend1 = new Backend(); + const backend2 = new Backend(); + const cluster1 = new FakeEdsCluster('cluster1', [{backends: [backend1], locality:{region: 'region1'}}]); + const cluster2 = new FakeEdsCluster('cluster2', [{backends: [backend2], locality:{region: 'region2'}}]); + const aggregateCluster1 = new FakeAggregateCluster('aggregateCluster1', [cluster1, cluster2]); + const aggregateCluster2 = new FakeAggregateCluster('aggregateCluster2', [cluster1, aggregateCluster1]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster2}]); + return Promise.all([backend1.startAsync(), backend2.startAsync()]).then(() => { + xdsServer.setEdsResource(cluster1.getEndpointConfig()); + xdsServer.setCdsResource(cluster1.getClusterConfig()); + xdsServer.setEdsResource(cluster2.getEndpointConfig()); + xdsServer.setCdsResource(cluster2.getClusterConfig()); + xdsServer.setCdsResource(aggregateCluster1.getClusterConfig()); + xdsServer.setCdsResource(aggregateCluster2.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 = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + return cluster1.waitForAllBackendsToReceiveTraffic(); + }).then(() => backend1.shutdownAsync() + ).then(() => cluster2.waitForAllBackendsToReceiveTraffic() + ).then(() => backend1.startAsync() + ).then(() => cluster1.waitForAllBackendsToReceiveTraffic()); + }); + it('Should handle EDS then DNS cluster order', () => { + const backend1 = new Backend(); + const backend2 = new Backend(); + const cluster1 = new FakeEdsCluster('cluster1', [{backends: [backend1], locality:{region: 'region1'}}]); + const cluster2 = new FakeDnsCluster('cluster2', backend2); + const aggregateCluster = new FakeAggregateCluster('aggregateCluster', [cluster1, cluster2]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster}]); + return routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster1.getEndpointConfig()); + xdsServer.setCdsResource(cluster1.getClusterConfig()); + xdsServer.setCdsResource(cluster2.getClusterConfig()); + xdsServer.setCdsResource(aggregateCluster.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 = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + return cluster1.waitForAllBackendsToReceiveTraffic(); + }).then(() => backend1.shutdownAsync() + ).then(() => cluster2.waitForAllBackendsToReceiveTraffic() + ).then(() => backend1.startAsync() + ).then(() => cluster1.waitForAllBackendsToReceiveTraffic()); + }); + it('Should handle DNS then EDS cluster order', () => { + const backend1 = new Backend(); + const backend2 = new Backend(); + const cluster1 = new FakeDnsCluster('cluster1', backend1); + const cluster2 = new FakeEdsCluster('cluster2', [{backends: [backend2], locality:{region: 'region2'}}]); + const aggregateCluster = new FakeAggregateCluster('aggregateCluster', [cluster1, cluster2]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster}]); + return routeGroup.startAllBackends().then(() => { + xdsServer.setCdsResource(cluster1.getClusterConfig()); + xdsServer.setEdsResource(cluster2.getEndpointConfig()); + xdsServer.setCdsResource(cluster2.getClusterConfig()); + xdsServer.setCdsResource(aggregateCluster.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 = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + return cluster1.waitForAllBackendsToReceiveTraffic(); + }).then(() => backend1.shutdownAsync() + ).then(() => cluster2.waitForAllBackendsToReceiveTraffic() + ).then(() => backend1.startAsync() + ).then(() => cluster1.waitForAllBackendsToReceiveTraffic()); + }); + }); +}); diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts index d0d1b6031..7c28f0920 100644 --- a/packages/grpc-js-xds/test/test-core.ts +++ b/packages/grpc-js-xds/test/test-core.ts @@ -17,7 +17,7 @@ import { Backend } from "./backend"; import { XdsTestClient } from "./client"; -import { FakeCluster, FakeRouteGroup } from "./framework"; +import { FakeEdsCluster, FakeRouteGroup } from "./framework"; import { XdsServer } from "./xds-server"; import { register } from "../src"; @@ -39,7 +39,7 @@ describe('core xDS functionality', () => { xdsServer?.shutdownServer(); }) it('should route requests to the single backend', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); diff --git a/packages/grpc-js-xds/test/xds-server.ts b/packages/grpc-js-xds/test/xds-server.ts index b82a3a673..36fd27b85 100644 --- a/packages/grpc-js-xds/test/xds-server.ts +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -40,7 +40,8 @@ const loadedProtos = loadPackageDefinition(loadSync( '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' + 'envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto', + 'envoy/extensions/clusters/aggregate/v3/cluster.proto' ], { keepCase: true, From fed7b02a35631630e0c53fcf4e92bb9cab165c97 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 3 Feb 2023 09:33:51 -0800 Subject: [PATCH 431/694] Update latestConfig in ChildLoadBalancerHandler when handling update --- packages/grpc-js/src/load-balancer-child-handler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 86f29e2dc..595d411a0 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -127,6 +127,7 @@ export class ChildLoadBalancerHandler implements LoadBalancer { childToUpdate = this.pendingChild; } } + this.latestConfig = lbConfig; childToUpdate.updateAddressList(addressList, lbConfig, attributes); } exitIdle(): void { From b914a0388f3d8f2af9262bdef5ec456e848a35fa Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 3 Feb 2023 13:27:46 -0800 Subject: [PATCH 432/694] Validate that LOGICAL_DNS address is non-empty --- packages/grpc-js-xds/src/xds-stream-state/cds-state.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 384a5da5b..656a25418 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -81,6 +81,9 @@ export class CdsState extends BaseXdsStreamState implements Xds if (!socketAddress) { return false; } + if (socketAddress.address === '') { + return false; + } if (socketAddress.port_specifier !== 'port_value') { return false; } From 4e148cbb77ffd2cb5670f9b8b692b8d22db0f99d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 3 Feb 2023 15:16:42 -0800 Subject: [PATCH 433/694] Use the load-balancer filename prefix for the new LB policies --- packages/grpc-js-xds/src/index.ts | 4 ++-- packages/grpc-js-xds/src/load-balancer-cds.ts | 2 +- ...{xds-cluster-impl.ts => load-balancer-xds-cluster-impl.ts} | 0 ...ster-resolver.ts => load-balancer-xds-cluster-resolver.ts} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename packages/grpc-js-xds/src/{xds-cluster-impl.ts => load-balancer-xds-cluster-impl.ts} (100%) rename packages/grpc-js-xds/src/{xds-cluster-resolver.ts => load-balancer-xds-cluster-resolver.ts} (99%) diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 933fafb51..51926ac47 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -17,8 +17,8 @@ import * as resolver_xds from './resolver-xds'; import * as load_balancer_cds from './load-balancer-cds'; -import * as xds_cluster_resolver from './xds-cluster-resolver'; -import * as xds_cluster_impl from './xds-cluster-impl'; +import * as xds_cluster_resolver from './load-balancer-xds-cluster-resolver'; +import * as xds_cluster_impl from './load-balancer-xds-cluster-impl'; import * as load_balancer_lrs from './load-balancer-lrs'; import * as load_balancer_priority from './load-balancer-priority'; import * as load_balancer_weighted_target from './load-balancer-weighted-target'; diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index b29daffd9..84d8de25f 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -33,7 +33,7 @@ import { Watcher } from './xds-stream-state/xds-stream-state'; import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; import { Duration__Output } from './generated/google/protobuf/Duration'; import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; -import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler, XdsClusterResolverLoadBalancingConfig } from './xds-cluster-resolver'; +import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler, XdsClusterResolverLoadBalancingConfig } from './load-balancer-xds-cluster-resolver'; import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources'; const TRACER_NAME = 'cds_balancer'; diff --git a/packages/grpc-js-xds/src/xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts similarity index 100% rename from packages/grpc-js-xds/src/xds-cluster-impl.ts rename to packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts diff --git a/packages/grpc-js-xds/src/xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts similarity index 99% rename from packages/grpc-js-xds/src/xds-cluster-resolver.ts rename to packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index e3dea30b4..d86873693 100644 --- a/packages/grpc-js-xds/src/xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -24,7 +24,7 @@ import { LrsLoadBalancingConfig } from "./load-balancer-lrs"; import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from "./load-balancer-priority"; import { WeightedTarget, WeightedTargetLoadBalancingConfig } from "./load-balancer-weighted-target"; import { getSingletonXdsClient, XdsClient } from "./xds-client"; -import { DropCategory, XdsClusterImplLoadBalancingConfig } from "./xds-cluster-impl"; +import { DropCategory, XdsClusterImplLoadBalancingConfig } from "./load-balancer-xds-cluster-impl"; import { Watcher } from "./xds-stream-state/xds-stream-state"; import LoadBalancingConfig = experimental.LoadBalancingConfig; From cf090c7f5075452d322ead84496b7f0ed0bb1868 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Feb 2023 13:44:22 -0800 Subject: [PATCH 434/694] grpc-js: Fix commitCallWithMostMessages trying to commit completed attempts --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/retrying-call.ts | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 9e1dfcba6..290d80b75 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.7", + "version": "1.8.8", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index 8daf5ba70..05e5f40c1 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -273,15 +273,24 @@ export class RetryingCall implements Call { } private commitCallWithMostMessages() { + if (this.state === 'COMMITTED') { + return; + } let mostMessages = -1; let callWithMostMessages = -1; for (const [index, childCall] of this.underlyingCalls.entries()) { - if (childCall.nextMessageToSend > mostMessages) { + if (childCall.state === 'ACTIVE' && childCall.nextMessageToSend > mostMessages) { mostMessages = childCall.nextMessageToSend; callWithMostMessages = index; } } - this.commitCall(callWithMostMessages); + if (callWithMostMessages === -1) { + /* There are no active calls, disable retries to force the next call that + * is started to be committed. */ + this.state = 'TRANSPARENT_ONLY'; + } else { + this.commitCall(callWithMostMessages); + } } private isStatusCodeInList(list: (Status | string)[], code: Status) { @@ -601,7 +610,11 @@ export class RetryingCall implements Call { } } else { this.commitCallWithMostMessages(); - const call = this.underlyingCalls[this.committedCallIndex!]; + // commitCallWithMostMessages can fail if we are between ping attempts + if (this.committedCallIndex === null) { + return; + } + const call = this.underlyingCalls[this.committedCallIndex]; bufferEntry.callback = context.callback; if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { call.call.sendMessageWithContext({ From 3596c4f65518b1f0e8aae841b255a98e68dfe608 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Feb 2023 14:52:20 -0800 Subject: [PATCH 435/694] grpc-js: Remove progress field in status from retrying call --- packages/grpc-js/src/retrying-call.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index 8daf5ba70..351678965 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -212,7 +212,12 @@ export class RetryingCall implements Call { } } process.nextTick(() => { - this.listener?.onReceiveStatus(statusObject); + // Explicitly construct status object to remove progress field + this.listener?.onReceiveStatus({ + code: statusObject.code, + details: statusObject.details, + metadata: statusObject.metadata + }); }); } From 18c803e6dd458b762fa5fe7361b4abc59d263382 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 8 Feb 2023 09:55:32 -0800 Subject: [PATCH 436/694] grpc-js: Export InterceptingListener and NextCall types --- packages/grpc-js/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 786fa9925..51f394785 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -237,7 +237,7 @@ export const getClientChannel = (client: Client) => { export { StatusBuilder }; -export { Listener } from './call-interface'; +export { Listener, InterceptingListener } from './call-interface'; export { Requester, @@ -248,6 +248,7 @@ export { InterceptorProvider, InterceptingCall, InterceptorConfigurationError, + NextCall } from './client-interceptors'; export { From 37eb5ed2fabb4a3831f0a506bf44cd6e1ae3da5a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Feb 2023 10:18:24 -0800 Subject: [PATCH 437/694] grpc-js: Improve timeout handling and deadline logging --- packages/grpc-js/src/deadline.ts | 39 +++++++++++++++++++++++- packages/grpc-js/src/internal-channel.ts | 4 +-- packages/grpc-js/src/resolving-call.ts | 8 ++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/deadline.ts b/packages/grpc-js/src/deadline.ts index 58ea0a805..be1f3c3b0 100644 --- a/packages/grpc-js/src/deadline.ts +++ b/packages/grpc-js/src/deadline.ts @@ -51,8 +51,45 @@ export function getDeadlineTimeoutString(deadline: Deadline) { throw new Error('Deadline is too far in the future') } +/** + * See https://nodejs.org/api/timers.html#settimeoutcallback-delay-args + * In particular, "When delay is larger than 2147483647 or less than 1, the + * delay will be set to 1. Non-integer delays are truncated to an integer." + * This number of milliseconds is almost 25 days. + */ +const MAX_TIMEOUT_TIME = 2147483647; + +/** + * Get the timeout value that should be passed to setTimeout now for the timer + * to end at the deadline. For any deadline before now, the timer should end + * immediately, represented by a value of 0. For any deadline more than + * MAX_TIMEOUT_TIME milliseconds in the future, a timer cannot be set that will + * end at that time, so it is treated as infinitely far in the future. + * @param deadline + * @returns + */ export function getRelativeTimeout(deadline: Deadline) { const deadlineMs = deadline instanceof Date ? deadline.getTime() : deadline; const now = new Date().getTime(); - return deadlineMs - now; + const timeout = deadlineMs - now; + if (timeout < 0) { + return 0; + } else if (timeout > MAX_TIMEOUT_TIME) { + return Infinity + } else { + return timeout; + } +} + +export function deadlineToString(deadline: Deadline): string { + if (deadline instanceof Date) { + return deadline.toISOString(); + } else { + const dateDeadline = new Date(deadline); + if (Number.isNaN(dateDeadline.getTime())) { + return '' + deadline; + } else { + return dateDeadline.toISOString(); + } + } } \ No newline at end of file diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 9137be272..2a1d00f53 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -46,7 +46,7 @@ import { LoadBalancingCall } from './load-balancing-call'; import { CallCredentials } from './call-credentials'; import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from './call-interface'; import { SubchannelCall } from './subchannel-call'; -import { Deadline, getDeadlineTimeoutString } from './deadline'; +import { Deadline, deadlineToString, getDeadlineTimeoutString } from './deadline'; import { ResolvingCall } from './resolving-call'; import { getNextCallNumber } from './call-number'; import { restrictControlPlaneStatusCode } from './control-plane-status'; @@ -469,7 +469,7 @@ export class InternalChannel { '] method="' + method + '", deadline=' + - deadline + deadlineToString(deadline) ); const finalOptions: CallStreamOptions = { deadline: deadline, diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index f1fecd1d2..b6398126c 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -18,7 +18,7 @@ import { CallCredentials } from "./call-credentials"; import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; import { LogVerbosity, Propagate, Status } from "./constants"; -import { Deadline, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; +import { Deadline, deadlineToString, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; import { FilterStack, FilterStackFactory } from "./filter-stack"; import { InternalChannel } from "./internal-channel"; import { Metadata } from "./metadata"; @@ -79,9 +79,9 @@ export class ResolvingCall implements Call { private runDeadlineTimer() { clearTimeout(this.deadlineTimer); - this.trace('Deadline: ' + this.deadline); - if (this.deadline !== Infinity) { - const timeout = getRelativeTimeout(this.deadline); + this.trace('Deadline: ' + deadlineToString(this.deadline)); + const timeout = getRelativeTimeout(this.deadline); + if (timeout !== Infinity) { this.trace('Deadline will be reached in ' + timeout + 'ms'); const handleDeadline = () => { this.cancelWithStatus( From faf96a0e4f089371f83835a773d779049cd18cc6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Feb 2023 17:04:41 -0800 Subject: [PATCH 438/694] grpc-js-xds: Fix bug that prevented priority name reuse --- packages/grpc-js-xds/interop/Dockerfile | 2 +- packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index b93e309d7..58a9a98f4 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -33,6 +33,6 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ ENV GRPC_VERBOSITY="DEBUG" -ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection +ENV GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_cluster_resolver,xds_cluster_impl,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index d86873693..8c01138b5 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -331,6 +331,7 @@ export class XdsClusterResolver implements LoadBalancer { ...address, }); } + newLocalityPriorities.set(localityToName(localityObj.locality), priority); } const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); const xdsClusterImplConfig = new XdsClusterImplLoadBalancingConfig(entry.discoveryMechanism.cluster, priorityEntry.dropCategories, [weightedTargetConfig], entry.discoveryMechanism.eds_service_name, entry.discoveryMechanism.lrs_load_reporting_server_name, entry.discoveryMechanism.max_concurrent_requests); From c20ddd3d2b0a47cb3fb1ca4911664cf0f93a7f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20R=C3=B6nnkvist?= Date: Fri, 10 Feb 2023 10:50:38 +0100 Subject: [PATCH 439/694] write @deprecated jsdoc annotation if comments are enabled update golden generated with @deprecated annotation --- .../bin/proto-loader-gen-types.ts | 45 ++++++++++--------- .../google/protobuf/FileOptions.ts | 6 +++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index f75822084..057d14f3c 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -169,13 +169,18 @@ function getChildMessagesAndEnums(namespace: Protobuf.NamespaceBase): (Protobuf. return messageList; } -function formatComment(formatter: TextFormatter, comment?: string | null) { - if (!comment) { +function formatComment(formatter: TextFormatter, comment?: string | null, options?: Protobuf.ReflectionObject['options']) { + if (!comment && !options?.deprecated) { return; } formatter.writeLine('/**'); - for(const line of comment.split('\n')) { - formatter.writeLine(` * ${line.replace(/\*\//g, '* /')}`); + if (comment) { + for(const line of comment.split('\n')) { + formatter.writeLine(` * ${line.replace(/\*\//g, '* /')}`); + } + } + if (options?.deprecated) { + formatter.writeLine(' * @deprecated'); } formatter.writeLine(' */'); } @@ -184,7 +189,7 @@ const typeBrandHint = `This field is a type brand and is not populated at runtim https://github.com/grpc/grpc-node/pull/2281`; function formatTypeBrand(formatter: TextFormatter, messageType: Protobuf.Type) { - formatComment(formatter, typeBrandHint); + formatComment(formatter, typeBrandHint, messageType.options); formatter.writeLine(`__type: '${messageType.fullName}'`); } @@ -245,7 +250,7 @@ function getFieldTypePermissive(field: Protobuf.FieldBase, options: GeneratorOpt function generatePermissiveMessageInterface(formatter: TextFormatter, messageType: Protobuf.Type, options: GeneratorOptions, nameOverride?: string) { const {inputName} = useNameFmter(options); if (options.includeComments) { - formatComment(formatter, messageType.comment); + formatComment(formatter, messageType.comment, messageType.options); } if (messageType.fullName === '.google.protobuf.Any') { /* This describes the behavior of the Protobuf.js Any wrapper fromObject @@ -262,14 +267,14 @@ function generatePermissiveMessageInterface(formatter: TextFormatter, messageTyp const repeatedString = field.repeated ? '[]' : ''; const type: string = getFieldTypePermissive(field, options); if (options.includeComments) { - formatComment(formatter, field.comment); + formatComment(formatter, field.comment, field.options); } formatter.writeLine(`'${field.name}'?: (${type})${repeatedString};`); } for (const oneof of messageType.oneofsArray) { const typeString = oneof.fieldsArray.map(field => `"${field.name}"`).join('|'); if (options.includeComments) { - formatComment(formatter, oneof.comment); + formatComment(formatter, oneof.comment, oneof.options); } formatter.writeLine(`'${oneof.name}'?: ${typeString};`); } @@ -353,7 +358,7 @@ function getFieldTypeRestricted(field: Protobuf.FieldBase, options: GeneratorOpt function generateRestrictedMessageInterface(formatter: TextFormatter, messageType: Protobuf.Type, options: GeneratorOptions, nameOverride?: string) { const {outputName} = useNameFmter(options); if (options.includeComments) { - formatComment(formatter, messageType.comment); + formatComment(formatter, messageType.comment, messageType.options); } if (messageType.fullName === '.google.protobuf.Any' && options.json) { /* This describes the behavior of the Protobuf.js Any wrapper toObject @@ -383,7 +388,7 @@ function generateRestrictedMessageInterface(formatter: TextFormatter, messageTyp const repeatedString = field.repeated ? '[]' : ''; const type = getFieldTypeRestricted(field, options); if (options.includeComments) { - formatComment(formatter, field.comment); + formatComment(formatter, field.comment, field.options); } formatter.writeLine(`'${field.name}'${optionalString}: (${type})${repeatedString};`); } @@ -391,7 +396,7 @@ function generateRestrictedMessageInterface(formatter: TextFormatter, messageTyp for (const oneof of messageType.oneofsArray) { const typeString = oneof.fieldsArray.map(field => `"${field.name}"`).join('|'); if (options.includeComments) { - formatComment(formatter, oneof.comment); + formatComment(formatter, oneof.comment, oneof.options); } formatter.writeLine(`'${oneof.name}': ${typeString};`); } @@ -470,13 +475,13 @@ function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum formatter.writeLine(`// Original file: ${(enumType.filename ?? 'null')?.replace(/\\/g, '/')}`); formatter.writeLine(''); if (options.includeComments) { - formatComment(formatter, enumType.comment); + formatComment(formatter, enumType.comment, enumType.options); } formatter.writeLine(`export const ${name} = {`); formatter.indent(); for (const key of Object.keys(enumType.values)) { if (options.includeComments) { - formatComment(formatter, enumType.comments[key]); + formatComment(formatter, enumType.comments[key], (enumType.valuesOptions ?? {})[key]); } formatter.writeLine(`${key}: ${options.enums == String ? `'${key}'` : enumType.values[key]},`); } @@ -486,7 +491,7 @@ function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum // Permissive Type formatter.writeLine(''); if (options.includeComments) { - formatComment(formatter, enumType.comment); + formatComment(formatter, enumType.comment, enumType.options); } formatter.writeLine(`export type ${inputName(name)} =`) formatter.indent(); @@ -502,7 +507,7 @@ function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum // Restrictive Type formatter.writeLine(''); if (options.includeComments) { - formatComment(formatter, enumType.comment); + formatComment(formatter, enumType.comment, enumType.options); } formatter.writeLine(`export type ${outputName(name)} = typeof ${name}[keyof typeof ${name}]`) } @@ -542,7 +547,7 @@ const CLIENT_RESERVED_METHOD_NAMES = new Set([ function generateServiceClientInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { const {outputName, inputName} = useNameFmter(options); if (options.includeComments) { - formatComment(formatter, serviceType.comment); + formatComment(formatter, serviceType.comment, serviceType.options); } formatter.writeLine(`export interface ${serviceType.name}Client extends grpc.Client {`); formatter.indent(); @@ -553,7 +558,7 @@ function generateServiceClientInterface(formatter: TextFormatter, serviceType: P continue; } if (options.includeComments) { - formatComment(formatter, method.comment); + formatComment(formatter, method.comment, method.options); } const requestType = inputName(getTypeInterfaceName(method.resolvedRequestType!)); const responseType = outputName(getTypeInterfaceName(method.resolvedResponseType!)); @@ -597,14 +602,14 @@ function generateServiceClientInterface(formatter: TextFormatter, serviceType: P function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { const {inputName, outputName} = useNameFmter(options); if (options.includeComments) { - formatComment(formatter, serviceType.comment); + formatComment(formatter, serviceType.comment, serviceType.options); } formatter.writeLine(`export interface ${serviceType.name}Handlers extends grpc.UntypedServiceImplementation {`); formatter.indent(); for (const methodName of Object.keys(serviceType.methods).sort()) { const method = serviceType.methods[methodName]; if (options.includeComments) { - formatComment(formatter, method.comment); + formatComment(formatter, method.comment, serviceType.options); } const requestType = outputName(getTypeInterfaceName(method.resolvedRequestType!)); const responseType = inputName(getTypeInterfaceName(method.resolvedResponseType!)); @@ -711,7 +716,7 @@ function generateServiceImports(formatter: TextFormatter, namespace: Protobuf.Na function generateSingleLoadedDefinitionType(formatter: TextFormatter, nested: Protobuf.ReflectionObject, options: GeneratorOptions) { if (nested instanceof Protobuf.Service) { if (options.includeComments) { - formatComment(formatter, nested.comment); + formatComment(formatter, nested.comment, nested.options); } const typeInterfaceName = getTypeInterfaceName(nested); formatter.writeLine(`${nested.name}: SubtypeConstructor & { service: ${typeInterfaceName}Definition }`); diff --git a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts index fdeac9cd4..c80374024 100644 --- a/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts +++ b/packages/proto-loader/golden-generated/google/protobuf/FileOptions.ts @@ -29,6 +29,9 @@ export interface IFileOptions { 'ccGenericServices'?: (boolean); 'javaGenericServices'?: (boolean); 'pyGenericServices'?: (boolean); + /** + * @deprecated + */ 'javaGenerateEqualsAndHash'?: (boolean); 'deprecated'?: (boolean); 'javaStringCheckUtf8'?: (boolean); @@ -47,6 +50,9 @@ export interface OFileOptions { 'ccGenericServices': (boolean); 'javaGenericServices': (boolean); 'pyGenericServices': (boolean); + /** + * @deprecated + */ 'javaGenerateEqualsAndHash': (boolean); 'deprecated': (boolean); 'javaStringCheckUtf8': (boolean); From c4350deb4feb86deb3edef7fd15aac6472e53beb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 10 Feb 2023 09:56:50 -0800 Subject: [PATCH 440/694] grpc-js-xds: Pass along outlier detection config from CDS to child policy --- packages/grpc-js-xds/src/load-balancer-cds.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 84d8de25f..4c1c576f3 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -158,7 +158,8 @@ function generateDiscoveryMechanismForCluster(config: Cluster__Output): Discover lrs_load_reporting_server_name: config.lrs_server?.self ? '' : undefined, max_concurrent_requests: maxConcurrentRequests, type: 'EDS', - eds_service_name: config.eds_cluster_config!.service_name === '' ? undefined : config.eds_cluster_config!.service_name + eds_service_name: config.eds_cluster_config!.service_name === '' ? undefined : config.eds_cluster_config!.service_name, + outlier_detection: translateOutlierDetectionConfig(config.outlier_detection) }; } else { // Logical DNS cluster From ad298bc7c843ee66a69a855b8762bb04a33dbf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20R=C3=B6nnkvist?= Date: Sat, 11 Feb 2023 22:06:59 +0100 Subject: [PATCH 441/694] remove possible deprecated options from type brand output --- packages/proto-loader/bin/proto-loader-gen-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 057d14f3c..f23327fca 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -189,7 +189,7 @@ const typeBrandHint = `This field is a type brand and is not populated at runtim https://github.com/grpc/grpc-node/pull/2281`; function formatTypeBrand(formatter: TextFormatter, messageType: Protobuf.Type) { - formatComment(formatter, typeBrandHint, messageType.options); + formatComment(formatter, typeBrandHint); formatter.writeLine(`__type: '${messageType.fullName}'`); } From 1c4f12181a9cc9f09292ce30f6e35ce13535318b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 13 Feb 2023 13:33:34 -0800 Subject: [PATCH 442/694] proto-loader: Bump version to 0.7.5 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 495f20059..8930d45cc 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.4", + "version": "0.7.5", "author": "Google Inc.", "contributors": [ { From 2ed8e71ba17052e3c36d37a04cbbe6a6c1f246a9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 14 Feb 2023 13:47:50 -0800 Subject: [PATCH 443/694] grpc-js: Propagate keepalive throttling throughout channel --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/internal-channel.ts | 53 +++++++++++++++++-- .../src/load-balancer-outlier-detection.ts | 8 +-- packages/grpc-js/src/subchannel-interface.ts | 7 ++- packages/grpc-js/src/subchannel.ts | 22 +++++--- packages/grpc-js/src/transport.ts | 7 ++- 6 files changed, 81 insertions(+), 18 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 290d80b75..aafd2803a 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.8", + "version": "1.8.9", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 9137be272..8b4536ae5 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -51,6 +51,7 @@ import { ResolvingCall } from './resolving-call'; import { getNextCallNumber } from './call-number'; import { restrictControlPlaneStatusCode } from './control-plane-status'; import { MessageBufferTracker, RetryingCall, RetryThrottler } from './retrying-call'; +import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from './subchannel-interface'; /** * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args @@ -84,6 +85,33 @@ const RETRY_THROTTLER_MAP: Map = new Map(); const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1<<24; // 16 MB const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1<<20; // 1 MB +class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { + private stateListeners: ConnectivityStateListener[] = []; + private refCount = 0; + constructor(childSubchannel: SubchannelInterface, private channel: InternalChannel) { + super(childSubchannel); + childSubchannel.addConnectivityStateListener((subchannel, previousState, newState, keepaliveTime) => { + channel.throttleKeepalive(keepaliveTime); + for (const listener of this.stateListeners) { + listener(this, previousState, newState, keepaliveTime); + } + }); + } + + ref(): void { + this.child.ref(); + this.refCount += 1; + } + + unref(): void { + this.child.unref(); + this.refCount -= 1; + if (this.refCount <= 0) { + this.channel.removeWrappedSubchannel(this); + } + } +} + export class InternalChannel { private resolvingLoadBalancer: ResolvingLoadBalancer; @@ -116,8 +144,10 @@ export class InternalChannel { * configSelector becomes set or the channel state becomes anything other * than TRANSIENT_FAILURE. */ - private currentResolutionError: StatusObject | null = null; - private retryBufferTracker: MessageBufferTracker; + private currentResolutionError: StatusObject | null = null; + private retryBufferTracker: MessageBufferTracker; + private keepaliveTime: number; + private wrappedSubchannels: Set = new Set(); // Channelz info private readonly channelzEnabled: boolean = true; @@ -190,6 +220,7 @@ export class InternalChannel { options['grpc.retry_buffer_size'] ?? DEFAULT_RETRY_BUFFER_SIZE_BYTES, options['grpc.per_rpc_retry_buffer_size'] ?? DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES ); + this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; const channelControlHelper: ChannelControlHelper = { createSubchannel: ( subchannelAddress: SubchannelAddress, @@ -201,10 +232,13 @@ export class InternalChannel { Object.assign({}, this.options, subchannelArgs), this.credentials ); + subchannel.throttleKeepalive(this.keepaliveTime); if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); } - return subchannel; + const wrappedSubchannel = new ChannelSubchannelWrapper(subchannel, this); + this.wrappedSubchannels.add(wrappedSubchannel); + return wrappedSubchannel; }, updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.currentPicker = picker; @@ -369,6 +403,19 @@ export class InternalChannel { } } + throttleKeepalive(newKeepaliveTime: number) { + if (newKeepaliveTime > this.keepaliveTime) { + this.keepaliveTime = newKeepaliveTime; + for (const wrappedSubchannel of this.wrappedSubchannels) { + wrappedSubchannel.throttleKeepalive(newKeepaliveTime); + } + } + } + + removeWrappedSubchannel(wrappedSubchannel: ChannelSubchannelWrapper) { + this.wrappedSubchannels.delete(wrappedSubchannel); + } + doPick(metadata: Metadata, extraPickInfo: {[key: string]: string}) { return this.currentPicker.pick({metadata: metadata, extraPickInfo: extraPickInfo}); } diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index cdf580523..2f72a9625 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -205,11 +205,11 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements constructor(childSubchannel: SubchannelInterface, private mapEntry?: MapEntry) { super(childSubchannel); this.childSubchannelState = childSubchannel.getConnectivityState(); - childSubchannel.addConnectivityStateListener((subchannel, previousState, newState) => { + childSubchannel.addConnectivityStateListener((subchannel, previousState, newState, keepaliveTime) => { this.childSubchannelState = newState; if (!this.ejected) { for (const listener of this.stateListeners) { - listener(this, previousState, newState); + listener(this, previousState, newState, keepaliveTime); } } }); @@ -265,14 +265,14 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements eject() { this.ejected = true; for (const listener of this.stateListeners) { - listener(this, this.childSubchannelState, ConnectivityState.TRANSIENT_FAILURE); + listener(this, this.childSubchannelState, ConnectivityState.TRANSIENT_FAILURE, -1); } } uneject() { this.ejected = false; for (const listener of this.stateListeners) { - listener(this, ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState); + listener(this, ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState, -1); } } diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts index 082a8b3c0..165ebc3e1 100644 --- a/packages/grpc-js/src/subchannel-interface.ts +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -22,7 +22,8 @@ import { Subchannel } from "./subchannel"; export type ConnectivityStateListener = ( subchannel: SubchannelInterface, previousState: ConnectivityState, - newState: ConnectivityState + newState: ConnectivityState, + keepaliveTime: number ) => void; /** @@ -40,6 +41,7 @@ export interface SubchannelInterface { removeConnectivityStateListener(listener: ConnectivityStateListener): void; startConnecting(): void; getAddress(): string; + throttleKeepalive(newKeepaliveTime: number): void; ref(): void; unref(): void; getChannelzRef(): SubchannelRef; @@ -67,6 +69,9 @@ export abstract class BaseSubchannelWrapper implements SubchannelInterface { getAddress(): string { return this.child.getAddress(); } + throttleKeepalive(newKeepaliveTime: number): void { + this.child.throttleKeepalive(newKeepaliveTime); + } ref(): void { this.child.ref(); } diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index b4876f178..c93e0c451 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -64,7 +64,7 @@ export class Subchannel { private backoffTimeout: BackoffTimeout; - private keepaliveTimeMultiplier = 1; + private keepaliveTime: number; /** * Tracks channels and subchannel pools with references to this subchannel */ @@ -111,6 +111,8 @@ export class Subchannel { }, backoffOptions); this.subchannelAddressString = subchannelAddressToString(subchannelAddress); + this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; + if (options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; } @@ -169,7 +171,7 @@ export class Subchannel { private startConnectingInternal() { let options = this.options; if (options['grpc.keepalive_time_ms']) { - const adjustedKeepaliveTime = Math.min(options['grpc.keepalive_time_ms'] * this.keepaliveTimeMultiplier, KEEPALIVE_MAX_TIME_MS); + const adjustedKeepaliveTime = Math.min(this.keepaliveTime, KEEPALIVE_MAX_TIME_MS); options = {...options, 'grpc.keepalive_time_ms': adjustedKeepaliveTime}; } this.connector.connect(this.subchannelAddress, this.credentials, options).then( @@ -181,14 +183,14 @@ export class Subchannel { } transport.addDisconnectListener((tooManyPings) => { this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); - if (tooManyPings) { - this.keepaliveTimeMultiplier *= 2; + if (tooManyPings && this.keepaliveTime > 0) { + this.keepaliveTime *= 2; logging.log( LogVerbosity.ERROR, `Connection to ${uriToString(this.channelTarget)} at ${ this.subchannelAddressString - } rejected by server because of excess pings. Increasing ping interval multiplier to ${ - this.keepaliveTimeMultiplier + } rejected by server because of excess pings. Increasing ping interval to ${ + this.keepaliveTime } ms` ); } @@ -262,7 +264,7 @@ export class Subchannel { /* We use a shallow copy of the stateListeners array in case a listener * is removed during this iteration */ for (const listener of [...this.stateListeners]) { - listener(this, previousState, newState); + listener(this, previousState, newState, this.keepaliveTime); } return true; } @@ -403,4 +405,10 @@ export class Subchannel { getRealSubchannel(): this { return this; } + + throttleKeepalive(newKeepaliveTime: number) { + if (newKeepaliveTime > this.keepaliveTime) { + this.keepaliveTime = newKeepaliveTime; + } + } } diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index a9308471b..3c9163d13 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -77,7 +77,7 @@ class Http2Transport implements Transport { /** * The amount of time in between sending pings */ - private keepaliveTimeMs: number = KEEPALIVE_MAX_TIME_MS; + private keepaliveTimeMs: number = -1; /** * The amount of time to wait for an acknowledgement after sending a ping */ @@ -133,7 +133,7 @@ class Http2Transport implements Transport { ] .filter((e) => e) .join(' '); // remove falsey values first - + if ('grpc.keepalive_time_ms' in options) { this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!; } @@ -334,6 +334,9 @@ class Http2Transport implements Transport { } private startKeepalivePings() { + if (this.keepaliveTimeMs < 0) { + return; + } this.keepaliveIntervalId = setInterval(() => { this.sendPing(); }, this.keepaliveTimeMs); From f3c43542f876537e94b6084231a170ab0a110857 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 15 Feb 2023 14:45:31 -0800 Subject: [PATCH 444/694] grpc-js-xds: interop: log server events --- packages/grpc-js-xds/interop/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 58a9a98f4..5d0660aa3 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -33,6 +33,6 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ ENV GRPC_VERBOSITY="DEBUG" -ENV GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_cluster_resolver,xds_cluster_impl,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection +ENV GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_cluster_resolver,xds_cluster_impl,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection,server,server_call ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] From 6862af2350cceed9b39ebda1b03020c9353ec03e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 21 Feb 2023 15:26:09 -0800 Subject: [PATCH 445/694] grpc-js: Fix bugs in pick first LB policy and channel subchannel wrapper --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/internal-channel.ts | 11 +++++------ packages/grpc-js/src/load-balancer-pick-first.ts | 5 +++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index aafd2803a..0ae2d6742 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.9", + "version": "1.8.10", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 4f4c879fc..14038bd3f 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -86,16 +86,14 @@ const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1<<24; // 16 MB const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1<<20; // 1 MB class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { - private stateListeners: ConnectivityStateListener[] = []; private refCount = 0; + private subchannelStateListener: ConnectivityStateListener; constructor(childSubchannel: SubchannelInterface, private channel: InternalChannel) { super(childSubchannel); - childSubchannel.addConnectivityStateListener((subchannel, previousState, newState, keepaliveTime) => { + this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime) => { channel.throttleKeepalive(keepaliveTime); - for (const listener of this.stateListeners) { - listener(this, previousState, newState, keepaliveTime); - } - }); + }; + childSubchannel.addConnectivityStateListener(this.subchannelStateListener); } ref(): void { @@ -107,6 +105,7 @@ class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements Subchann this.child.unref(); this.refCount -= 1; if (this.refCount <= 0) { + this.child.removeConnectivityStateListener(this.subchannelStateListener); this.channel.removeWrappedSubchannel(this); } } diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 027eae074..1f6530e44 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -33,6 +33,7 @@ import { } from './picker'; import { SubchannelAddress, + subchannelAddressEqual, subchannelAddressToString, } from './subchannel-address'; import * as logging from './logging'; @@ -168,7 +169,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { * connecting to the next one instead of waiting for the connection * delay timer. */ if ( - subchannel === this.subchannels[this.currentSubchannelIndex] && + subchannel.getRealSubchannel() === this.subchannels[this.currentSubchannelIndex].getRealSubchannel() && newState === ConnectivityState.TRANSIENT_FAILURE ) { this.startNextSubchannelConnecting(); @@ -420,7 +421,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { if ( this.subchannels.length === 0 || !this.latestAddressList.every( - (value, index) => addressList[index] === value + (value, index) => subchannelAddressEqual(addressList[index], value) ) ) { this.latestAddressList = addressList; From 31aec874dd39c81bd07575d43a8efbcc5692ac51 Mon Sep 17 00:00:00 2001 From: install <1994052+install@users.noreply.github.com> Date: Wed, 22 Feb 2023 11:57:26 -0500 Subject: [PATCH 446/694] proto-loader-gen-types Narrow field Long check - Explicitly list the primitive field types that use Long, instead of searching for `64` in the type name. --- packages/proto-loader/bin/proto-loader-gen-types.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index f23327fca..0b899476d 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -414,6 +414,8 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob const childTypes = getChildMessagesAndEnums(messageType); formatter.writeLine(`// Original file: ${(messageType.filename ?? 'null')?.replace(/\\/g, '/')}`); formatter.writeLine(''); + const isLongField = (field: Protobuf.Field) => + ['int64', 'uint64', 'sint64', 'fixed64', 'sfixed64'].includes(field.type); messageType.fieldsArray.sort((fieldA, fieldB) => fieldA.id - fieldB.id); for (const field of messageType.fieldsArray) { if (field.resolvedType && childTypes.indexOf(field.resolvedType) < 0) { @@ -424,7 +426,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob seenDeps.add(dependency.fullName); formatter.writeLine(getImportLine(dependency, messageType, options)); } - if (field.type.indexOf('64') >= 0) { + if (isLongField(field)) { usesLong = true; } } @@ -439,7 +441,7 @@ function generateMessageInterfaces(formatter: TextFormatter, messageType: Protob seenDeps.add(dependency.fullName); formatter.writeLine(getImportLine(dependency, messageType, options)); } - if (field.type.indexOf('64') >= 0) { + if (isLongField(field)) { usesLong = true; } } From 1f14d1c138b9e6297ee097264185b24498f3059b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 23 Feb 2023 17:49:03 -0800 Subject: [PATCH 447/694] grpc-js: Stop leaking freed message buffer placeholder objects --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/retrying-call.ts | 61 +++++++++++++++------------ 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 0ae2d6742..f75f780db 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.10", + "version": "1.8.11", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index a9424373e..5ae585b9e 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -151,6 +151,12 @@ export class RetryingCall implements Call { private initialMetadata: Metadata | null = null; private underlyingCalls: UnderlyingCall[] = []; private writeBuffer: WriteBufferEntry[] = []; + /** + * The offset of message indices in the writeBuffer. For example, if + * writeBufferOffset is 10, message 10 is in writeBuffer[0] and message 15 + * is in writeBuffer[5]. + */ + private writeBufferOffset = 0; /** * Tracks whether a read has been started, so that we know whether to start * reads on new child calls. This only matters for the first read, because @@ -203,14 +209,8 @@ export class RetryingCall implements Call { private reportStatus(statusObject: StatusObject) { this.trace('ended with status: code=' + statusObject.code + ' details="' + statusObject.details + '"'); this.bufferTracker.freeAll(this.callNumber); - for (let i = 0; i < this.writeBuffer.length; i++) { - if (this.writeBuffer[i].entryType === 'MESSAGE') { - this.writeBuffer[i] = { - entryType: 'FREED', - allocated: false - }; - } - } + this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length; + this.writeBuffer = []; process.nextTick(() => { // Explicitly construct status object to remove progress field this.listener?.onReceiveStatus({ @@ -236,20 +236,27 @@ export class RetryingCall implements Call { } } - private maybefreeMessageBufferEntry(messageIndex: number) { + private getBufferEntry(messageIndex: number): WriteBufferEntry { + return this.writeBuffer[messageIndex - this.writeBufferOffset] ?? {entryType: 'FREED', allocated: false}; + } + + private getNextBufferIndex() { + return this.writeBufferOffset + this.writeBuffer.length; + } + + private clearSentMessages() { if (this.state !== 'COMMITTED') { return; } - const bufferEntry = this.writeBuffer[messageIndex]; - if (bufferEntry.entryType === 'MESSAGE') { + const earliestNeededMessageIndex = this.underlyingCalls[this.committedCallIndex!].nextMessageToSend; + for (let messageIndex = this.writeBufferOffset; messageIndex < earliestNeededMessageIndex; messageIndex++) { + const bufferEntry = this.getBufferEntry(messageIndex); if (bufferEntry.allocated) { this.bufferTracker.free(bufferEntry.message!.message.length, this.callNumber); } - this.writeBuffer[messageIndex] = { - entryType: 'FREED', - allocated: false - }; } + this.writeBuffer = this.writeBuffer.slice(earliestNeededMessageIndex - this.writeBufferOffset); + this.writeBufferOffset = earliestNeededMessageIndex; } private commitCall(index: number) { @@ -272,9 +279,7 @@ export class RetryingCall implements Call { this.underlyingCalls[i].state = 'COMPLETED'; this.underlyingCalls[i].call.cancelWithStatus(Status.CANCELLED, 'Discarded in favor of other hedged attempt'); } - for (let messageIndex = 0; messageIndex < this.underlyingCalls[index].nextMessageToSend - 1; messageIndex += 1) { - this.maybefreeMessageBufferEntry(messageIndex); - } + this.clearSentMessages(); } private commitCallWithMostMessages() { @@ -555,8 +560,8 @@ export class RetryingCall implements Call { private handleChildWriteCompleted(childIndex: number) { const childCall = this.underlyingCalls[childIndex]; const messageIndex = childCall.nextMessageToSend; - this.writeBuffer[messageIndex].callback?.(); - this.maybefreeMessageBufferEntry(messageIndex); + this.getBufferEntry(messageIndex).callback?.(); + this.clearSentMessages(); childCall.nextMessageToSend += 1; this.sendNextChildMessage(childIndex); } @@ -566,10 +571,10 @@ export class RetryingCall implements Call { if (childCall.state === 'COMPLETED') { return; } - if (this.writeBuffer[childCall.nextMessageToSend]) { - const bufferEntry = this.writeBuffer[childCall.nextMessageToSend]; + if (this.getBufferEntry(childCall.nextMessageToSend)) { + const bufferEntry = this.getBufferEntry(childCall.nextMessageToSend); switch (bufferEntry.entryType) { - case 'MESSAGE': + case 'MESSAGE': childCall.call.sendMessageWithContext({ callback: (error) => { // Ignore error @@ -594,13 +599,13 @@ export class RetryingCall implements Call { message, flags: context.flags, }; - const messageIndex = this.writeBuffer.length; + const messageIndex = this.getNextBufferIndex(); const bufferEntry: WriteBufferEntry = { entryType: 'MESSAGE', message: writeObj, allocated: this.bufferTracker.allocate(message.length, this.callNumber) }; - this.writeBuffer[messageIndex] = bufferEntry; + this.writeBuffer.push(bufferEntry); if (bufferEntry.allocated) { context.callback?.(); for (const [callIndex, call] of this.underlyingCalls.entries()) { @@ -642,11 +647,11 @@ export class RetryingCall implements Call { } halfClose(): void { this.trace('halfClose called'); - const halfCloseIndex = this.writeBuffer.length; - this.writeBuffer[halfCloseIndex] = { + const halfCloseIndex = this.getNextBufferIndex(); + this.writeBuffer.push({ entryType: 'HALF_CLOSE', allocated: false - }; + }); for (const call of this.underlyingCalls) { if (call?.state === 'ACTIVE' && call.nextMessageToSend === halfCloseIndex) { call.nextMessageToSend += 1; From 865731b4c54cdf50739eafbea4cda6c998f4cccb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 24 Feb 2023 09:55:45 -0800 Subject: [PATCH 448/694] grpc-js-xds: Use simpler search algorithm in weighted target picker --- .../src/load-balancer-weighted-target.ts | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts index 5d7cfa04e..7cd92d98b 100644 --- a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -119,31 +119,16 @@ class WeightedTargetPicker implements Picker { pick(pickArgs: PickArgs): PickResult { // num | 0 is equivalent to floor(num) const selection = (Math.random() * this.rangeTotal) | 0; - - /* Binary search for the element of the list such that - * pickerList[index - 1].rangeEnd <= selection < pickerList[index].rangeEnd - */ - let mid = 0; - let startIndex = 0; - let endIndex = this.pickerList.length - 1; - let index = 0; - while (endIndex > startIndex) { - mid = ((startIndex + endIndex) / 2) | 0; - if (this.pickerList[mid].rangeEnd > selection) { - endIndex = mid; - } else if (this.pickerList[mid].rangeEnd < selection) { - startIndex = mid + 1; - } else { - // + 1 here because the range is exclusive at the top end - index = mid + 1; - break; + + for (const entry of this.pickerList) { + if (selection < entry.rangeEnd) { + return entry.picker.pick(pickArgs); } } - if (index === 0) { - index = startIndex; - } - return this.pickerList[index].picker.pick(pickArgs); + /* Default to first element if the iteration doesn't find anything for some + * reason. */ + return this.pickerList[0].picker.pick(pickArgs); } } From 081270f013f6e67fb69d03f38a2e780beb641409 Mon Sep 17 00:00:00 2001 From: Ulrich Van Den Hekke Date: Sun, 26 Feb 2023 13:14:32 +0100 Subject: [PATCH 449/694] grpc-js: add await/async on method that return promise add await/async on method that return promise to ensure that the order of message (and of the end of stream) are preserved --- packages/grpc-js/src/server-call.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 631ec7675..ba1292176 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -808,10 +808,10 @@ export class Http2ServerCallStream< let pushedEnd = false; - const maybePushEnd = () => { + const maybePushEnd = async () => { if (!pushedEnd && readsDone && !pendingMessageProcessing) { pushedEnd = true; - this.pushOrBufferMessage(readable, null); + await this.pushOrBufferMessage(readable, null); } }; @@ -844,16 +844,16 @@ export class Http2ServerCallStream< // Just return early if (!decompressedMessage) return; - this.pushOrBufferMessage(readable, decompressedMessage); + await this.pushOrBufferMessage(readable, decompressedMessage); } pendingMessageProcessing = false; this.stream.resume(); - maybePushEnd(); + await maybePushEnd(); }); - this.stream.once('end', () => { + this.stream.once('end', async () => { readsDone = true; - maybePushEnd(); + await maybePushEnd(); }); } @@ -877,16 +877,16 @@ export class Http2ServerCallStream< return this.canPush; } - private pushOrBufferMessage( + private async pushOrBufferMessage( readable: | ServerReadableStream | ServerDuplexStream, messageBytes: Buffer | null - ): void { + ): Promise { if (this.isPushPending) { this.bufferedMessages.push(messageBytes); } else { - this.pushMessage(readable, messageBytes); + await this.pushMessage(readable, messageBytes); } } @@ -939,7 +939,7 @@ export class Http2ServerCallStream< this.isPushPending = false; if (this.bufferedMessages.length > 0) { - this.pushMessage( + await this.pushMessage( readable, this.bufferedMessages.shift() as Buffer | null ); From 0726fdf290116faaae9c1aea36c30061983c284d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 6 Mar 2023 10:11:46 -0800 Subject: [PATCH 450/694] grpc-js: Fix address equality check in pick-first --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/load-balancer-pick-first.ts | 3 ++- packages/grpc-js/src/subchannel-address.ts | 10 ++++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index f75f780db..722f9d866 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.11", + "version": "1.8.12", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 1f6530e44..a501b1f7d 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -420,8 +420,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { * address list is different from the existing one */ if ( this.subchannels.length === 0 || + this.latestAddressList.length !== addressList.length || !this.latestAddressList.every( - (value, index) => subchannelAddressEqual(addressList[index], value) + (value, index) => addressList[index] && subchannelAddressEqual(addressList[index], value) ) ) { this.latestAddressList = addressList; diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts index 29022caa5..e542e645e 100644 --- a/packages/grpc-js/src/subchannel-address.ts +++ b/packages/grpc-js/src/subchannel-address.ts @@ -41,9 +41,15 @@ export function isTcpSubchannelAddress( } export function subchannelAddressEqual( - address1: SubchannelAddress, - address2: SubchannelAddress + address1?: SubchannelAddress, + address2?: SubchannelAddress ): boolean { + if (!address1 && !address2) { + return true; + } + if (!address1 || !address2) { + return false; + } if (isTcpSubchannelAddress(address1)) { return ( isTcpSubchannelAddress(address2) && From c23c67cd4fa4b327503b5247584ed96d078a7a97 Mon Sep 17 00:00:00 2001 From: Ulrich Van Den Hekke Date: Sun, 26 Feb 2023 13:14:32 +0100 Subject: [PATCH 451/694] grpc-js: add await/async on method that return promise add await/async on method that return promise to ensure that the order of message (and of the end of stream) are preserved --- packages/grpc-js/src/server-call.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 8dcf6be33..48186bc29 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -812,10 +812,10 @@ export class Http2ServerCallStream< let pushedEnd = false; - const maybePushEnd = () => { + const maybePushEnd = async () => { if (!pushedEnd && readsDone && !pendingMessageProcessing) { pushedEnd = true; - this.pushOrBufferMessage(readable, null); + await this.pushOrBufferMessage(readable, null); } }; @@ -848,16 +848,16 @@ export class Http2ServerCallStream< // Just return early if (!decompressedMessage) return; - this.pushOrBufferMessage(readable, decompressedMessage); + await this.pushOrBufferMessage(readable, decompressedMessage); } pendingMessageProcessing = false; this.stream.resume(); - maybePushEnd(); + await maybePushEnd(); }); - this.stream.once('end', () => { + this.stream.once('end', async () => { readsDone = true; - maybePushEnd(); + await maybePushEnd(); }); } @@ -881,16 +881,16 @@ export class Http2ServerCallStream< return this.canPush; } - private pushOrBufferMessage( + private async pushOrBufferMessage( readable: | ServerReadableStream | ServerDuplexStream, messageBytes: Buffer | null - ): void { + ): Promise { if (this.isPushPending) { this.bufferedMessages.push(messageBytes); } else { - this.pushMessage(readable, messageBytes); + await this.pushMessage(readable, messageBytes); } } @@ -943,7 +943,7 @@ export class Http2ServerCallStream< this.isPushPending = false; if (this.bufferedMessages.length > 0) { - this.pushMessage( + await this.pushMessage( readable, this.bufferedMessages.shift() as Buffer | null ); From c525025f06647d022d5201a08fc179eff1b6bcc1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 6 Mar 2023 15:10:29 -0800 Subject: [PATCH 452/694] grpc-js: Trace before call to LB policy picker --- packages/grpc-js/src/load-balancing-call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 48aaf48ac..f74933983 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -102,6 +102,7 @@ export class LoadBalancingCall implements Call { if (!this.metadata) { throw new Error('doPick called before start'); } + this.trace('Pick called') const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation); const subchannelString = pickResult.subchannel ? '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : From d78d6d3b64fec9fb1cee872747238a779c02c141 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Mar 2023 13:10:46 -0800 Subject: [PATCH 453/694] proto-loader: Bump to 0.7.6 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 8930d45cc..b39e0204d 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.5", + "version": "0.7.6", "author": "Google Inc.", "contributors": [ { From 79161816e6b76d5fea787d8329f038d00fb156c5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Mar 2023 14:58:58 -0800 Subject: [PATCH 454/694] grpc-js: Add more logging to trace handling of received messages --- packages/grpc-js/src/resolving-call.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index b6398126c..f29fb7fd7 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -178,13 +178,17 @@ export class ResolvingCall implements Call { this.filterStack = this.filterStackFactory.createFilter(); this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then(filteredMetadata => { this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); + this.trace('Created child [' + this.child.getCallNumber() + ']') this.child.start(filteredMetadata, { onReceiveMetadata: metadata => { + this.trace('Received metadata') this.listener!.onReceiveMetadata(this.filterStack!.receiveMetadata(metadata)); }, onReceiveMessage: message => { + this.trace('Received message'); this.readFilterPending = true; this.filterStack!.receiveMessage(message).then(filteredMesssage => { + this.trace('Finished filtering received message'); this.readFilterPending = false; this.listener!.onReceiveMessage(filteredMesssage); if (this.pendingChildStatus) { @@ -195,6 +199,7 @@ export class ResolvingCall implements Call { }); }, onReceiveStatus: status => { + this.trace('Received status'); if (this.readFilterPending) { this.pendingChildStatus = status; } else { From 481f704c775d78de363a7311a28b087c124c97c0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Mar 2023 16:37:04 -0800 Subject: [PATCH 455/694] grpc-js-xds: Populate Node message field user_agent_version --- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/xds-client.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index c0c3200f5..0d8080f96 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.8.0", + "version": "1.8.1", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 2dfa41236..bd4bd84cb 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -334,11 +334,13 @@ export class XdsClient { this.adsNode = { ...bootstrapInfo.node, user_agent_name: userAgentName, + user_agent_version: clientVersion, client_features: ['envoy.lb.does_not_support_overprovisioning'], }; this.lrsNode = { ...bootstrapInfo.node, user_agent_name: userAgentName, + user_agent_version: clientVersion, client_features: ['envoy.lrs.supports_send_all_clusters'], }; setCsdsClientNode(this.adsNode); From 6bc6b8665bef8e158121fc5ce5608dd2da748bb1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 25 Jan 2023 09:50:49 -0800 Subject: [PATCH 456/694] grpc-js-xds: Add unit test framework --- packages/grpc-js-xds/package.json | 3 +- .../grpc-js-xds/proto/grpc/testing/echo.proto | 70 ++++ .../proto/grpc/testing/echo_messages.proto | 74 ++++ .../proto/grpc/testing/simple_messages.proto | 26 ++ .../testing/xds/v3/orca_load_report.proto | 44 +++ packages/grpc-js-xds/test/backend.ts | 105 ++++++ packages/grpc-js-xds/test/client.ts | 72 ++++ packages/grpc-js-xds/test/framework.ts | 208 +++++++++++ packages/grpc-js-xds/test/generated/echo.ts | 46 +++ .../test/generated/grpc/testing/DebugInfo.ts | 18 + .../generated/grpc/testing/EchoRequest.ts | 13 + .../generated/grpc/testing/EchoResponse.ts | 13 + .../grpc/testing/EchoTest1Service.ts | 117 ++++++ .../grpc/testing/EchoTest2Service.ts | 117 ++++++ .../generated/grpc/testing/EchoTestService.ts | 150 ++++++++ .../generated/grpc/testing/ErrorStatus.ts | 20 + .../generated/grpc/testing/NoRpcService.ts | 19 + .../generated/grpc/testing/RequestParams.ts | 75 ++++ .../generated/grpc/testing/ResponseParams.ts | 15 + .../generated/grpc/testing/SimpleRequest.ts | 8 + .../generated/grpc/testing/SimpleResponse.ts | 8 + .../generated/grpc/testing/StringValue.ts | 10 + .../grpc/testing/UnimplementedEchoService.ts | 27 ++ .../xds/data/orca/v3/OrcaLoadReport.ts | 59 +++ packages/grpc-js-xds/test/test-core.ts | 63 ++++ packages/grpc-js-xds/test/xds-server.ts | 342 ++++++++++++++++++ 26 files changed, 1721 insertions(+), 1 deletion(-) create mode 100644 packages/grpc-js-xds/proto/grpc/testing/echo.proto create mode 100644 packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto create mode 100644 packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto create mode 100644 packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto create mode 100644 packages/grpc-js-xds/test/backend.ts create mode 100644 packages/grpc-js-xds/test/client.ts create mode 100644 packages/grpc-js-xds/test/framework.ts create mode 100644 packages/grpc-js-xds/test/generated/echo.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts create mode 100644 packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts create mode 100644 packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts create mode 100644 packages/grpc-js-xds/test/test-core.ts create mode 100644 packages/grpc-js-xds/test/xds-server.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index c0c3200f5..bd1511e5a 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -13,7 +13,8 @@ "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", - "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-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" }, "repository": { "type": "git", diff --git a/packages/grpc-js-xds/proto/grpc/testing/echo.proto b/packages/grpc-js-xds/proto/grpc/testing/echo.proto new file mode 100644 index 000000000..7f444b43f --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/echo.proto @@ -0,0 +1,70 @@ + +// Copyright 2015 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. + +syntax = "proto3"; + +package grpc.testing; + +import "grpc/testing/echo_messages.proto"; +import "grpc/testing/simple_messages.proto"; + +service EchoTestService { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + rpc CheckDeadlineUpperBound(SimpleRequest) returns (StringValue); + rpc CheckDeadlineSet(SimpleRequest) returns (StringValue); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); + rpc UnimplementedBidi(stream EchoRequest) returns (stream EchoResponse); +} + +service EchoTest1Service { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +service EchoTest2Service { + rpc Echo(EchoRequest) returns (EchoResponse); + rpc Echo1(EchoRequest) returns (EchoResponse); + rpc Echo2(EchoRequest) returns (EchoResponse); + // A service which checks that the initial metadata sent over contains some + // expected key value pair + rpc CheckClientInitialMetadata(SimpleRequest) returns (SimpleResponse); + rpc RequestStream(stream EchoRequest) returns (EchoResponse); + rpc ResponseStream(EchoRequest) returns (stream EchoResponse); + rpc BidiStream(stream EchoRequest) returns (stream EchoResponse); + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +service UnimplementedEchoService { + rpc Unimplemented(EchoRequest) returns (EchoResponse); +} + +// A service without any rpc defined to test coverage. +service NoRpcService {} diff --git a/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto b/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto new file mode 100644 index 000000000..44f22133e --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/echo_messages.proto @@ -0,0 +1,74 @@ + +// Copyright 2015 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. + +syntax = "proto3"; + +package grpc.testing; + +option cc_enable_arenas = true; + +import "grpc/testing/xds/v3/orca_load_report.proto"; + +// Message to be echoed back serialized in trailer. +message DebugInfo { + repeated string stack_entries = 1; + string detail = 2; +} + +// Error status client expects to see. +message ErrorStatus { + int32 code = 1; + string error_message = 2; + string binary_error_details = 3; +} + +message RequestParams { + bool echo_deadline = 1; + int32 client_cancel_after_us = 2; + int32 server_cancel_after_us = 3; + bool echo_metadata = 4; + bool check_auth_context = 5; + int32 response_message_length = 6; + bool echo_peer = 7; + string expected_client_identity = 8; // will force check_auth_context. + bool skip_cancelled_check = 9; + string expected_transport_security_type = 10; + DebugInfo debug_info = 11; + bool server_die = 12; // Server should not see a request with this set. + string binary_error_details = 13; + ErrorStatus expected_error = 14; + int32 server_sleep_us = 15; // sleep when invoking server for deadline tests + int32 backend_channel_idx = 16; // which backend to send request to + bool echo_metadata_initially = 17; + bool server_notify_client_when_started = 18; + xds.data.orca.v3.OrcaLoadReport backend_metrics = 19; + bool echo_host_from_authority_header = 20; +} + +message EchoRequest { + string message = 1; + RequestParams param = 2; +} + +message ResponseParams { + int64 request_deadline = 1; + string host = 2; + string peer = 3; +} + +message EchoResponse { + string message = 1; + ResponseParams param = 2; +} diff --git a/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto b/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto new file mode 100644 index 000000000..3afe236b4 --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/simple_messages.proto @@ -0,0 +1,26 @@ + +// Copyright 2018 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. + +syntax = "proto3"; + +package grpc.testing; + +message SimpleRequest {} + +message SimpleResponse {} + +message StringValue { + string message = 1; +} diff --git a/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto b/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto new file mode 100644 index 000000000..033e64ba4 --- /dev/null +++ b/packages/grpc-js-xds/proto/grpc/testing/xds/v3/orca_load_report.proto @@ -0,0 +1,44 @@ +// Copyright 2020 The 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. + +// Local copy of Envoy xDS proto file, used for testing only. + +syntax = "proto3"; + +package xds.data.orca.v3; + +// See section `ORCA load report format` of the design document in +// :ref:`https://github.com/envoyproxy/envoy/issues/6614`. + +message OrcaLoadReport { + // CPU utilization expressed as a fraction of available CPU resources. This + // should be derived from the latest sample or measurement. + double cpu_utilization = 1; + + // Memory utilization expressed as a fraction of available memory + // resources. This should be derived from the latest sample or measurement. + double mem_utilization = 2; + + // Total RPS being served by an endpoint. This should cover all services that an endpoint is + // responsible for. + uint64 rps = 3; + + // Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + // storage) associated with the request. + map request_cost = 4; + + // Resource utilization values. Each value is expressed as a fraction of total resources + // available, derived from the latest sample or measurement. + map utilization = 5; +} diff --git a/packages/grpc-js-xds/test/backend.ts b/packages/grpc-js-xds/test/backend.ts new file mode 100644 index 000000000..ce509c556 --- /dev/null +++ b/packages/grpc-js-xds/test/backend.ts @@ -0,0 +1,105 @@ +/* + * 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. + * + */ + +import { loadPackageDefinition, sendUnaryData, Server, ServerCredentials, ServerUnaryCall, UntypedServiceImplementation } from "@grpc/grpc-js"; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType } from "./generated/echo"; +import { EchoRequest__Output } from "./generated/grpc/testing/EchoRequest"; +import { EchoResponse } from "./generated/grpc/testing/EchoResponse"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + 'grpc/testing/echo.proto' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to build/test + __dirname + '/../../proto/' + ], + })) as unknown as ProtoGrpcType; + +export class Backend { + private server: Server; + private receivedCallCount = 0; + private callListeners: (() => void)[] = []; + private port: number | null = null; + constructor() { + this.server = new Server(); + this.server.addService(loadedProtos.grpc.testing.EchoTestService.service, this as unknown as UntypedServiceImplementation); + } + Echo(call: ServerUnaryCall, callback: sendUnaryData) { + // call.request.params is currently ignored + this.addCall(); + callback(null, {message: call.request.message}); + } + + addCall() { + this.receivedCallCount++; + this.callListeners.forEach(listener => listener()); + } + + onCall(listener: () => void) { + this.callListeners.push(listener); + } + + start(callback: (error: Error | null, port: number) => void) { + this.server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (error, port) => { + if (!error) { + this.port = port; + this.server.start(); + } + callback(error, port); + }) + } + + startAsync(): Promise { + return new Promise((resolve, reject) => { + this.start((error, port) => { + if (error) { + reject(error); + } else { + resolve(port); + } + }); + }); + } + + getPort(): number { + if (this.port === null) { + throw new Error('Port not set. Backend not yet started.'); + } + return this.port; + } + + getCallCount() { + return this.receivedCallCount; + } + + resetCallCount() { + this.receivedCallCount = 0; + } + + shutdown(callback: (error?: Error) => void) { + this.server.tryShutdown(callback); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/client.ts b/packages/grpc-js-xds/test/client.ts new file mode 100644 index 000000000..6404a3eb2 --- /dev/null +++ b/packages/grpc-js-xds/test/client.ts @@ -0,0 +1,72 @@ +/* + * 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. + * + */ + +import { credentials, loadPackageDefinition } from "@grpc/grpc-js"; +import { loadSync } from "@grpc/proto-loader"; +import { ProtoGrpcType } from "./generated/echo"; +import { EchoTestServiceClient } from "./generated/grpc/testing/EchoTestService"; +import { XdsServer } from "./xds-server"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + 'grpc/testing/echo.proto' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to build/test + __dirname + '/../../proto/' + ], + })) as unknown as ProtoGrpcType; + +const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config'; + +export class XdsTestClient { + private client: EchoTestServiceClient; + private callInterval: NodeJS.Timer; + + constructor(targetName: string, xdsServer: XdsServer) { + this.client = new loadedProtos.grpc.testing.EchoTestService(`xds:///${targetName}`, credentials.createInsecure(), {[BOOTSTRAP_CONFIG_KEY]: xdsServer.getBootstrapInfoString()}); + this.callInterval = setInterval(() => {}, 0); + clearInterval(this.callInterval); + } + + startCalls(interval: number) { + clearInterval(this.callInterval); + this.callInterval = setInterval(() => { + this.client.echo({message: 'test'}, (error, value) => { + if (error) { + throw error; + } + }); + }, interval); + } + + stopCalls() { + clearInterval(this.callInterval); + } + + close() { + this.stopCalls(); + this.client.close(); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts new file mode 100644 index 000000000..945b08a3a --- /dev/null +++ b/packages/grpc-js-xds/test/framework.ts @@ -0,0 +1,208 @@ +/* + * 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. + * + */ + +import { ClusterLoadAssignment } from "../src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { Cluster } from "../src/generated/envoy/config/cluster/v3/Cluster"; +import { Backend } from "./backend"; +import { Locality } from "../src/generated/envoy/config/core/v3/Locality"; +import { RouteConfiguration } from "../src/generated/envoy/config/route/v3/RouteConfiguration"; +import { Route } from "../src/generated/envoy/config/route/v3/Route"; +import { Listener } from "../src/generated/envoy/config/listener/v3/Listener"; +import { HttpConnectionManager } from "../src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager"; +import { AnyExtension } from "@grpc/proto-loader"; +import { HTTP_CONNECTION_MANGER_TYPE_URL } from "../src/resources"; +import { LocalityLbEndpoints } from "../src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints"; +import { LbEndpoint } from "../src/generated/envoy/config/endpoint/v3/LbEndpoint"; + +interface Endpoint { + locality: Locality; + backends: Backend[]; + weight?: number; + priority?: number; +} + +function getLbEndpoint(backend: Backend): LbEndpoint { + return { + health_status: "HEALTHY", + endpoint: { + address: { + socket_address: { + address: '::1', + port_value: backend.getPort() + } + } + } + }; +} + +function getLocalityLbEndpoints(endpoint: Endpoint): LocalityLbEndpoints { + return { + lb_endpoints: endpoint.backends.map(getLbEndpoint), + locality: endpoint.locality, + load_balancing_weight: {value: endpoint.weight ?? 1}, + priority: endpoint.priority ?? 0 + } +} + +export class FakeCluster { + constructor(private name: string, private endpoints: Endpoint[]) {} + + getEndpointConfig(): ClusterLoadAssignment { + return { + cluster_name: this.name, + endpoints: this.endpoints.map(getLocalityLbEndpoints) + }; + } + + getClusterConfig(): Cluster { + return { + name: this.name, + type: 'EDS', + eds_cluster_config: {eds_config: {ads: {}}}, + lb_policy: 'ROUND_ROBIN' + } + } + + getName() { + return this.name; + } + + startAllBackends(): Promise { + return Promise.all(this.endpoints.map(endpoint => Promise.all(endpoint.backends.map(backend => backend.startAsync())))); + } + + private haveAllBackendsReceivedTraffic(): boolean { + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + if (backend.getCallCount() < 1) { + return false; + } + } + } + return true; + } + + waitForAllBackendsToReceiveTraffic(): Promise { + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + backend.resetCallCount(); + } + } + return new Promise((resolve, reject) => { + let finishedPromise = false; + for (const endpoint of this.endpoints) { + for (const backend of endpoint.backends) { + backend.onCall(() => { + if (finishedPromise) { + return; + } + if (this.haveAllBackendsReceivedTraffic()) { + finishedPromise = true; + resolve(); + } + }); + } + } + }); + } +} + +interface FakeRoute { + cluster?: FakeCluster; + weightedClusters?: [{cluster: FakeCluster, weight: number}]; +} + +function createRouteConfig(route: FakeRoute): Route { + if (route.cluster) { + return { + match: { + prefix: '' + }, + route: { + cluster: route.cluster.getName() + } + }; + } else { + return { + match: { + prefix: '' + }, + route: { + weighted_clusters: { + clusters: route.weightedClusters!.map(clusterWeight => ({ + name: clusterWeight.cluster.getName(), + weight: {value: clusterWeight.weight} + })) + } + } + } + } +} + +export class FakeRouteGroup { + constructor(private name: string, private routes: FakeRoute[]) {} + + getRouteConfiguration(): RouteConfiguration { + return { + name: this.name, + virtual_hosts: [{ + domains: ['*'], + routes: this.routes.map(createRouteConfig) + }] + }; + } + + getListener(): Listener { + const httpConnectionManager: HttpConnectionManager & AnyExtension = { + '@type': HTTP_CONNECTION_MANGER_TYPE_URL, + rds: { + route_config_name: this.name, + config_source: {ads: {}} + } + } + return { + name: this.name, + api_listener: { + api_listener: httpConnectionManager + } + }; + } + + startAllBackends(): Promise { + return Promise.all(this.routes.map(route => { + if (route.cluster) { + return route.cluster.startAllBackends(); + } else if (route.weightedClusters) { + return Promise.all(route.weightedClusters.map(clusterWeight => clusterWeight.cluster.startAllBackends())); + } else { + return Promise.resolve(); + } + })); + } + + waitForAllBackendsToReceiveTraffic(): Promise { + return Promise.all(this.routes.map(route => { + if (route.cluster) { + return route.cluster.waitForAllBackendsToReceiveTraffic(); + } else if (route.weightedClusters) { + return Promise.all(route.weightedClusters.map(clusterWeight => clusterWeight.cluster.waitForAllBackendsToReceiveTraffic())).then(() => {}); + } else { + return Promise.resolve(); + } + })); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/generated/echo.ts b/packages/grpc-js-xds/test/generated/echo.ts new file mode 100644 index 000000000..537a49cfa --- /dev/null +++ b/packages/grpc-js-xds/test/generated/echo.ts @@ -0,0 +1,46 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { EchoTest1ServiceClient as _grpc_testing_EchoTest1ServiceClient, EchoTest1ServiceDefinition as _grpc_testing_EchoTest1ServiceDefinition } from './grpc/testing/EchoTest1Service'; +import type { EchoTest2ServiceClient as _grpc_testing_EchoTest2ServiceClient, EchoTest2ServiceDefinition as _grpc_testing_EchoTest2ServiceDefinition } from './grpc/testing/EchoTest2Service'; +import type { EchoTestServiceClient as _grpc_testing_EchoTestServiceClient, EchoTestServiceDefinition as _grpc_testing_EchoTestServiceDefinition } from './grpc/testing/EchoTestService'; +import type { NoRpcServiceClient as _grpc_testing_NoRpcServiceClient, NoRpcServiceDefinition as _grpc_testing_NoRpcServiceDefinition } from './grpc/testing/NoRpcService'; +import type { UnimplementedEchoServiceClient as _grpc_testing_UnimplementedEchoServiceClient, UnimplementedEchoServiceDefinition as _grpc_testing_UnimplementedEchoServiceDefinition } from './grpc/testing/UnimplementedEchoService'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + grpc: { + testing: { + DebugInfo: MessageTypeDefinition + EchoRequest: MessageTypeDefinition + EchoResponse: MessageTypeDefinition + EchoTest1Service: SubtypeConstructor & { service: _grpc_testing_EchoTest1ServiceDefinition } + EchoTest2Service: SubtypeConstructor & { service: _grpc_testing_EchoTest2ServiceDefinition } + EchoTestService: SubtypeConstructor & { service: _grpc_testing_EchoTestServiceDefinition } + ErrorStatus: MessageTypeDefinition + /** + * A service without any rpc defined to test coverage. + */ + NoRpcService: SubtypeConstructor & { service: _grpc_testing_NoRpcServiceDefinition } + RequestParams: MessageTypeDefinition + ResponseParams: MessageTypeDefinition + SimpleRequest: MessageTypeDefinition + SimpleResponse: MessageTypeDefinition + StringValue: MessageTypeDefinition + UnimplementedEchoService: SubtypeConstructor & { service: _grpc_testing_UnimplementedEchoServiceDefinition } + } + } + xds: { + data: { + orca: { + v3: { + OrcaLoadReport: MessageTypeDefinition + } + } + } + } +} + diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts b/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts new file mode 100644 index 000000000..123188fe3 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/DebugInfo.ts @@ -0,0 +1,18 @@ +// Original file: proto/grpc/testing/echo_messages.proto + + +/** + * Message to be echoed back serialized in trailer. + */ +export interface DebugInfo { + 'stack_entries'?: (string)[]; + 'detail'?: (string); +} + +/** + * Message to be echoed back serialized in trailer. + */ +export interface DebugInfo__Output { + 'stack_entries': (string)[]; + 'detail': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts new file mode 100644 index 000000000..cadf04f7a --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoRequest.ts @@ -0,0 +1,13 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { RequestParams as _grpc_testing_RequestParams, RequestParams__Output as _grpc_testing_RequestParams__Output } from '../../grpc/testing/RequestParams'; + +export interface EchoRequest { + 'message'?: (string); + 'param'?: (_grpc_testing_RequestParams | null); +} + +export interface EchoRequest__Output { + 'message': (string); + 'param': (_grpc_testing_RequestParams__Output | null); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts new file mode 100644 index 000000000..d54beaf4f --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoResponse.ts @@ -0,0 +1,13 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { ResponseParams as _grpc_testing_ResponseParams, ResponseParams__Output as _grpc_testing_ResponseParams__Output } from '../../grpc/testing/ResponseParams'; + +export interface EchoResponse { + 'message'?: (string); + 'param'?: (_grpc_testing_ResponseParams | null); +} + +export interface EchoResponse__Output { + 'message': (string); + 'param': (_grpc_testing_ResponseParams__Output | null); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts new file mode 100644 index 000000000..a2b1947f6 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest1Service.ts @@ -0,0 +1,117 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; + +export interface EchoTest1ServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface EchoTest1ServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTest1ServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts new file mode 100644 index 000000000..033e70143 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTest2Service.ts @@ -0,0 +1,117 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; + +export interface EchoTest2ServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface EchoTest2ServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTest2ServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts new file mode 100644 index 000000000..d1fa2d075 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/EchoTestService.ts @@ -0,0 +1,150 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; +import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; +import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; +import type { StringValue as _grpc_testing_StringValue, StringValue__Output as _grpc_testing_StringValue__Output } from '../../grpc/testing/StringValue'; + +export interface EchoTestServiceClient extends grpc.Client { + BidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + BidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + bidiStream(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CheckClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + checkClientInitialMetadata(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineSet(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineSet(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + CheckDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + checkDeadlineUpperBound(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_StringValue__Output>): grpc.ClientUnaryCall; + + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo1(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + echo2(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + RequestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + RequestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + requestStream(callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientWritableStream<_grpc_testing_EchoRequest>; + + ResponseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + ResponseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + responseStream(argument: _grpc_testing_EchoRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_testing_EchoResponse__Output>; + + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + + UnimplementedBidi(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + UnimplementedBidi(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + unimplementedBidi(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + unimplementedBidi(options?: grpc.CallOptions): grpc.ClientDuplexStream<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse__Output>; + +} + +export interface EchoTestServiceHandlers extends grpc.UntypedServiceImplementation { + BidiStream: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + /** + * A service which checks that the initial metadata sent over contains some + * expected key value pair + */ + CheckClientInitialMetadata: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse>; + + CheckDeadlineSet: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue>; + + CheckDeadlineUpperBound: grpc.handleUnaryCall<_grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue>; + + Echo: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo1: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Echo2: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + RequestStream: grpc.handleClientStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + ResponseStream: grpc.handleServerStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + + UnimplementedBidi: grpc.handleBidiStreamingCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface EchoTestServiceDefinition extends grpc.ServiceDefinition { + BidiStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + CheckClientInitialMetadata: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + CheckDeadlineSet: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_StringValue, _grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue__Output> + CheckDeadlineUpperBound: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_StringValue, _grpc_testing_SimpleRequest__Output, _grpc_testing_StringValue__Output> + Echo: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo1: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Echo2: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + RequestStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + ResponseStream: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> + UnimplementedBidi: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts b/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts new file mode 100644 index 000000000..42ff36d9a --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/ErrorStatus.ts @@ -0,0 +1,20 @@ +// Original file: proto/grpc/testing/echo_messages.proto + + +/** + * Error status client expects to see. + */ +export interface ErrorStatus { + 'code'?: (number); + 'error_message'?: (string); + 'binary_error_details'?: (string); +} + +/** + * Error status client expects to see. + */ +export interface ErrorStatus__Output { + 'code': (number); + 'error_message': (string); + 'binary_error_details': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts new file mode 100644 index 000000000..7427c8097 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/NoRpcService.ts @@ -0,0 +1,19 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' + +/** + * A service without any rpc defined to test coverage. + */ +export interface NoRpcServiceClient extends grpc.Client { +} + +/** + * A service without any rpc defined to test coverage. + */ +export interface NoRpcServiceHandlers extends grpc.UntypedServiceImplementation { +} + +export interface NoRpcServiceDefinition extends grpc.ServiceDefinition { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts b/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts new file mode 100644 index 000000000..e8c5ef1d1 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/RequestParams.ts @@ -0,0 +1,75 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { DebugInfo as _grpc_testing_DebugInfo, DebugInfo__Output as _grpc_testing_DebugInfo__Output } from '../../grpc/testing/DebugInfo'; +import type { ErrorStatus as _grpc_testing_ErrorStatus, ErrorStatus__Output as _grpc_testing_ErrorStatus__Output } from '../../grpc/testing/ErrorStatus'; +import type { OrcaLoadReport as _xds_data_orca_v3_OrcaLoadReport, OrcaLoadReport__Output as _xds_data_orca_v3_OrcaLoadReport__Output } from '../../xds/data/orca/v3/OrcaLoadReport'; + +export interface RequestParams { + 'echo_deadline'?: (boolean); + 'client_cancel_after_us'?: (number); + 'server_cancel_after_us'?: (number); + 'echo_metadata'?: (boolean); + 'check_auth_context'?: (boolean); + 'response_message_length'?: (number); + 'echo_peer'?: (boolean); + /** + * will force check_auth_context. + */ + 'expected_client_identity'?: (string); + 'skip_cancelled_check'?: (boolean); + 'expected_transport_security_type'?: (string); + 'debug_info'?: (_grpc_testing_DebugInfo | null); + /** + * Server should not see a request with this set. + */ + 'server_die'?: (boolean); + 'binary_error_details'?: (string); + 'expected_error'?: (_grpc_testing_ErrorStatus | null); + /** + * sleep when invoking server for deadline tests + */ + 'server_sleep_us'?: (number); + /** + * which backend to send request to + */ + 'backend_channel_idx'?: (number); + 'echo_metadata_initially'?: (boolean); + 'server_notify_client_when_started'?: (boolean); + 'backend_metrics'?: (_xds_data_orca_v3_OrcaLoadReport | null); + 'echo_host_from_authority_header'?: (boolean); +} + +export interface RequestParams__Output { + 'echo_deadline': (boolean); + 'client_cancel_after_us': (number); + 'server_cancel_after_us': (number); + 'echo_metadata': (boolean); + 'check_auth_context': (boolean); + 'response_message_length': (number); + 'echo_peer': (boolean); + /** + * will force check_auth_context. + */ + 'expected_client_identity': (string); + 'skip_cancelled_check': (boolean); + 'expected_transport_security_type': (string); + 'debug_info': (_grpc_testing_DebugInfo__Output | null); + /** + * Server should not see a request with this set. + */ + 'server_die': (boolean); + 'binary_error_details': (string); + 'expected_error': (_grpc_testing_ErrorStatus__Output | null); + /** + * sleep when invoking server for deadline tests + */ + 'server_sleep_us': (number); + /** + * which backend to send request to + */ + 'backend_channel_idx': (number); + 'echo_metadata_initially': (boolean); + 'server_notify_client_when_started': (boolean); + 'backend_metrics': (_xds_data_orca_v3_OrcaLoadReport__Output | null); + 'echo_host_from_authority_header': (boolean); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts b/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts new file mode 100644 index 000000000..588e463c2 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/ResponseParams.ts @@ -0,0 +1,15 @@ +// Original file: proto/grpc/testing/echo_messages.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface ResponseParams { + 'request_deadline'?: (number | string | Long); + 'host'?: (string); + 'peer'?: (string); +} + +export interface ResponseParams__Output { + 'request_deadline': (string); + 'host': (string); + 'peer': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts new file mode 100644 index 000000000..292a2020c --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleRequest.ts @@ -0,0 +1,8 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface SimpleRequest { +} + +export interface SimpleRequest__Output { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts new file mode 100644 index 000000000..3e8735e5e --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/SimpleResponse.ts @@ -0,0 +1,8 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface SimpleResponse { +} + +export interface SimpleResponse__Output { +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts b/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts new file mode 100644 index 000000000..4a779ae2b --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/StringValue.ts @@ -0,0 +1,10 @@ +// Original file: proto/grpc/testing/simple_messages.proto + + +export interface StringValue { + 'message'?: (string); +} + +export interface StringValue__Output { + 'message': (string); +} diff --git a/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts b/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts new file mode 100644 index 000000000..48128976e --- /dev/null +++ b/packages/grpc-js-xds/test/generated/grpc/testing/UnimplementedEchoService.ts @@ -0,0 +1,27 @@ +// Original file: proto/grpc/testing/echo.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { EchoRequest as _grpc_testing_EchoRequest, EchoRequest__Output as _grpc_testing_EchoRequest__Output } from '../../grpc/testing/EchoRequest'; +import type { EchoResponse as _grpc_testing_EchoResponse, EchoResponse__Output as _grpc_testing_EchoResponse__Output } from '../../grpc/testing/EchoResponse'; + +export interface UnimplementedEchoServiceClient extends grpc.Client { + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + Unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + unimplemented(argument: _grpc_testing_EchoRequest, callback: grpc.requestCallback<_grpc_testing_EchoResponse__Output>): grpc.ClientUnaryCall; + +} + +export interface UnimplementedEchoServiceHandlers extends grpc.UntypedServiceImplementation { + Unimplemented: grpc.handleUnaryCall<_grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse>; + +} + +export interface UnimplementedEchoServiceDefinition extends grpc.ServiceDefinition { + Unimplemented: MethodDefinition<_grpc_testing_EchoRequest, _grpc_testing_EchoResponse, _grpc_testing_EchoRequest__Output, _grpc_testing_EchoResponse__Output> +} diff --git a/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts b/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts new file mode 100644 index 000000000..d66c42713 --- /dev/null +++ b/packages/grpc-js-xds/test/generated/xds/data/orca/v3/OrcaLoadReport.ts @@ -0,0 +1,59 @@ +// Original file: proto/grpc/testing/xds/v3/orca_load_report.proto + +import type { Long } from '@grpc/proto-loader'; + +export interface OrcaLoadReport { + /** + * CPU utilization expressed as a fraction of available CPU resources. This + * should be derived from the latest sample or measurement. + */ + 'cpu_utilization'?: (number | string); + /** + * Memory utilization expressed as a fraction of available memory + * resources. This should be derived from the latest sample or measurement. + */ + 'mem_utilization'?: (number | string); + /** + * Total RPS being served by an endpoint. This should cover all services that an endpoint is + * responsible for. + */ + 'rps'?: (number | string | Long); + /** + * Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + * storage) associated with the request. + */ + 'request_cost'?: ({[key: string]: number | string}); + /** + * Resource utilization values. Each value is expressed as a fraction of total resources + * available, derived from the latest sample or measurement. + */ + 'utilization'?: ({[key: string]: number | string}); +} + +export interface OrcaLoadReport__Output { + /** + * CPU utilization expressed as a fraction of available CPU resources. This + * should be derived from the latest sample or measurement. + */ + 'cpu_utilization': (number | string); + /** + * Memory utilization expressed as a fraction of available memory + * resources. This should be derived from the latest sample or measurement. + */ + 'mem_utilization': (number | string); + /** + * Total RPS being served by an endpoint. This should cover all services that an endpoint is + * responsible for. + */ + 'rps': (string); + /** + * Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of + * storage) associated with the request. + */ + 'request_cost': ({[key: string]: number | string}); + /** + * Resource utilization values. Each value is expressed as a fraction of total resources + * available, derived from the latest sample or measurement. + */ + 'utilization': ({[key: string]: number | string}); +} diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts new file mode 100644 index 000000000..d0d1b6031 --- /dev/null +++ b/packages/grpc-js-xds/test/test-core.ts @@ -0,0 +1,63 @@ +/* + * 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. + * + */ + +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; + +import { register } from "../src"; +import assert = require("assert"); + +register(); + +describe('core xDS functionality', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }) + it('should route requests to the single backend', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('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 = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }, reason => done(reason)); + }, reason => done(reason)); + }); +}); \ No newline at end of file diff --git a/packages/grpc-js-xds/test/xds-server.ts b/packages/grpc-js-xds/test/xds-server.ts new file mode 100644 index 000000000..b82a3a673 --- /dev/null +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -0,0 +1,342 @@ +/* + * 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. + * + */ + +import { ServerDuplexStream, Server, UntypedServiceImplementation, ServerCredentials, loadPackageDefinition } from "@grpc/grpc-js"; +import { AnyExtension, loadSync } from "@grpc/proto-loader"; +import { EventEmitter } from "stream"; +import { Cluster } from "../src/generated/envoy/config/cluster/v3/Cluster"; +import { ClusterLoadAssignment } from "../src/generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { Listener } from "../src/generated/envoy/config/listener/v3/Listener"; +import { RouteConfiguration } from "../src/generated/envoy/config/route/v3/RouteConfiguration"; +import { AggregatedDiscoveryServiceHandlers } from "../src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService"; +import { DiscoveryRequest__Output } from "../src/generated/envoy/service/discovery/v3/DiscoveryRequest"; +import { DiscoveryResponse } from "../src/generated/envoy/service/discovery/v3/DiscoveryResponse"; +import { Any } from "../src/generated/google/protobuf/Any"; +import { LDS_TYPE_URL, RDS_TYPE_URL, CDS_TYPE_URL, EDS_TYPE_URL, LdsTypeUrl, RdsTypeUrl, CdsTypeUrl, EdsTypeUrl, AdsTypeUrl } from "../src/resources" +import * as adsTypes from '../src/generated/ads'; +import * as lrsTypes from '../src/generated/lrs'; +import { LoadStatsRequest__Output } from "../src/generated/envoy/service/load_stats/v3/LoadStatsRequest"; +import { LoadStatsResponse } from "../src/generated/envoy/service/load_stats/v3/LoadStatsResponse"; + +const loadedProtos = loadPackageDefinition(loadSync( + [ + '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' + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/googleapis/', + __dirname + '/../../deps/protoc-gen-validate/', + ], + })) as unknown as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType; + +type AdsInputType = T extends EdsTypeUrl + ? ClusterLoadAssignment + : T extends CdsTypeUrl + ? Cluster + : T extends RdsTypeUrl + ? RouteConfiguration + : Listener; + +const ADS_TYPE_URLS = new Set([LDS_TYPE_URL, RDS_TYPE_URL, CDS_TYPE_URL, EDS_TYPE_URL]); + +interface ResponseState { + state: 'ACKED' | 'NACKED'; + errorMessage?: string; +} + +interface ResponseListener { + (typeUrl: AdsTypeUrl, responseState: ResponseState): void; +} + +type ResourceAny = AdsInputType & {'@type': T}; + +interface ResourceState { + resource?: ResourceAny; + resourceTypeVersion: number; + subscriptions: Set; +} + +interface ResourceTypeState { + resourceTypeVersion: number; + /** + * Key type is type URL + */ + resourceNameMap: Map>; +} + +interface ResourceMap { + [EDS_TYPE_URL]: ResourceTypeState; + [CDS_TYPE_URL]: ResourceTypeState; + [RDS_TYPE_URL]: ResourceTypeState; + [LDS_TYPE_URL]: ResourceTypeState; +} + +function isAdsTypeUrl(value: string): value is AdsTypeUrl { + return ADS_TYPE_URLS.has(value); +} + +export class XdsServer { + private resourceMap: ResourceMap = { + [EDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [CDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [RDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + [LDS_TYPE_URL]: { + resourceTypeVersion: 0, + resourceNameMap: new Map() + }, + }; + private responseListeners = new Set(); + private resourceTypesToIgnore = new Set(); + private clients = new Map>(); + private server: Server | null = null; + private port: number | null = null; + + addResponseListener(listener: ResponseListener) { + this.responseListeners.add(listener); + } + + removeResponseListener(listener: ResponseListener) { + this.responseListeners.delete(listener); + } + + setResource(resource: ResourceAny, name: string) { + const resourceTypeState = this.resourceMap[resource["@type"]] as ResourceTypeState; + resourceTypeState.resourceTypeVersion += 1; + let resourceState: ResourceState | undefined = resourceTypeState.resourceNameMap.get(name); + if (!resourceState) { + resourceState = { + resourceTypeVersion: 0, + subscriptions: new Set() + }; + resourceTypeState.resourceNameMap.set(name, resourceState); + } + resourceState.resourceTypeVersion = resourceTypeState.resourceTypeVersion; + resourceState.resource = resource; + this.sendResourceUpdates(resource['@type'], resourceState.subscriptions, new Set([name])); + } + + setLdsResource(resource: Listener) { + this.setResource({...resource, '@type': LDS_TYPE_URL}, resource.name!); + } + + setRdsResource(resource: RouteConfiguration) { + this.setResource({...resource, '@type': RDS_TYPE_URL}, resource.name!); + } + + setCdsResource(resource: Cluster) { + this.setResource({...resource, '@type': CDS_TYPE_URL}, resource.name!); + } + + setEdsResource(resource: ClusterLoadAssignment) { + this.setResource({...resource, '@type': EDS_TYPE_URL}, resource.cluster_name!); + } + + unsetResource(typeUrl: T, name: string) { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + resourceTypeState.resourceTypeVersion += 1; + let resourceState: ResourceState | undefined = resourceTypeState.resourceNameMap.get(name); + if (resourceState) { + resourceState.resourceTypeVersion = resourceTypeState.resourceTypeVersion; + delete resourceState.resource; + this.sendResourceUpdates(typeUrl, resourceState.subscriptions, new Set([name])); + } + } + + ignoreResourceType(typeUrl: AdsTypeUrl) { + this.resourceTypesToIgnore.add(typeUrl); + } + + private sendResourceUpdates(typeUrl: T, clients: Set, includeResources: Set) { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + const clientResources = new Map(); + for (const [resourceName, resourceState] of resourceTypeState.resourceNameMap) { + /* For RDS and EDS, only send updates for the listed updated resources. + * Otherwise include all resources. */ + if ((typeUrl === RDS_TYPE_URL || typeUrl === EDS_TYPE_URL) && !includeResources.has(resourceName)) { + continue; + } + if (!resourceState.resource) { + continue; + } + for (const clientName of clients) { + if (!resourceState.subscriptions.has(clientName)) { + continue; + } + let resourcesList = clientResources.get(clientName); + if (!resourcesList) { + resourcesList = []; + clientResources.set(clientName, resourcesList); + } + resourcesList.push(resourceState.resource); + } + } + for (const [clientName, resourceList] of clientResources) { + this.clients.get(clientName)?.write({ + resources: resourceList, + version_info: resourceTypeState.resourceTypeVersion.toString(), + nonce: resourceTypeState.resourceTypeVersion.toString(), + type_url: typeUrl + }); + } + } + + private updateResponseListeners(typeUrl: AdsTypeUrl, responseState: ResponseState) { + for (const listener of this.responseListeners) { + listener(typeUrl, responseState); + } + } + + private maybeSubscribe(typeUrl: T, client: string, resourceName: string): boolean { + const resourceTypeState = this.resourceMap[typeUrl] as ResourceTypeState; + let resourceState = resourceTypeState.resourceNameMap.get(resourceName); + if (!resourceState) { + resourceState = { + resourceTypeVersion: 0, + subscriptions: new Set() + }; + resourceTypeState.resourceNameMap.set(resourceName, resourceState); + } + const newlySubscribed = !resourceState.subscriptions.has(client); + resourceState.subscriptions.add(client); + return newlySubscribed; + } + + private handleUnsubscriptions(typeUrl: AdsTypeUrl, client: string, requestedResourceNames?: Set) { + const resourceTypeState = this.resourceMap[typeUrl]; + for (const [resourceName, resourceState] of resourceTypeState.resourceNameMap) { + if (!requestedResourceNames || !requestedResourceNames.has(resourceName)) { + resourceState.subscriptions.delete(client); + if (!resourceState.resource && resourceState.subscriptions.size === 0) { + resourceTypeState.resourceNameMap.delete(resourceName) + } + } + } + } + + private handleRequest(clientName: string, request: DiscoveryRequest__Output) { + if (!isAdsTypeUrl(request.type_url)) { + console.error(`Received ADS request with unsupported type_url ${request.type_url}`); + return; + } + const clientResourceVersion = request.version_info === '' ? 0 : Number.parseInt(request.version_info); + if (request.error_detail) { + this.updateResponseListeners(request.type_url, {state: 'NACKED', errorMessage: request.error_detail.message}); + } else { + this.updateResponseListeners(request.type_url, {state: 'ACKED'}); + } + const requestedResourceNames = new Set(request.resource_names); + const resourceTypeState = this.resourceMap[request.type_url]; + const updatedResources = new Set(); + for (const resourceName of requestedResourceNames) { + if (this.maybeSubscribe(request.type_url, clientName, resourceName) || resourceTypeState.resourceNameMap.get(resourceName)!.resourceTypeVersion > clientResourceVersion) { + updatedResources.add(resourceName); + } + } + this.handleUnsubscriptions(request.type_url, clientName, requestedResourceNames); + if (updatedResources.size > 0) { + this.sendResourceUpdates(request.type_url, new Set([clientName]), updatedResources); + } + } + + StreamAggregatedResources(call: ServerDuplexStream) { + const clientName = call.getPeer(); + this.clients.set(clientName, call); + call.on('data', (request: DiscoveryRequest__Output) => { + this.handleRequest(clientName, request); + }); + call.on('end', () => { + this.clients.delete(clientName); + for (const typeUrl of ADS_TYPE_URLS) { + this.handleUnsubscriptions(typeUrl as AdsTypeUrl, clientName); + } + call.end(); + }); + } + + StreamLoadStats(call: ServerDuplexStream) { + const statsResponse = {load_reporting_interval: {seconds: 30}}; + call.write(statsResponse); + call.on('data', (request: LoadStatsRequest__Output) => { + call.write(statsResponse); + }); + call.on('end', () => { + call.end(); + }); + } + + startServer(callback: (error: Error | null, port: number) => void) { + if (this.server) { + return; + } + const server = new Server(); + server.addService(loadedProtos.envoy.service.discovery.v3.AggregatedDiscoveryService.service, this as unknown as UntypedServiceImplementation); + server.addService(loadedProtos.envoy.service.load_stats.v3.LoadReportingService.service, this as unknown as UntypedServiceImplementation); + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (error, port) => { + if (!error) { + this.server = server; + this.port = port; + server.start(); + } + callback(error, port); + }); + } + + shutdownServer() { + this.server?.forceShutdown(); + } + + getBootstrapInfoString(): string { + if (this.port === null) { + throw new Error('Bootstrap info unavailable; server not started'); + } + const bootstrapInfo = { + xds_servers: [{ + server_uri: `localhost:${this.port}`, + channel_creds: [{type: 'insecure'}] + }], + node: { + id: 'test', + locality: {} + } + } + return JSON.stringify(bootstrapInfo); + } +} \ No newline at end of file From e32bbc7aacdca42bd4690471449c43d6c29ffec1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 27 Jan 2023 13:39:44 -0800 Subject: [PATCH 457/694] grpc-js-xds: Allow tests to set bootstrap info in channel args --- packages/grpc-js-xds/src/load-balancer-cds.ts | 8 +++--- packages/grpc-js-xds/src/load-balancer-eds.ts | 10 ++++--- packages/grpc-js-xds/src/load-balancer-lrs.ts | 2 +- packages/grpc-js-xds/src/resolver-xds.ts | 26 ++++++++++++++----- packages/grpc-js-xds/src/xds-bootstrap.ts | 6 ++--- packages/grpc-js-xds/src/xds-client.ts | 14 +++++++--- .../src/xds-stream-state/eds-state.ts | 8 ++++++ 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 4d47b2546..243a1e967 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -125,6 +125,7 @@ export class CdsLoadBalancer implements LoadBalancer { private latestConfig: CdsLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown } = {}; + private xdsClient: XdsClient | null = null; constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper); @@ -188,6 +189,7 @@ export class CdsLoadBalancer implements LoadBalancer { } trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); this.latestAttributes = attributes; + this.xdsClient = attributes.xdsClient as XdsClient; /* If the cluster is changing, disable the old watcher before adding the new * one */ @@ -196,7 +198,7 @@ export class CdsLoadBalancer implements LoadBalancer { this.latestConfig?.getCluster() !== lbConfig.getCluster() ) { trace('Removing old cluster watcher for cluster name ' + this.latestConfig!.getCluster()); - getSingletonXdsClient().removeClusterWatcher( + this.xdsClient.removeClusterWatcher( this.latestConfig!.getCluster(), this.watcher ); @@ -212,7 +214,7 @@ export class CdsLoadBalancer implements LoadBalancer { if (!this.isWatcherActive) { trace('Adding new cluster watcher for cluster name ' + lbConfig.getCluster()); - getSingletonXdsClient().addClusterWatcher(lbConfig.getCluster(), this.watcher); + this.xdsClient.addClusterWatcher(lbConfig.getCluster(), this.watcher); this.isWatcherActive = true; } } @@ -226,7 +228,7 @@ export class CdsLoadBalancer implements LoadBalancer { trace('Destroying load balancer with cluster name ' + this.latestConfig?.getCluster()); this.childBalancer.destroy(); if (this.isWatcherActive) { - getSingletonXdsClient().removeClusterWatcher( + this.xdsClient?.removeClusterWatcher( this.latestConfig!.getCluster(), this.watcher ); diff --git a/packages/grpc-js-xds/src/load-balancer-eds.ts b/packages/grpc-js-xds/src/load-balancer-eds.ts index b0bd3f030..03a4078ac 100644 --- a/packages/grpc-js-xds/src/load-balancer-eds.ts +++ b/packages/grpc-js-xds/src/load-balancer-eds.ts @@ -167,6 +167,7 @@ export class EdsLoadBalancer implements LoadBalancer { private lastestConfig: EdsLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown } = {}; + private xdsClient: XdsClient | null = null; private latestEdsUpdate: ClusterLoadAssignment__Output | null = null; /** @@ -488,13 +489,14 @@ export class EdsLoadBalancer implements LoadBalancer { trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); this.lastestConfig = lbConfig; this.latestAttributes = attributes; + this.xdsClient = attributes.xdsClient as XdsClient; const newEdsServiceName = lbConfig.getEdsServiceName() ?? lbConfig.getCluster(); /* If the name is changing, disable the old watcher before adding the new * one */ if (this.isWatcherActive && this.edsServiceName !== newEdsServiceName) { trace('Removing old endpoint watcher for edsServiceName ' + this.edsServiceName) - getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName!, this.watcher); + this.xdsClient.removeEndpointWatcher(this.edsServiceName!, this.watcher); /* Setting isWatcherActive to false here lets us have one code path for * calling addEndpointWatcher */ this.isWatcherActive = false; @@ -507,12 +509,12 @@ export class EdsLoadBalancer implements LoadBalancer { if (!this.isWatcherActive) { trace('Adding new endpoint watcher for edsServiceName ' + this.edsServiceName); - getSingletonXdsClient().addEndpointWatcher(this.edsServiceName, this.watcher); + this.xdsClient.addEndpointWatcher(this.edsServiceName, this.watcher); this.isWatcherActive = true; } if (lbConfig.getLrsLoadReportingServerName()) { - this.clusterDropStats = getSingletonXdsClient().addClusterDropStats( + this.clusterDropStats = this.xdsClient.addClusterDropStats( lbConfig.getLrsLoadReportingServerName()!, lbConfig.getCluster(), lbConfig.getEdsServiceName() ?? '' @@ -533,7 +535,7 @@ export class EdsLoadBalancer implements LoadBalancer { destroy(): void { trace('Destroying load balancer with edsServiceName ' + this.edsServiceName); if (this.edsServiceName) { - getSingletonXdsClient().removeEndpointWatcher(this.edsServiceName, this.watcher); + this.xdsClient?.removeEndpointWatcher(this.edsServiceName, this.watcher); } this.childBalancer.destroy(); } diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 745b21c5c..9610ea834 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -169,7 +169,7 @@ export class LrsLoadBalancer implements LoadBalancer { if (!(lbConfig instanceof LrsLoadBalancingConfig)) { return; } - this.localityStatsReporter = getSingletonXdsClient().addClusterLocalityStats( + this.localityStatsReporter = (attributes.xdsClient as XdsClient).addClusterLocalityStats( lbConfig.getLrsLoadReportingServerName(), lbConfig.getClusterName(), lbConfig.getEdsServiceName(), diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 401465be6..9879a2c6e 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -48,6 +48,7 @@ import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment' import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; import RetryPolicy = experimental.RetryPolicy; +import { validateBootstrapConfig } from './xds-bootstrap'; const TRACER_NAME = 'xds_resolver'; @@ -210,6 +211,8 @@ function getDefaultRetryMaxInterval(baseInterval: string): string { return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`; } +const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config'; + const RETRY_CODES: {[key: string]: status} = { 'cancelled': status.CANCELLED, 'deadline-exceeded': status.DEADLINE_EXCEEDED, @@ -238,11 +241,20 @@ class XdsResolver implements Resolver { private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = []; + private xdsClient: XdsClient; + constructor( private target: GrpcUri, private listener: ResolverListener, private channelOptions: ChannelOptions ) { + if (channelOptions[BOOTSTRAP_CONFIG_KEY]) { + const parsedConfig = JSON.parse(channelOptions[BOOTSTRAP_CONFIG_KEY]); + const validatedConfig = validateBootstrapConfig(parsedConfig); + this.xdsClient = new XdsClient(validatedConfig); + } else { + this.xdsClient = getSingletonXdsClient(); + } this.ldsWatcher = { onValidUpdate: (update: Listener__Output) => { const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, update.api_listener!.api_listener!.value); @@ -267,16 +279,16 @@ class XdsResolver implements Resolver { const routeConfigName = httpConnectionManager.rds!.route_config_name; if (this.latestRouteConfigName !== routeConfigName) { if (this.latestRouteConfigName !== null) { - getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } - getSingletonXdsClient().addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher); + this.xdsClient.addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher); this.latestRouteConfigName = routeConfigName; } break; } case 'route_config': if (this.latestRouteConfigName) { - getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } this.handleRouteConfig(httpConnectionManager.route_config!); break; @@ -546,7 +558,7 @@ class XdsResolver implements Resolver { methodConfig: [], loadBalancingConfig: [lbPolicyConfig] } - this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {}); + this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {xdsClient: this.xdsClient}); } private reportResolutionError(reason: string) { @@ -563,15 +575,15 @@ class XdsResolver implements Resolver { // Wait until updateResolution is called once to start the xDS requests if (!this.isLdsWatcherActive) { trace('Starting resolution for target ' + uriToString(this.target)); - getSingletonXdsClient().addListenerWatcher(this.target.path, this.ldsWatcher); + this.xdsClient.addListenerWatcher(this.target.path, this.ldsWatcher); this.isLdsWatcherActive = true; } } destroy() { - getSingletonXdsClient().removeListenerWatcher(this.target.path, this.ldsWatcher); + this.xdsClient.removeListenerWatcher(this.target.path, this.ldsWatcher); if (this.latestRouteConfigName) { - getSingletonXdsClient().removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } } diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 876b6d958..72a0ca375 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -231,7 +231,7 @@ function validateNode(obj: any): Node { return result; } -function validateBootstrapFile(obj: any): BootstrapInfo { +export function validateBootstrapConfig(obj: any): BootstrapInfo { return { xdsServers: obj.xds_servers.map(validateXdsServerConfig), node: validateNode(obj.node), @@ -265,7 +265,7 @@ export async function loadBootstrapInfo(): Promise { } try { const parsedFile = JSON.parse(data); - resolve(validateBootstrapFile(parsedFile)); + resolve(validateBootstrapConfig(parsedFile)); } catch (e) { reject( new Error( @@ -290,7 +290,7 @@ export async function loadBootstrapInfo(): Promise { if (bootstrapConfig) { try { const parsedConfig = JSON.parse(bootstrapConfig); - const loadedBootstrapInfoValue = validateBootstrapFile(parsedConfig); + const loadedBootstrapInfoValue = validateBootstrapConfig(parsedConfig); loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue); } catch (e) { throw new Error( diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 2dfa41236..a2d33d1a5 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -21,7 +21,7 @@ import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel, connectivityState } from '@grpc/grpc-js'; import * as adsTypes from './generated/ads'; import * as lrsTypes from './generated/lrs'; -import { loadBootstrapInfo } from './xds-bootstrap'; +import { BootstrapInfo, loadBootstrapInfo } from './xds-bootstrap'; import { Node } from './generated/envoy/config/core/v3/Node'; import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; import { DiscoveryRequest } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; @@ -276,7 +276,7 @@ export class XdsClient { private adsBackoff: BackoffTimeout; private lrsBackoff: BackoffTimeout; - constructor() { + constructor(bootstrapInfoOverride?: BootstrapInfo) { const edsState = new EdsState(() => { this.updateNames('eds'); }); @@ -310,7 +310,15 @@ export class XdsClient { }); this.lrsBackoff.unref(); - Promise.all([loadBootstrapInfo(), loadAdsProtos()]).then( + async function getBootstrapInfo(): Promise { + if (bootstrapInfoOverride) { + return bootstrapInfoOverride; + } else { + return loadBootstrapInfo(); + } + } + + Promise.all([getBootstrapInfo(), loadAdsProtos()]).then( ([bootstrapInfo, protoDefinitions]) => { if (this.hasShutdown) { return; diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index cec6a4764..b043ebbc0 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -61,10 +61,12 @@ export class EdsState extends BaseXdsStreamState const priorityTotalWeights: Map = new Map(); for (const endpoint of message.endpoints) { if (!endpoint.locality) { + trace('EDS validation: endpoint locality unset'); return false; } for (const {locality, priority} of seenLocalities) { if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) { + trace('EDS validation: endpoint locality duplicated: ' + JSON.stringify(locality) + ', priority=' + priority); return false; } } @@ -72,16 +74,20 @@ export class EdsState extends BaseXdsStreamState for (const lb of endpoint.lb_endpoints) { const socketAddress = lb.endpoint?.address?.socket_address; if (!socketAddress) { + trace('EDS validation: endpoint socket_address not set'); return false; } if (socketAddress.port_specifier !== 'port_value') { + trace('EDS validation: socket_address.port_specifier !== "port_value"'); return false; } if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) { + trace('EDS validation: address not a valid IPv4 or IPv6 address: ' + socketAddress.address); return false; } for (const address of seenAddresses) { if (addressesEqual(socketAddress, address)) { + trace('EDS validation: duplicate address seen: ' + address); return false; } } @@ -91,11 +97,13 @@ export class EdsState extends BaseXdsStreamState } for (const totalWeight of priorityTotalWeights.values()) { if (totalWeight > UINT32_MAX) { + trace('EDS validation: total weight > UINT32_MAX') return false; } } for (const priority of priorityTotalWeights.keys()) { if (priority > 0 && !priorityTotalWeights.has(priority - 1)) { + trace('EDS validation: priorities not contiguous'); return false; } } From 056dc8e56ea4b59444700ac407f247e5128b6d0c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 10 Mar 2023 13:58:02 -0800 Subject: [PATCH 458/694] grpc-js: Unregister socket from channelz when closing transport --- packages/grpc-js/src/transport.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 3c9163d13..8abc13aba 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -20,7 +20,7 @@ import { checkServerIdentity, CipherNameAndProtocol, ConnectionOptions, PeerCert import { StatusObject } from './call-interface'; import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; -import { ChannelzCallTracker, registerChannelzSocket, SocketInfo, SocketRef, TlsInfo } from './channelz'; +import { ChannelzCallTracker, registerChannelzSocket, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; import { LogVerbosity } from './constants'; import { getProxiedConnection, ProxyConnectionResult } from './http_proxy'; import * as logging from './logging'; @@ -471,6 +471,7 @@ class Http2Transport implements Transport { shutdown() { this.session.close(); + unregisterChannelzRef(this.channelzRef); } } From 3fbdf0d337aa88b70c0a055ff7fafcff91541b3b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 10 Mar 2023 14:05:39 -0800 Subject: [PATCH 459/694] grpc-js: Bump version to 1.8.13 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 722f9d866..815dabeb0 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.12", + "version": "1.8.13", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From e5e6731917ed646f3d2bcc25304943de11758b46 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 24 Feb 2023 09:55:45 -0800 Subject: [PATCH 460/694] grpc-js-xds: Use simpler search algorithm in weighted target picker --- .../src/load-balancer-weighted-target.ts | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts index 5d7cfa04e..7cd92d98b 100644 --- a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -119,31 +119,16 @@ class WeightedTargetPicker implements Picker { pick(pickArgs: PickArgs): PickResult { // num | 0 is equivalent to floor(num) const selection = (Math.random() * this.rangeTotal) | 0; - - /* Binary search for the element of the list such that - * pickerList[index - 1].rangeEnd <= selection < pickerList[index].rangeEnd - */ - let mid = 0; - let startIndex = 0; - let endIndex = this.pickerList.length - 1; - let index = 0; - while (endIndex > startIndex) { - mid = ((startIndex + endIndex) / 2) | 0; - if (this.pickerList[mid].rangeEnd > selection) { - endIndex = mid; - } else if (this.pickerList[mid].rangeEnd < selection) { - startIndex = mid + 1; - } else { - // + 1 here because the range is exclusive at the top end - index = mid + 1; - break; + + for (const entry of this.pickerList) { + if (selection < entry.rangeEnd) { + return entry.picker.pick(pickArgs); } } - if (index === 0) { - index = startIndex; - } - return this.pickerList[index].picker.pick(pickArgs); + /* Default to first element if the iteration doesn't find anything for some + * reason. */ + return this.pickerList[0].picker.pick(pickArgs); } } From 6f17499e3c7c0da5a04f8cd93621fb95e6347bba Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 22 Mar 2023 16:44:21 -0700 Subject: [PATCH 461/694] grpc-js-xds: Use non-alpine docker image for interop tests --- packages/grpc-js-xds/interop/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 5d0660aa3..4f66926c5 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -16,7 +16,7 @@ # following command from grpc-node directory: # docker build -t -f packages/grpc-js-xds/interop/Dockerfile . -FROM node:16-alpine as build +FROM node:16-slim as build # Make a grpc-node directory and copy the repo into it. WORKDIR /node/src/grpc-node @@ -27,7 +27,7 @@ RUN npm install WORKDIR /node/src/grpc-node/packages/grpc-js-xds RUN npm install -FROM node:16-alpine +FROM node:16-slim WORKDIR /node/src/grpc-node COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ From 5c41ee44188d18ff6a8fa41ca5a9c49d85c2a16f Mon Sep 17 00:00:00 2001 From: Pietro De Nicolao Date: Thu, 30 Mar 2023 13:41:05 +0200 Subject: [PATCH 462/694] docs: replace invalid link in grpc-tools README --- packages/grpc-tools/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-tools/README.md b/packages/grpc-tools/README.md index eae786929..980ce93eb 100644 --- a/packages/grpc-tools/README.md +++ b/packages/grpc-tools/README.md @@ -9,7 +9,7 @@ libraries. This library exports the `grpc_tools_node_protoc` executable, which accepts all of the same arguments as `protoc` itself. For use with Node, you most likely want to use CommonJS-style imports. An example of generating code this way can -be found in [this guide](https://developers.google.com/protocol-buffers/docs/reference/javascript-generated#commonjs-imports). +be found in [this guide](https://github.com/protocolbuffers/protobuf-javascript/blob/main/docs/index.md). The `grpc_tools_node_protoc` automatically includes the Node gRPC plugin, so it also accepts the `--grpc_out=[option:]path` argument. The option can be one of the following: @@ -19,4 +19,4 @@ one of the following: - `generate_package_definition`: Generates code that does not `require` any gRPC library, and instead generates `PackageDefinition` objects that can be passed to the `loadPackageDefinition` function provided by both the - `grpc` and `@grpc/grpc-js` libraries. \ No newline at end of file + `grpc` and `@grpc/grpc-js` libraries. From e48b2ca84657607aed8de1d425f2845466dfbd0e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 31 Mar 2023 12:10:36 -0700 Subject: [PATCH 463/694] grpc-js-xds: Use Node 18 in interop docker image --- packages/grpc-js-xds/interop/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 4f66926c5..5c8e362f1 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -16,7 +16,7 @@ # following command from grpc-node directory: # docker build -t -f packages/grpc-js-xds/interop/Dockerfile . -FROM node:16-slim as build +FROM node:18-slim as build # Make a grpc-node directory and copy the repo into it. WORKDIR /node/src/grpc-node @@ -27,7 +27,7 @@ RUN npm install WORKDIR /node/src/grpc-node/packages/grpc-js-xds RUN npm install -FROM node:16-slim +FROM node:18-slim WORKDIR /node/src/grpc-node COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ From 7840a108d3eb9e614dee413f54df9a3b66ee600b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 3 Apr 2023 09:54:38 -0700 Subject: [PATCH 464/694] grpc-js-xds: Use Debian and Node 18 in interop Dockerfile (1.8.x) --- packages/grpc-js-xds/interop/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index b93e309d7..5987f1ee4 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -16,7 +16,7 @@ # following command from grpc-node directory: # docker build -t -f packages/grpc-js-xds/interop/Dockerfile . -FROM node:16-alpine as build +FROM node:18-slim as build # Make a grpc-node directory and copy the repo into it. WORKDIR /node/src/grpc-node @@ -27,7 +27,7 @@ RUN npm install WORKDIR /node/src/grpc-node/packages/grpc-js-xds RUN npm install -FROM node:16-alpine +FROM node:18-slim WORKDIR /node/src/grpc-node COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ From 8f47d67a4129352fbb39480295798e6e5917a6e3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 3 Apr 2023 16:51:51 -0700 Subject: [PATCH 465/694] grpc-js-xds: Use the same tracers in the legacy driver as in the new one --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index d4490c5ef..2dc13a6c0 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -50,7 +50,7 @@ grpc/tools/run_tests/helper_scripts/prep_xds.sh mkdir -p "${KOKORO_ARTIFACTS_DIR}/github/grpc/reports" -GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds \ +GRPC_NODE_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_cluster_resolver,xds_cluster_impl,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection,server,server_call \ GRPC_NODE_VERBOSITY=DEBUG \ NODE_XDS_INTEROP_VERBOSITY=1 \ python3 grpc/tools/run_tests/run_xds_tests.py \ From d21ce8cc498fd9896e4ca8b515463ecdb401e2c7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 5 Apr 2023 16:35:10 -0700 Subject: [PATCH 466/694] grpc-js: Simplify round robin implementation --- .../grpc-js/src/load-balancer-round-robin.ts | 37 ++++--------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 91c10b238..9e91038ff 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -93,14 +93,6 @@ class RoundRobinPicker implements Picker { } } -interface ConnectivityStateCounts { - [ConnectivityState.CONNECTING]: number; - [ConnectivityState.IDLE]: number; - [ConnectivityState.READY]: number; - [ConnectivityState.SHUTDOWN]: number; - [ConnectivityState.TRANSIENT_FAILURE]: number; -} - export class RoundRobinLoadBalancer implements LoadBalancer { private subchannels: SubchannelInterface[] = []; @@ -108,25 +100,14 @@ export class RoundRobinLoadBalancer implements LoadBalancer { private subchannelStateListener: ConnectivityStateListener; - private subchannelStateCounts: ConnectivityStateCounts; - private currentReadyPicker: RoundRobinPicker | null = null; constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.subchannelStateCounts = { - [ConnectivityState.CONNECTING]: 0, - [ConnectivityState.IDLE]: 0, - [ConnectivityState.READY]: 0, - [ConnectivityState.SHUTDOWN]: 0, - [ConnectivityState.TRANSIENT_FAILURE]: 0, - }; this.subchannelStateListener = ( subchannel: SubchannelInterface, previousState: ConnectivityState, newState: ConnectivityState ) => { - this.subchannelStateCounts[previousState] -= 1; - this.subchannelStateCounts[newState] += 1; this.calculateAndUpdateState(); if ( @@ -139,8 +120,12 @@ export class RoundRobinLoadBalancer implements LoadBalancer { }; } + private countSubchannelsWithState(state: ConnectivityState) { + return this.subchannels.filter(subchannel => subchannel.getConnectivityState() === state).length; + } + private calculateAndUpdateState() { - if (this.subchannelStateCounts[ConnectivityState.READY] > 0) { + if (this.countSubchannelsWithState(ConnectivityState.READY) > 0) { const readySubchannels = this.subchannels.filter( (subchannel) => subchannel.getConnectivityState() === ConnectivityState.READY @@ -158,10 +143,10 @@ export class RoundRobinLoadBalancer implements LoadBalancer { ConnectivityState.READY, new RoundRobinPicker(readySubchannels, index) ); - } else if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) { + } else if (this.countSubchannelsWithState(ConnectivityState.CONNECTING) > 0) { this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); } else if ( - this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > 0 + this.countSubchannelsWithState(ConnectivityState.TRANSIENT_FAILURE) > 0 ) { this.updateState( ConnectivityState.TRANSIENT_FAILURE, @@ -193,13 +178,6 @@ export class RoundRobinLoadBalancer implements LoadBalancer { subchannel.unref(); this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); } - this.subchannelStateCounts = { - [ConnectivityState.CONNECTING]: 0, - [ConnectivityState.IDLE]: 0, - [ConnectivityState.READY]: 0, - [ConnectivityState.SHUTDOWN]: 0, - [ConnectivityState.TRANSIENT_FAILURE]: 0, - }; this.subchannels = []; } @@ -220,7 +198,6 @@ export class RoundRobinLoadBalancer implements LoadBalancer { subchannel.addConnectivityStateListener(this.subchannelStateListener); this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); const subchannelState = subchannel.getConnectivityState(); - this.subchannelStateCounts[subchannelState] += 1; if ( subchannelState === ConnectivityState.IDLE || subchannelState === ConnectivityState.TRANSIENT_FAILURE From 8d161133215534dc87cca4b7290365714133e12b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 5 Apr 2023 16:42:13 -0700 Subject: [PATCH 467/694] grpc-js-xds: Remove extra 'only' from local testing --- packages/grpc-js-xds/test/test-cluster-type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/test/test-cluster-type.ts b/packages/grpc-js-xds/test/test-cluster-type.ts index 57f45ea31..483180d9c 100644 --- a/packages/grpc-js-xds/test/test-cluster-type.ts +++ b/packages/grpc-js-xds/test/test-cluster-type.ts @@ -24,7 +24,7 @@ import { Backend } from "./backend"; register(); -describe.only('Cluster types', () => { +describe('Cluster types', () => { let xdsServer: XdsServer; let client: XdsTestClient; beforeEach(done => { From 287b0684b0e3b7de413deebed746c4f673f38d59 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 6 Apr 2023 14:22:14 -0700 Subject: [PATCH 468/694] grpc-js-xds: Render call time histograms nicely in interop logs --- .../grpc-js-xds/interop/xds-interop-client.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 0394c0ace..3055b5b5d 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -267,6 +267,30 @@ const callTimeHistogram: {[callType: string]: {[status: number]: number[]}} = { EmptyCall: {} } +function renderHistogram(histogram: number[]): string { + const maxValue = Math.max(...histogram); + const maxIndexLength = `${histogram.length - 1}`.length; + const maxBarWidth = 60; + const result: string[] = []; + result.push('-'.repeat(maxIndexLength + maxBarWidth + 1)); + for (let i = 0; i < histogram.length; i++) { + result.push(`${' '.repeat(maxIndexLength - `${i}`.length)}${i}|${'█'.repeat(maxBarWidth * histogram[i] / maxValue)}`); + } + return result.join('\n'); +} + +function printAllHistograms() { + console.log('Call duration histograms'); + for (const callType in callTimeHistogram) { + console.log(callType); + const x = callTimeHistogram[callType]; + for (const statusCode in callTimeHistogram[callType]) { + console.log(`${statusCode} ${grpc.status[statusCode]}`); + console.log(renderHistogram(callTimeHistogram[callType][statusCode])); + } + } +} + /** * Timestamps output by process.hrtime.bigint() are a bigint number of * nanoseconds. This is the representation of 1 second in that context. @@ -420,7 +444,7 @@ function main() { }, GetClientAccumulatedStats: (call, callback) => { console.log(`Sending accumulated stats response: ${JSON.stringify(accumulatedStats)}`); - console.log(`Call durations: ${JSON.stringify(callTimeHistogram, undefined, 2)}`); + printAllHistograms(); callback(null, accumulatedStats); } } From 0ec5463bee0156f0941d66494c71d1f0ba54eb70 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 7 Apr 2023 14:45:21 -0700 Subject: [PATCH 469/694] PSM Interop: experiment with qps affect on circuit_breaking ref b/232859415 --- packages/grpc-js-xds/scripts/xds.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 2dc13a6c0..a65447dde 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -60,6 +60,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_clu --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ + --qps=50 \ ${XDS_V3_OPT-} \ --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/github/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ From 43d42dcf3f2409c53a623b46ab72c89af353e4e8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 11 Apr 2023 14:24:25 -0700 Subject: [PATCH 470/694] grpc-js: Fix connectivity state change event sequencing --- .../grpc-js/src/load-balancer-pick-first.ts | 2 +- packages/grpc-js/src/subchannel.ts | 65 ++++----- .../test/test-global-subchannel-pool.ts | 130 ++++++++++++++++++ 3 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 packages/grpc-js/test/test-global-subchannel-pool.ts diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index a501b1f7d..41d21a2ea 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -322,12 +322,12 @@ export class PickFirstLoadBalancer implements LoadBalancer { ); } this.currentPick = subchannel; - this.updateState(ConnectivityState.READY, new PickFirstPicker(subchannel)); subchannel.addConnectivityStateListener(this.pickedSubchannelStateListener); subchannel.ref(); this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); this.resetSubchannelList(); clearTimeout(this.connectionDelayTimeout); + this.updateState(ConnectivityState.READY, new PickFirstPicker(subchannel)); } private updateState(newState: ConnectivityState, picker: Picker) { diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index c93e0c451..de420cc97 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -60,7 +60,7 @@ export class Subchannel { * state changes. Will be modified by `addConnectivityStateListener` and * `removeConnectivityStateListener` */ - private stateListeners: ConnectivityStateListener[] = []; + private stateListeners: Set = new Set(); private backoffTimeout: BackoffTimeout; @@ -227,6 +227,8 @@ export class Subchannel { } const previousState = this.connectivityState; this.connectivityState = newState; + process.nextTick(() => { + }); switch (newState) { case ConnectivityState.READY: this.stopBackoff(); @@ -261,9 +263,7 @@ export class Subchannel { default: throw new Error(`Invalid state: unknown ConnectivityState ${newState}`); } - /* We use a shallow copy of the stateListeners array in case a listener - * is removed during this iteration */ - for (const listener of [...this.stateListeners]) { + for (const listener of this.stateListeners) { listener(this, previousState, newState, this.keepaliveTime); } return true; @@ -291,13 +291,15 @@ export class Subchannel { if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); } - this.transitionToState( - [ConnectivityState.CONNECTING, ConnectivityState.READY], - ConnectivityState.IDLE - ); if (this.channelzEnabled) { unregisterChannelzRef(this.channelzRef); } + process.nextTick(() => { + this.transitionToState( + [ConnectivityState.CONNECTING, ConnectivityState.READY], + ConnectivityState.IDLE + ); + }); } } @@ -339,20 +341,22 @@ export class Subchannel { * Otherwise, do nothing. */ startConnecting() { - /* First, try to transition from IDLE to connecting. If that doesn't happen - * because the state is not currently IDLE, check if it is - * TRANSIENT_FAILURE, and if so indicate that it should go back to - * connecting after the backoff timer ends. Otherwise do nothing */ - if ( - !this.transitionToState( - [ConnectivityState.IDLE], - ConnectivityState.CONNECTING - ) - ) { - if (this.connectivityState === ConnectivityState.TRANSIENT_FAILURE) { - this.continueConnecting = true; + process.nextTick(() => { + /* First, try to transition from IDLE to connecting. If that doesn't happen + * because the state is not currently IDLE, check if it is + * TRANSIENT_FAILURE, and if so indicate that it should go back to + * connecting after the backoff timer ends. Otherwise do nothing */ + if ( + !this.transitionToState( + [ConnectivityState.IDLE], + ConnectivityState.CONNECTING + ) + ) { + if (this.connectivityState === ConnectivityState.TRANSIENT_FAILURE) { + this.continueConnecting = true; + } } - } + }); } /** @@ -368,7 +372,7 @@ export class Subchannel { * @param listener */ addConnectivityStateListener(listener: ConnectivityStateListener) { - this.stateListeners.push(listener); + this.stateListeners.add(listener); } /** @@ -377,21 +381,20 @@ export class Subchannel { * `addConnectivityStateListener` */ removeConnectivityStateListener(listener: ConnectivityStateListener) { - const listenerIndex = this.stateListeners.indexOf(listener); - if (listenerIndex > -1) { - this.stateListeners.splice(listenerIndex, 1); - } + this.stateListeners.delete(listener); } /** * Reset the backoff timeout, and immediately start connecting if in backoff. */ resetBackoff() { - this.backoffTimeout.reset(); - this.transitionToState( - [ConnectivityState.TRANSIENT_FAILURE], - ConnectivityState.CONNECTING - ); + process.nextTick(() => { + this.backoffTimeout.reset(); + this.transitionToState( + [ConnectivityState.TRANSIENT_FAILURE], + ConnectivityState.CONNECTING + ); + }); } getAddress(): string { diff --git a/packages/grpc-js/test/test-global-subchannel-pool.ts b/packages/grpc-js/test/test-global-subchannel-pool.ts new file mode 100644 index 000000000..c19125687 --- /dev/null +++ b/packages/grpc-js/test/test-global-subchannel-pool.ts @@ -0,0 +1,130 @@ +/* + * 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. + * + */ + +import * as assert from 'assert'; +import * as path from 'path'; + +import * as grpc from '../src'; +import {sendUnaryData, Server, ServerCredentials, ServerUnaryCall, ServiceClientConstructor, ServiceError} from '../src'; + +import {loadProtoFile} from './common'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = + loadProtoFile(protoFile).EchoService as ServiceClientConstructor; + +describe.only('Global subchannel pool', () => { + let server: Server; + let serverPort: number; + + let client1: InstanceType; + let client2: InstanceType; + + let promises: Promise[]; + + before(done => { + server = new Server(); + server.addService(echoService.service, { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, call.request); + }, + }); + + server.bindAsync( + 'localhost:0', ServerCredentials.createInsecure(), (err, port) => { + assert.ifError(err); + serverPort = port; + server.start(); + done(); + }); + }); + + beforeEach(() => { + promises = []; + }) + + after(done => { + server.tryShutdown(done); + }); + + function callService(client: InstanceType) { + return new Promise((resolve) => { + const request = {value: 'test value', value2: 3}; + + client.echo(request, (error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, request); + resolve(); + }); + }) + } + + function connect() { + const grpcOptions = { + 'grpc.use_local_subchannel_pool': 0, + } + + client1 = new echoService( + `127.0.0.1:${serverPort}`, grpc.credentials.createInsecure(), + grpcOptions); + + client2 = new echoService( + `127.0.0.1:${serverPort}`, grpc.credentials.createInsecure(), + grpcOptions); + } + + /* This is a regression test for a bug where client1.close in the + * waitForReady callback would cause the subchannel to transition to IDLE + * even though client2 is also using it. */ + it('Should handle client.close calls in waitForReady', + done => { + connect(); + + promises.push(new Promise((resolve) => { + client1.waitForReady(Date.now() + 50, (error) => { + assert.ifError(error); + client1.close(); + resolve(); + }); + })) + + promises.push(new Promise((resolve) => { + client2.waitForReady(Date.now() + 50, (error) => { + assert.ifError(error); + resolve(); + }); + })) + + Promise.all(promises).then(() => {done()}); + }) + + it('Call the service', done => { + promises.push(callService(client2)); + + Promise.all(promises).then(() => { + done(); + }); + }) + + it('Should complete the client lifecycle without error', done => { + setTimeout(() => { + client1.close(); + client2.close(); + done() + }, 500); + }); +}); From 6bc85716cd65bea8e532341efaf1e0331ec9328c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Apr 2023 14:46:27 -0700 Subject: [PATCH 471/694] grpc-js: Bump version to 1.8.14 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 815dabeb0..dd341ef03 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.13", + "version": "1.8.14", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 37099980127e05a83c9f3d609879c0af6ca4e66b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 13 Apr 2023 09:25:38 -0700 Subject: [PATCH 472/694] grpc-js: Fix a couple of errors from a previous PR --- packages/grpc-js/src/subchannel.ts | 2 -- packages/grpc-js/test/test-global-subchannel-pool.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index de420cc97..307f6b81e 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -227,8 +227,6 @@ export class Subchannel { } const previousState = this.connectivityState; this.connectivityState = newState; - process.nextTick(() => { - }); switch (newState) { case ConnectivityState.READY: this.stopBackoff(); diff --git a/packages/grpc-js/test/test-global-subchannel-pool.ts b/packages/grpc-js/test/test-global-subchannel-pool.ts index c19125687..999a11bf7 100644 --- a/packages/grpc-js/test/test-global-subchannel-pool.ts +++ b/packages/grpc-js/test/test-global-subchannel-pool.ts @@ -27,7 +27,7 @@ const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); const echoService = loadProtoFile(protoFile).EchoService as ServiceClientConstructor; -describe.only('Global subchannel pool', () => { +describe('Global subchannel pool', () => { let server: Server; let serverPort: number; From 1e9c766bc155dc8ee0fe6a43c9e879aa838f3293 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 13 Apr 2023 10:59:05 -0700 Subject: [PATCH 473/694] grpc-js-xds: Add federation support --- packages/grpc-js-xds/src/resolver-xds.ts | 45 +++++++++++++++++++---- packages/grpc-js-xds/src/xds-bootstrap.ts | 7 ++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 9879a2c6e..200c481e0 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -48,7 +48,7 @@ import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment' import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; import RetryPolicy = experimental.RetryPolicy; -import { validateBootstrapConfig } from './xds-bootstrap'; +import { BootstrapInfo, loadBootstrapInfo, validateBootstrapConfig } from './xds-bootstrap'; const TRACER_NAME = 'xds_resolver'; @@ -211,6 +211,22 @@ function getDefaultRetryMaxInterval(baseInterval: string): string { return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`; } +function formatTemplateString(templateString: string, value: string): string { + return templateString.replace(/%s/g, value); +} + +function getListenerResourceName(bootstrapConfig: BootstrapInfo, target: GrpcUri): string { + if (target.authority) { + if (target.authority in bootstrapConfig.authorities) { + return formatTemplateString(bootstrapConfig.authorities[target.authority].clientListenerResourceNameTemplate, target.path); + } else { + throw new Error(`Authority ${target.authority} not found in bootstrap file`); + } + } else { + return formatTemplateString(bootstrapConfig.clientDefaultListenerResourceNameTemplate, target.path); + } +} + const BOOTSTRAP_CONFIG_KEY = 'grpc.TEST_ONLY_DO_NOT_USE_IN_PROD.xds_bootstrap_config'; const RETRY_CODES: {[key: string]: status} = { @@ -227,6 +243,7 @@ class XdsResolver implements Resolver { private ldsWatcher: Watcher; private rdsWatcher: Watcher private isLdsWatcherActive = false; + private listenerResourceName: string | null = null; /** * The latest route config name from an LDS response. The RDS watcher is * actively watching that name if and only if this is not null. @@ -573,15 +590,29 @@ class XdsResolver implements Resolver { updateResolution(): void { // Wait until updateResolution is called once to start the xDS requests - if (!this.isLdsWatcherActive) { - trace('Starting resolution for target ' + uriToString(this.target)); - this.xdsClient.addListenerWatcher(this.target.path, this.ldsWatcher); - this.isLdsWatcherActive = true; - } + loadBootstrapInfo().then((bootstrapInfo) => { + if (!this.isLdsWatcherActive) { + trace('Starting resolution for target ' + uriToString(this.target)); + try { + this.listenerResourceName = getListenerResourceName(bootstrapInfo, this.target); + trace('Resolving target ' + uriToString(this.target) + ' with Listener resource name ' + this.listenerResourceName); + this.xdsClient.addListenerWatcher(this.listenerResourceName, this.ldsWatcher); + this.isLdsWatcherActive = true; + + } catch (e) { + this.reportResolutionError(e.message); + } + } + + }, (error) => { + this.reportResolutionError(`${error}`); + }) } destroy() { - this.xdsClient.removeListenerWatcher(this.target.path, this.ldsWatcher); + if (this.listenerResourceName) { + this.xdsClient.removeListenerWatcher(this.listenerResourceName, this.ldsWatcher); + } if (this.latestRouteConfigName) { this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); } diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 72a0ca375..4550ba4a7 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -45,9 +45,16 @@ export interface XdsServerConfig { serverFeatures: string[]; } +export interface Authority { + clientListenerResourceNameTemplate: string; + xdsServers: XdsServerConfig[]; +} + export interface BootstrapInfo { xdsServers: XdsServerConfig[]; node: Node; + authorities: {[authorityName: string]: Authority}; + clientDefaultListenerResourceNameTemplate: string; } function validateChannelCredsConfig(obj: any): ChannelCredsConfig { From 2cb6ef86d4debde64e440d53ddb0f5ae10f228c7 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Fri, 7 Apr 2023 14:45:21 -0700 Subject: [PATCH 474/694] PSM Interop: experiment with qps affect on circuit_breaking ref b/232859415 --- packages/grpc-js-xds/scripts/xds.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index d4490c5ef..af22f584f 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -60,6 +60,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ + --qps=50 \ ${XDS_V3_OPT-} \ --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/github/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ From 546696c366778614de599d64576c3f83fb8af098 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 Apr 2023 10:40:01 -0700 Subject: [PATCH 475/694] grpc-js: Implement federation support --- packages/grpc-js-xds/gulpfile.ts | 1 + packages/grpc-js-xds/src/environment.ts | 3 +- packages/grpc-js-xds/src/load-balancer-cds.ts | 6 +- packages/grpc-js-xds/src/load-balancer-lrs.ts | 4 +- .../src/load-balancer-xds-cluster-impl.ts | 6 +- .../src/load-balancer-xds-cluster-resolver.ts | 6 +- packages/grpc-js-xds/src/resolver-xds.ts | 70 ++- packages/grpc-js-xds/src/xds-bootstrap.ts | 102 ++-- packages/grpc-js-xds/src/xds-client.ts | 542 +++++++++++------- .../test/test-listener-resource-name.ts | 123 ++++ 10 files changed, 581 insertions(+), 282 deletions(-) create mode 100644 packages/grpc-js-xds/test/test-listener-resource-name.ts diff --git a/packages/grpc-js-xds/gulpfile.ts b/packages/grpc-js-xds/gulpfile.ts index 4ee6ac2c5..f2e77b8bd 100644 --- a/packages/grpc-js-xds/gulpfile.ts +++ b/packages/grpc-js-xds/gulpfile.ts @@ -61,6 +61,7 @@ const cleanAll = gulp.parallel(clean); const compile = checkTask(() => execNpmCommand('compile')); const runTests = checkTask(() => { + process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION = 'true'; return gulp.src(`${outDir}/test/**/*.js`) .pipe(mocha({reporter: 'mocha-jenkins-reporter', require: ['ts-node/register']})); diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 7ec0fd187..8bd85b51d 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -17,4 +17,5 @@ export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; -export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; \ No newline at end of file +export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; +export const EXPERIMENTAL_FEDERATION = (process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION ?? 'false') === 'true'; \ No newline at end of file diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 4c1c576f3..d1e8e0de7 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -16,7 +16,7 @@ */ import { connectivityState, status, Metadata, logVerbosity, experimental } from '@grpc/grpc-js'; -import { getSingletonXdsClient, XdsClient } from './xds-client'; +import { getSingletonXdsClient, XdsSingleServerClient } from './xds-client'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import SubchannelAddress = experimental.SubchannelAddress; import UnavailablePicker = experimental.UnavailablePicker; @@ -216,7 +216,7 @@ export class CdsLoadBalancer implements LoadBalancer { private latestConfig: CdsLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown } = {}; - private xdsClient: XdsClient | null = null; + private xdsClient: XdsSingleServerClient | null = null; private clusterTree: ClusterTree = {}; @@ -315,7 +315,7 @@ export class CdsLoadBalancer implements LoadBalancer { } trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); this.latestAttributes = attributes; - this.xdsClient = attributes.xdsClient as XdsClient; + this.xdsClient = attributes.xdsClient as XdsSingleServerClient; /* If the cluster is changing, disable the old watcher before adding the new * one */ diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 9610ea834..24d7bfc5e 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -17,7 +17,7 @@ import { connectivityState as ConnectivityState, StatusObject, status as Status, experimental } from '@grpc/grpc-js'; import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; -import { XdsClusterLocalityStats, XdsClient, getSingletonXdsClient } from './xds-client'; +import { XdsClusterLocalityStats, XdsSingleServerClient, getSingletonXdsClient } from './xds-client'; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; @@ -169,7 +169,7 @@ export class LrsLoadBalancer implements LoadBalancer { if (!(lbConfig instanceof LrsLoadBalancingConfig)) { return; } - this.localityStatsReporter = (attributes.xdsClient as XdsClient).addClusterLocalityStats( + this.localityStatsReporter = (attributes.xdsClient as XdsSingleServerClient).addClusterLocalityStats( lbConfig.getLrsLoadReportingServerName(), lbConfig.getClusterName(), lbConfig.getEdsServiceName(), diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index 16f2cc3c9..6b8d5fae8 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -16,7 +16,7 @@ */ import { experimental, logVerbosity, status as Status, Metadata, connectivityState } from "@grpc/grpc-js"; -import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from "./xds-client"; +import { getSingletonXdsClient, XdsSingleServerClient, XdsClusterDropStats } from "./xds-client"; import LoadBalancingConfig = experimental.LoadBalancingConfig; import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; @@ -222,7 +222,7 @@ class XdsClusterImplBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; private latestConfig: XdsClusterImplLoadBalancingConfig | null = null; private clusterDropStats: XdsClusterDropStats | null = null; - private xdsClient: XdsClient | null = null; + private xdsClient: XdsSingleServerClient | null = null; constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { @@ -243,7 +243,7 @@ class XdsClusterImplBalancer implements LoadBalancer { } trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); this.latestConfig = lbConfig; - this.xdsClient = attributes.xdsClient as XdsClient; + this.xdsClient = attributes.xdsClient as XdsSingleServerClient; if (lbConfig.getLrsLoadReportingServerName()) { this.clusterDropStats = this.xdsClient.addClusterDropStats( diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index 8c01138b5..93977ab45 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -23,7 +23,7 @@ import { ClusterLoadAssignment__Output } from "./generated/envoy/config/endpoint import { LrsLoadBalancingConfig } from "./load-balancer-lrs"; import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from "./load-balancer-priority"; import { WeightedTarget, WeightedTargetLoadBalancingConfig } from "./load-balancer-weighted-target"; -import { getSingletonXdsClient, XdsClient } from "./xds-client"; +import { getSingletonXdsClient, XdsSingleServerClient } from "./xds-client"; import { DropCategory, XdsClusterImplLoadBalancingConfig } from "./load-balancer-xds-cluster-impl"; import { Watcher } from "./xds-stream-state/xds-stream-state"; @@ -243,7 +243,7 @@ export class XdsClusterResolver implements LoadBalancer { private discoveryMechanismList: DiscoveryMechanismEntry[] = []; private latestConfig: XdsClusterResolverLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown; } = {}; - private xdsClient: XdsClient | null = null; + private xdsClient: XdsSingleServerClient | null = null; private childBalancer: ChildLoadBalancerHandler; constructor(private readonly channelControlHelper: ChannelControlHelper) { @@ -368,7 +368,7 @@ export class XdsClusterResolver implements LoadBalancer { trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); this.latestConfig = lbConfig; this.latestAttributes = attributes; - this.xdsClient = attributes.xdsClient as XdsClient; + this.xdsClient = attributes.xdsClient as XdsSingleServerClient; if (this.discoveryMechanismList.length === 0) { for (const mechanism of lbConfig.getDiscoveryMechanisms()) { const mechanismEntry: DiscoveryMechanismEntry = { diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 200c481e0..423ade15c 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -18,7 +18,7 @@ import * as protoLoader from '@grpc/proto-loader'; import { RE2 } from 're2-wasm'; -import { getSingletonXdsClient, XdsClient } from './xds-client'; +import { getSingletonXdsClient, XdsSingleServerClient } from './xds-client'; import { StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions } from '@grpc/grpc-js'; import Resolver = experimental.Resolver; import GrpcUri = experimental.GrpcUri; @@ -44,7 +44,7 @@ import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resourc import Duration = experimental.Duration; import { Duration__Output } from './generated/google/protobuf/Duration'; import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter'; -import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment'; +import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_FEDERATION, EXPERIMENTAL_RETRY } from './environment'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; import RetryPolicy = experimental.RetryPolicy; @@ -211,11 +211,24 @@ function getDefaultRetryMaxInterval(baseInterval: string): string { return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`; } +/** + * Encode a text string as a valid path of a URI, as specified in RFC-3986 section 3.3 + * @param uriPath A value representing an unencoded URI path + * @returns + */ +function encodeURIPath(uriPath: string): string { + return uriPath.replace(/[^A-Za-z0-9._~!$&^()*+,;=/-]/g, substring => encodeURIComponent(substring)); +} + function formatTemplateString(templateString: string, value: string): string { - return templateString.replace(/%s/g, value); + if (templateString.startsWith('xdstp:')) { + return templateString.replace(/%s/g, encodeURIPath(value)); + } else { + return templateString.replace(/%s/g, value); + } } -function getListenerResourceName(bootstrapConfig: BootstrapInfo, target: GrpcUri): string { +export function getListenerResourceName(bootstrapConfig: BootstrapInfo, target: GrpcUri): string { if (target.authority) { if (target.authority in bootstrapConfig.authorities) { return formatTemplateString(bootstrapConfig.authorities[target.authority].clientListenerResourceNameTemplate, target.path); @@ -258,7 +271,9 @@ class XdsResolver implements Resolver { private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = []; - private xdsClient: XdsClient; + private bootstrapInfo: BootstrapInfo | null = null; + + private xdsClient: XdsSingleServerClient; constructor( private target: GrpcUri, @@ -267,8 +282,8 @@ class XdsResolver implements Resolver { ) { if (channelOptions[BOOTSTRAP_CONFIG_KEY]) { const parsedConfig = JSON.parse(channelOptions[BOOTSTRAP_CONFIG_KEY]); - const validatedConfig = validateBootstrapConfig(parsedConfig); - this.xdsClient = new XdsClient(validatedConfig); + this.bootstrapInfo = validateBootstrapConfig(parsedConfig); + this.xdsClient = new XdsSingleServerClient(this.bootstrapInfo); } else { this.xdsClient = getSingletonXdsClient(); } @@ -588,25 +603,40 @@ class XdsResolver implements Resolver { }); } + private startResolution(): void { + if (!this.isLdsWatcherActive) { + trace('Starting resolution for target ' + uriToString(this.target)); + try { + this.listenerResourceName = getListenerResourceName(this.bootstrapInfo!, this.target); + trace('Resolving target ' + uriToString(this.target) + ' with Listener resource name ' + this.listenerResourceName); + this.xdsClient.addListenerWatcher(this.listenerResourceName, this.ldsWatcher); + this.isLdsWatcherActive = true; + + } catch (e) { + this.reportResolutionError(e.message); + } + } + } + updateResolution(): void { - // Wait until updateResolution is called once to start the xDS requests - loadBootstrapInfo().then((bootstrapInfo) => { - if (!this.isLdsWatcherActive) { - trace('Starting resolution for target ' + uriToString(this.target)); + if (EXPERIMENTAL_FEDERATION) { + if (this.bootstrapInfo) { + this.startResolution(); + } else { try { - this.listenerResourceName = getListenerResourceName(bootstrapInfo, this.target); - trace('Resolving target ' + uriToString(this.target) + ' with Listener resource name ' + this.listenerResourceName); - this.xdsClient.addListenerWatcher(this.listenerResourceName, this.ldsWatcher); - this.isLdsWatcherActive = true; - + this.bootstrapInfo = loadBootstrapInfo(); } catch (e) { this.reportResolutionError(e.message); } + this.startResolution(); } - - }, (error) => { - this.reportResolutionError(`${error}`); - }) + } else { + if (!this.isLdsWatcherActive) { + trace('Starting resolution for target ' + uriToString(this.target)); + this.xdsClient.addListenerWatcher(this.target.path, this.ldsWatcher); + this.isLdsWatcherActive = true; + } + } } destroy() { diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 4550ba4a7..d905a6d55 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -16,6 +16,7 @@ */ import * as fs from 'fs'; +import { EXPERIMENTAL_FEDERATION } from './environment'; import { Struct } from './generated/google/protobuf/Struct'; import { Value } from './generated/google/protobuf/Value'; @@ -47,7 +48,7 @@ export interface XdsServerConfig { export interface Authority { clientListenerResourceNameTemplate: string; - xdsServers: XdsServerConfig[]; + xdsServers?: XdsServerConfig[]; } export interface BootstrapInfo { @@ -238,16 +239,60 @@ function validateNode(obj: any): Node { return result; } -export function validateBootstrapConfig(obj: any): BootstrapInfo { +function validateAuthority(obj: any, authorityName: string): Authority { + if ('client_listener_resource_name_template' in obj) { + if (typeof obj.client_listener_resource_name_template !== 'string') { + throw new Error(`authorities[${authorityName}].client_listener_resource_name_template: expected string, got ${typeof obj.client_listener_resource_name_template}`); + } + if (!obj.client_listener_resource_name_template.startsWith(`xdstp://${authorityName}/`)) { + throw new Error(`authorities[${authorityName}].client_listener_resource_name_template must start with "xdstp://${authorityName}/"`); + } + } return { - xdsServers: obj.xds_servers.map(validateXdsServerConfig), - node: validateNode(obj.node), + clientListenerResourceNameTemplate: obj.client_listener_resource_name_template ?? `xdstp://${authorityName}/envoy.config.listener.v3.Listener/%s`, + xdsServers: obj.xds_servers?.map(validateXdsServerConfig) }; } -let loadedBootstrapInfo: Promise | null = null; +function validateAuthoritiesMap(obj: any): {[authorityName: string]: Authority} { + if (!obj) { + return {}; + } + const result: {[authorityName: string]: Authority} = {}; + for (const [name, authority] of Object.entries(obj)) { + result[name] = validateAuthority(authority, name); + } + return result; +} + +export function validateBootstrapConfig(obj: any): BootstrapInfo { + const xdsServers = obj.xds_servers.map(validateXdsServerConfig); + const node = validateNode(obj.node); + if (EXPERIMENTAL_FEDERATION) { + if ('client_default_listener_resource_name_template' in obj) { + if (typeof obj.client_default_listener_resource_name_template !== 'string') { + throw new Error(`client_default_listener_resource_name_template: expected string, got ${typeof obj.client_default_listener_resource_name_template}`); + } + } + return { + xdsServers: xdsServers, + node: node, + authorities: validateAuthoritiesMap(obj.authorities), + clientDefaultListenerResourceNameTemplate: obj.client_default_listener_resource_name_template ?? '%s' + }; + } else { + return { + xdsServers: xdsServers, + node: node, + authorities: {}, + clientDefaultListenerResourceNameTemplate: '%s' + }; + } +} + +let loadedBootstrapInfo: BootstrapInfo | null = null; -export async function loadBootstrapInfo(): Promise { +export function loadBootstrapInfo(): BootstrapInfo { if (loadedBootstrapInfo !== null) { return loadedBootstrapInfo; } @@ -261,28 +306,19 @@ export async function loadBootstrapInfo(): Promise { */ const bootstrapPath = process.env.GRPC_XDS_BOOTSTRAP; if (bootstrapPath) { - loadedBootstrapInfo = new Promise((resolve, reject) => { - fs.readFile(bootstrapPath, { encoding: 'utf8' }, (err, data) => { - if (err) { - reject( - new Error( - `Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${err.message}` - ) - ); - } - try { - const parsedFile = JSON.parse(data); - resolve(validateBootstrapConfig(parsedFile)); - } catch (e) { - reject( - new Error( - `Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${e.message}` - ) - ); - } - }); - }); - return loadedBootstrapInfo; + let rawBootstrap: string; + try { + rawBootstrap = fs.readFileSync(bootstrapPath, { encoding: 'utf8'}); + } catch (e) { + throw new Error(`Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${e.message}`); + } + try { + const parsedFile = JSON.parse(rawBootstrap); + loadedBootstrapInfo = validateBootstrapConfig(parsedFile); + return loadedBootstrapInfo; + } catch (e) { + throw new Error(`Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${e.message}`) + } } /** @@ -297,8 +333,7 @@ export async function loadBootstrapInfo(): Promise { if (bootstrapConfig) { try { const parsedConfig = JSON.parse(bootstrapConfig); - const loadedBootstrapInfoValue = validateBootstrapConfig(parsedConfig); - loadedBootstrapInfo = Promise.resolve(loadedBootstrapInfoValue); + loadedBootstrapInfo = validateBootstrapConfig(parsedConfig); } catch (e) { throw new Error( `Failed to parse xDS bootstrap config from environment variable GRPC_XDS_BOOTSTRAP_CONFIG with error ${e.message}` @@ -308,9 +343,8 @@ export async function loadBootstrapInfo(): Promise { return loadedBootstrapInfo; } - return Promise.reject( - new Error( - 'The GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG environment variables need to be set to the path to the bootstrap file to use xDS' - ) + + throw new Error( + 'The GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG environment variables need to be set to the path to the bootstrap file to use xDS' ); } diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 20c914a1c..e49d1fe18 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -21,7 +21,7 @@ import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel, connectivityState } from '@grpc/grpc-js'; import * as adsTypes from './generated/ads'; import * as lrsTypes from './generated/lrs'; -import { BootstrapInfo, loadBootstrapInfo } from './xds-bootstrap'; +import { BootstrapInfo, loadBootstrapInfo, XdsServerConfig } from './xds-bootstrap'; import { Node } from './generated/envoy/config/core/v3/Node'; import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; import { DiscoveryRequest } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; @@ -56,18 +56,14 @@ function trace(text: string): void { const clientVersion = require('../../package.json').version; -let loadedProtos: Promise< - adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType -> | null = null; +let loadedProtos: adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType | null = null; -function loadAdsProtos(): Promise< - adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType -> { +function loadAdsProtos(): adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType { if (loadedProtos !== null) { return loadedProtos; } - loadedProtos = protoLoader - .load( + return (loadPackageDefinition(protoLoader + .loadSync( [ 'envoy/service/discovery/v3/ads.proto', 'envoy/service/load_stats/v3/lrs.proto', @@ -87,14 +83,7 @@ function loadAdsProtos(): Promise< __dirname + '/../../deps/protoc-gen-validate/', ], } - ) - .then( - (packageDefinition) => - (loadPackageDefinition( - packageDefinition - ) as unknown) as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType - ); - return loadedProtos; + )) as unknown) as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType; } function localityEqual( @@ -247,9 +236,12 @@ function getResponseMessages( return result; } -export class XdsClient { +class XdsSingleServerClient { - private adsNode: Node | null = null; + private adsNode: Node; + /* These client objects need to be nullable so that they can be shut down + * when not in use. If the channel could enter the IDLE state it would remove + * that need. */ private adsClient: AggregatedDiscoveryServiceClient | null = null; private adsCall: ClientDuplexStream< DiscoveryRequest, @@ -257,7 +249,7 @@ export class XdsClient { > | null = null; private receivedAdsResponseOnCurrentStream = false; - private lrsNode: Node | null = null; + private lrsNode: Node; private lrsClient: LoadReportingServiceClient | null = null; private lrsCall: ClientDuplexStream< LoadStatsRequest, @@ -269,14 +261,12 @@ export class XdsClient { private clusterStatsMap: ClusterLoadReportMap = new ClusterLoadReportMap(); private statsTimer: NodeJS.Timer; - private hasShutdown = false; - private adsState: AdsState; private adsBackoff: BackoffTimeout; private lrsBackoff: BackoffTimeout; - constructor(bootstrapInfoOverride?: BootstrapInfo) { + constructor(bootstrapNode: Node, private xdsServerConfig: XdsServerConfig) { const edsState = new EdsState(() => { this.updateNames('eds'); }); @@ -296,11 +286,6 @@ export class XdsClient { lds: ldsState, }; - const channelArgs = { - // 5 minutes - 'grpc.keepalive_time_ms': 5 * 60 * 1000 - } - this.adsBackoff = new BackoffTimeout(() => { this.maybeStartAdsStream(); }); @@ -309,103 +294,32 @@ export class XdsClient { this.maybeStartLrsStream(); }); this.lrsBackoff.unref(); - - async function getBootstrapInfo(): Promise { - if (bootstrapInfoOverride) { - return bootstrapInfoOverride; - } else { - return loadBootstrapInfo(); - } - } - - Promise.all([getBootstrapInfo(), loadAdsProtos()]).then( - ([bootstrapInfo, protoDefinitions]) => { - if (this.hasShutdown) { - return; - } - trace('Loaded bootstrap info: ' + JSON.stringify(bootstrapInfo, undefined, 2)); - if (bootstrapInfo.xdsServers.length < 1) { - trace('Failed to initialize xDS Client. No servers provided in bootstrap info.'); - // Bubble this error up to any listeners - this.reportStreamError({ - code: status.INTERNAL, - details: 'Failed to initialize xDS Client. No servers provided in bootstrap info.', - metadata: new Metadata(), - }); - return; - } - if (bootstrapInfo.xdsServers[0].serverFeatures.indexOf('ignore_resource_deletion') >= 0) { - this.adsState.lds.enableIgnoreResourceDeletion(); - this.adsState.cds.enableIgnoreResourceDeletion(); - } - const userAgentName = 'gRPC Node Pure JS'; - this.adsNode = { - ...bootstrapInfo.node, - user_agent_name: userAgentName, - user_agent_version: clientVersion, - client_features: ['envoy.lb.does_not_support_overprovisioning'], - }; - this.lrsNode = { - ...bootstrapInfo.node, - user_agent_name: userAgentName, - user_agent_version: clientVersion, - client_features: ['envoy.lrs.supports_send_all_clusters'], - }; - setCsdsClientNode(this.adsNode); - trace('ADS Node: ' + JSON.stringify(this.adsNode, undefined, 2)); - trace('LRS Node: ' + JSON.stringify(this.lrsNode, undefined, 2)); - const credentialsConfigs = bootstrapInfo.xdsServers[0].channelCreds; - let channelCreds: ChannelCredentials | null = null; - for (const config of credentialsConfigs) { - if (config.type === 'google_default') { - channelCreds = createGoogleDefaultCredentials(); - break; - } else if (config.type === 'insecure') { - channelCreds = ChannelCredentials.createInsecure(); - break; - } - } - if (channelCreds === null) { - trace('Failed to initialize xDS Client. No valid credentials types found.'); - // Bubble this error up to any listeners - this.reportStreamError({ - code: status.INTERNAL, - details: 'Failed to initialize xDS Client. No valid credentials types found.', - metadata: new Metadata(), - }); - return; - } - const serverUri = bootstrapInfo.xdsServers[0].serverUri - trace('Starting xDS client connected to server URI ' + bootstrapInfo.xdsServers[0].serverUri); - const channel = new Channel(serverUri, channelCreds, channelArgs); - this.adsClient = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( - serverUri, - channelCreds, - {channelOverride: channel} - ); - this.maybeStartAdsStream(); - channel.watchConnectivityState(channel.getConnectivityState(false), Infinity, () => { - this.handleAdsConnectivityStateUpdate(); - }) - - this.lrsClient = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( - serverUri, - channelCreds, - {channelOverride: channel} - ); - this.maybeStartLrsStream(); - }).catch((error) => { - trace('Failed to initialize xDS Client. ' + error.message); - // Bubble this error up to any listeners - this.reportStreamError({ - code: status.INTERNAL, - details: `Failed to initialize xDS Client. ${error.message}`, - metadata: new Metadata(), - }); - } - ); this.statsTimer = setInterval(() => {}, 0); clearInterval(this.statsTimer); + if (xdsServerConfig.serverFeatures.indexOf('ignore_resource_deletion') >= 0) { + this.adsState.lds.enableIgnoreResourceDeletion(); + this.adsState.cds.enableIgnoreResourceDeletion(); + } + const userAgentName = 'gRPC Node Pure JS'; + this.adsNode = { + ...bootstrapNode, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lb.does_not_support_overprovisioning'], + }; + this.lrsNode = { + ...bootstrapNode, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lrs.supports_send_all_clusters'], + }; + setCsdsClientNode(this.adsNode); + this.trace('ADS Node: ' + JSON.stringify(this.adsNode, undefined, 2)); + this.trace('LRS Node: ' + JSON.stringify(this.lrsNode, undefined, 2)); + } + + private trace(text: string) { + trace(this.xdsServerConfig.serverUri + ' ' + text); } private handleAdsConnectivityStateUpdate() { @@ -471,24 +385,24 @@ export class XdsClient { break; } } catch (e) { - trace('Nacking message with protobuf parsing error: ' + e.message); + this.trace('Nacking message with protobuf parsing error: ' + e.message); this.nack(message.type_url, e.message); return; } if (handleResponseResult === null) { // Null handleResponseResult means that the type_url was unrecognized - trace('Nacking message with unknown type URL ' + message.type_url); + this.trace('Nacking message with unknown type URL ' + message.type_url); this.nack(message.type_url, `Unknown type_url ${message.type_url}`); } else { updateCsdsResourceResponse(message.type_url as AdsTypeUrl, message.version_info, handleResponseResult.result); if (handleResponseResult.result.rejected.length > 0) { // rejected.length > 0 means that at least one message validation failed const errorString = `${handleResponseResult.serviceKind.toUpperCase()} Error: ${handleResponseResult.result.rejected[0].error}`; - trace('Nacking message with type URL ' + message.type_url + ': ' + errorString); + this.trace('Nacking message with type URL ' + message.type_url + ': ' + errorString); this.nack(message.type_url, errorString); } else { // If we get here, all message validation succeeded - trace('Acking message with type URL ' + message.type_url); + this.trace('Acking message with type URL ' + message.type_url); const serviceKind = handleResponseResult.serviceKind; this.adsState[serviceKind].nonce = message.nonce; this.adsState[serviceKind].versionInfo = message.version_info; @@ -497,8 +411,60 @@ export class XdsClient { } } + private maybeCreateClients() { + if (this.adsClient !== null && this.lrsClient !== null) { + return; + } + const channelArgs = { + // 5 minutes + 'grpc.keepalive_time_ms': 5 * 60 * 1000 + } + const credentialsConfigs = this.xdsServerConfig.channelCreds; + let channelCreds: ChannelCredentials | null = null; + for (const config of credentialsConfigs) { + if (config.type === 'google_default') { + channelCreds = createGoogleDefaultCredentials(); + break; + } else if (config.type === 'insecure') { + channelCreds = ChannelCredentials.createInsecure(); + break; + } + } + if (channelCreds === null) { + this.trace('Failed to initialize xDS Client. No valid credentials types found.'); + // Bubble this error up to any listeners + this.reportStreamError({ + code: status.INTERNAL, + details: 'Failed to initialize xDS Client. No valid credentials types found.', + metadata: new Metadata(), + }); + return; + } + const serverUri = this.xdsServerConfig.serverUri + this.trace('Starting xDS client connected to server URI ' + this.xdsServerConfig.serverUri); + const channel = new Channel(serverUri, channelCreds, channelArgs); + const protoDefinitions = loadAdsProtos(); + if (this.adsClient === null) { + this.adsClient = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( + serverUri, + channelCreds, + {channelOverride: channel} + ); + channel.watchConnectivityState(channel.getConnectivityState(false), Infinity, () => { + this.handleAdsConnectivityStateUpdate(); + }); + } + if (this.lrsClient === null) { + this.lrsClient = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( + serverUri, + channelCreds, + {channelOverride: channel} + ); + } + } + private handleAdsCallStatus(streamStatus: StatusObject) { - trace( + this.trace( 'ADS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details ); this.adsCall = null; @@ -512,29 +478,28 @@ export class XdsClient { } } + private hasOutstandingResourceRequests() { + return (this.adsState.eds.getResourceNames().length > 0 || + this.adsState.cds.getResourceNames().length > 0 || + this.adsState.rds.getResourceNames().length > 0 || + this.adsState.lds.getResourceNames().length); + } + /** * Start the ADS stream if the client exists and there is not already an * existing stream, and there are resources to request. */ private maybeStartAdsStream() { - if (this.hasShutdown) { - return; - } - if (this.adsState.eds.getResourceNames().length === 0 && - this.adsState.cds.getResourceNames().length === 0 && - this.adsState.rds.getResourceNames().length === 0 && - this.adsState.lds.getResourceNames().length === 0) { - return; - } - if (this.adsClient === null) { + if (!this.hasOutstandingResourceRequests()) { return; } if (this.adsCall !== null) { return; } + this.maybeCreateClients(); this.receivedAdsResponseOnCurrentStream = false; const metadata = new Metadata({waitForReady: true}); - this.adsCall = this.adsClient.StreamAggregatedResources(metadata); + this.adsCall = this.adsClient!.StreamAggregatedResources(metadata); this.adsCall.on('data', (message: DiscoveryResponse__Output) => { this.handleAdsResponse(message); }); @@ -542,7 +507,7 @@ export class XdsClient { this.handleAdsCallStatus(status); }); this.adsCall.on('error', () => {}); - trace('Started ADS stream'); + this.trace('Started ADS stream'); // Backoff relative to when we start the request this.adsBackoff.runOnce(); @@ -553,7 +518,7 @@ export class XdsClient { this.updateNames(service); } } - if (this.adsClient.getChannel().getConnectivityState(false) === connectivityState.READY) { + if (this.adsClient!.getChannel().getConnectivityState(false) === connectivityState.READY) { this.reportAdsStreamStarted(); } } @@ -586,7 +551,7 @@ export class XdsClient { * Acknowledge an update. This should be called after the local nonce and * version info are updated so that it sends the post-update values. */ - ack(serviceKind: AdsServiceKind) { + private ack(serviceKind: AdsServiceKind) { this.updateNames(serviceKind); } @@ -633,15 +598,20 @@ export class XdsClient { this.maybeSendAdsMessage(typeUrl, resourceNames, nonce, versionInfo, message); } + private shutdown() { + this.adsCall?.end(); + this.adsCall = null; + this.lrsCall?.end(); + this.lrsCall = null; + this.adsClient?.close(); + this.adsClient = null; + this.lrsClient?.close(); + this.lrsClient = null; + } + private updateNames(serviceKind: AdsServiceKind) { - if (this.adsState.eds.getResourceNames().length === 0 && - this.adsState.cds.getResourceNames().length === 0 && - this.adsState.rds.getResourceNames().length === 0 && - this.adsState.lds.getResourceNames().length === 0) { - this.adsCall?.end(); - this.adsCall = null; - this.lrsCall?.end(); - this.lrsCall = null; + if (!this.hasOutstandingResourceRequests()) { + this.shutdown(); return; } this.maybeStartAdsStream(); @@ -653,7 +623,7 @@ export class XdsClient { * of getTypeUrl is garbage and everything after that is invalid. */ return; } - trace('Sending update for ' + serviceKind + ' with names ' + this.adsState[serviceKind].getResourceNames()); + this.trace('Sending update for ' + serviceKind + ' with names ' + this.adsState[serviceKind].getResourceNames()); const typeUrl = this.getTypeUrl(serviceKind); updateCsdsRequestedNameList(typeUrl, this.adsState[serviceKind].getResourceNames()); this.maybeSendAdsMessage(typeUrl, this.adsState[serviceKind].getResourceNames(), this.adsState[serviceKind].nonce, this.adsState[serviceKind].versionInfo); @@ -675,7 +645,7 @@ export class XdsClient { } private handleLrsResponse(message: LoadStatsResponse__Output) { - trace('Received LRS response'); + this.trace('Received LRS response'); /* Once we get any response from the server, we assume that the stream is * in a good state, so we can reset the backoff timer. */ this.lrsBackoff.reset(); @@ -694,7 +664,7 @@ export class XdsClient { const loadReportingIntervalMs = Number.parseInt(message.load_reporting_interval!.seconds) * 1000 + message.load_reporting_interval!.nanos / 1_000_000; - trace('Received LRS response with load reporting interval ' + loadReportingIntervalMs + ' ms'); + this.trace('Received LRS response with load reporting interval ' + loadReportingIntervalMs + ' ms'); this.statsTimer = setInterval(() => { this.sendStats(); }, loadReportingIntervalMs); @@ -704,7 +674,7 @@ export class XdsClient { } private handleLrsCallStatus(streamStatus: StatusObject) { - trace( + this.trace( 'LRS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details ); this.lrsCall = null; @@ -716,42 +686,15 @@ export class XdsClient { } } - private maybeStartLrsStreamV3(): boolean { - if (!this.lrsClient) { - return false; - } - if (this.lrsCall) { - return false; - } - this.lrsCall = this.lrsClient.streamLoadStats(); - this.receivedLrsSettingsForCurrentStream = false; - this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { - this.handleLrsResponse(message); - }); - this.lrsCall.on('status', (status: StatusObject) => { - this.handleLrsCallStatus(status); - }); - this.lrsCall.on('error', () => {}); - return true; - } - private maybeStartLrsStream() { - if (this.hasShutdown) { - return; - } - if (this.adsState.eds.getResourceNames().length === 0 && - this.adsState.cds.getResourceNames().length === 0 && - this.adsState.rds.getResourceNames().length === 0 && - this.adsState.lds.getResourceNames().length === 0) { - return; - } - if (!this.lrsClient) { + if (!this.hasOutstandingResourceRequests()) { return; } if (this.lrsCall) { return; } - this.lrsCall = this.lrsClient.streamLoadStats(); + this.maybeCreateClients(); + this.lrsCall = this.lrsClient!.streamLoadStats(); this.receivedLrsSettingsForCurrentStream = false; this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { this.handleLrsResponse(message); @@ -760,7 +703,7 @@ export class XdsClient { this.handleLrsCallStatus(status); }); this.lrsCall.on('error', () => {}); - trace('Starting LRS stream'); + this.trace('Starting LRS stream'); this.lrsBackoff.runOnce(); /* Send buffered stats information when starting LRS stream. If there is no * buffered stats information, it will still send the node field. */ @@ -844,7 +787,7 @@ export class XdsClient { } } } - trace('Sending LRS stats ' + JSON.stringify(clusterStats, undefined, 2)); + this.trace('Sending LRS stats ' + JSON.stringify(clusterStats, undefined, 2)); this.maybeSendLrsMessage(clusterStats); } @@ -852,7 +795,7 @@ export class XdsClient { edsServiceName: string, watcher: Watcher ) { - trace('Watcher added for endpoint ' + edsServiceName); + this.trace('Watcher added for endpoint ' + edsServiceName); this.adsState.eds.addWatcher(edsServiceName, watcher); } @@ -860,37 +803,37 @@ export class XdsClient { edsServiceName: string, watcher: Watcher ) { - trace('Watcher removed for endpoint ' + edsServiceName); + this.trace('Watcher removed for endpoint ' + edsServiceName); this.adsState.eds.removeWatcher(edsServiceName, watcher); } addClusterWatcher(clusterName: string, watcher: Watcher) { - trace('Watcher added for cluster ' + clusterName); + this.trace('Watcher added for cluster ' + clusterName); this.adsState.cds.addWatcher(clusterName, watcher); } removeClusterWatcher(clusterName: string, watcher: Watcher) { - trace('Watcher removed for cluster ' + clusterName); + this.trace('Watcher removed for cluster ' + clusterName); this.adsState.cds.removeWatcher(clusterName, watcher); } addRouteWatcher(routeConfigName: string, watcher: Watcher) { - trace('Watcher added for route ' + routeConfigName); + this.trace('Watcher added for route ' + routeConfigName); this.adsState.rds.addWatcher(routeConfigName, watcher); } removeRouteWatcher(routeConfigName: string, watcher: Watcher) { - trace('Watcher removed for route ' + routeConfigName); + this.trace('Watcher removed for route ' + routeConfigName); this.adsState.rds.removeWatcher(routeConfigName, watcher); } addListenerWatcher(targetName: string, watcher: Watcher) { - trace('Watcher added for listener ' + targetName); + this.trace('Watcher added for listener ' + targetName); this.adsState.lds.addWatcher(targetName, watcher); } removeListenerWatcher(targetName: string, watcher: Watcher) { - trace('Watcher removed for listener ' + targetName); + this.trace('Watcher removed for listener ' + targetName); this.adsState.lds.removeWatcher(targetName, watcher); } @@ -903,17 +846,10 @@ export class XdsClient { * @param edsServiceName */ addClusterDropStats( - lrsServer: string, clusterName: string, edsServiceName: string ): XdsClusterDropStats { - trace('addClusterDropStats(lrsServer=' + lrsServer + ', clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ')'); - if (lrsServer !== '') { - return { - addUncategorizedCallDropped: () => {}, - addCallDropped: (category) => {}, - }; - } + this.trace('addClusterDropStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ')'); const clusterStats = this.clusterStatsMap.getOrCreate( clusterName, edsServiceName @@ -930,18 +866,11 @@ export class XdsClient { } addClusterLocalityStats( - lrsServer: string, clusterName: string, edsServiceName: string, locality: Locality__Output ): XdsClusterLocalityStats { - trace('addClusterLocalityStats(lrsServer=' + lrsServer + ', clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ', locality=' + JSON.stringify(locality) + ')'); - if (lrsServer !== '') { - return { - addCallStarted: () => {}, - addCallFinished: (fail) => {}, - }; - } + this.trace('addClusterLocalityStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ', locality=' + JSON.stringify(locality) + ')'); const clusterStats = this.clusterStatsMap.getOrCreate( clusterName, edsServiceName @@ -981,13 +910,194 @@ export class XdsClient { }, }; } +} - private shutdown(): void { - this.adsCall?.cancel(); - this.adsClient?.close(); - this.lrsCall?.cancel(); - this.lrsClient?.close(); - this.hasShutdown = true; +const KNOWN_SERVER_FEATURES = ['ignore_resource_deletion']; + +function serverConfigEqual(config1: XdsServerConfig, config2: XdsServerConfig): boolean { + if (config1.serverUri !== config2.serverUri) { + return false; + } + for (const feature of KNOWN_SERVER_FEATURES) { + if ((feature in config1.serverFeatures) !== (feature in config2.serverFeatures)) { + return false; + } + } + if (config1.channelCreds.length !== config2.channelCreds.length) { + return false; + } + for (const [index, creds1] of config1.channelCreds.entries()) { + const creds2 = config2.channelCreds[index]; + if (creds1.type !== creds2.type) { + return false; + } + if (JSON.stringify(creds1) !== JSON.stringify(creds2)) { + return false; + } + } + return true; +} + +interface ClientMapEntry { + serverConfig: XdsServerConfig; + client: XdsSingleServerClient; +} + +export class XdsClient { + private clientMap: ClientMapEntry[] = []; + + constructor(private bootstrapInfoOverride?: BootstrapInfo) {} + + private getBootstrapInfo() { + if (this.bootstrapInfoOverride) { + return this.bootstrapInfoOverride; + } else { + return loadBootstrapInfo(); + } + } + + private getClient(serverConfig: XdsServerConfig): XdsSingleServerClient | null { + for (const entry of this.clientMap) { + if (serverConfigEqual(serverConfig, entry.serverConfig)) { + return entry.client; + } + } + return null; + } + + private getOrCreateClientForResource(resourceName: string): XdsSingleServerClient { + const bootstrapInfo = this.getBootstrapInfo(); + let serverConfig: XdsServerConfig; + if (resourceName.startsWith('xdstp:')) { + const match = resourceName.match(/xdstp:\/\/([^/]+)\//); + if (!match) { + throw new Error(`Parse error: Resource ${resourceName} has no authority`); + } + const authority = match[1]; + if (authority in bootstrapInfo.authorities) { + serverConfig = bootstrapInfo.authorities[authority].xdsServers?.[0] ?? bootstrapInfo.xdsServers[0]; + } else { + throw new Error(`Authority ${authority} in resource ${resourceName} not found in authorities list`); + } + } else { + serverConfig = bootstrapInfo.xdsServers[0]; + } + for (const entry of this.clientMap) { + if (serverConfigEqual(serverConfig, entry.serverConfig)) { + return entry.client; + } + } + const newClient = new XdsSingleServerClient(bootstrapInfo.node, serverConfig); + this.clientMap.push({serverConfig: serverConfig, client: newClient}); + return newClient; + } + + addEndpointWatcher( + edsServiceName: string, + watcher: Watcher + ) { + trace('addEndpointWatcher(' + edsServiceName + ')'); + try { + const client = this.getOrCreateClientForResource(edsServiceName); + client.addEndpointWatcher(edsServiceName, watcher); + } catch (e) { + trace('addEndpointWatcher error: ' + e.message); + watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + } + } + + removeEndpointWatcher( + edsServiceName: string, + watcher: Watcher + ) { + trace('removeEndpointWatcher(' + edsServiceName + ')'); + try { + const client = this.getOrCreateClientForResource(edsServiceName); + client.removeEndpointWatcher(edsServiceName, watcher); + } catch (e) { + } + } + + addClusterWatcher(clusterName: string, watcher: Watcher) { + trace('addClusterWatcher(' + clusterName + ')'); + try { + const client = this.getOrCreateClientForResource(clusterName); + client.addClusterWatcher(clusterName, watcher); + } catch (e) { + trace('addClusterWatcher error: ' + e.message); + watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + } + } + + removeClusterWatcher(clusterName: string, watcher: Watcher) { + trace('removeClusterWatcher(' + clusterName + ')'); + try { + const client = this.getOrCreateClientForResource(clusterName); + client.removeClusterWatcher(clusterName, watcher); + } catch (e) { + } + } + + addRouteWatcher(routeConfigName: string, watcher: Watcher) { + trace('addRouteWatcher(' + routeConfigName + ')'); + try { + const client = this.getOrCreateClientForResource(routeConfigName); + client.addRouteWatcher(routeConfigName, watcher); + } catch (e) { + trace('addRouteWatcher error: ' + e.message); + watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + } + } + + removeRouteWatcher(routeConfigName: string, watcher: Watcher) { + trace('removeRouteWatcher(' + routeConfigName + ')'); + try { + const client = this.getOrCreateClientForResource(routeConfigName); + client.removeRouteWatcher(routeConfigName, watcher); + } catch (e) { + } + } + + addListenerWatcher(targetName: string, watcher: Watcher) { + trace('addListenerWatcher(' + targetName + ')'); + try { + const client = this.getOrCreateClientForResource(targetName); + client.addListenerWatcher(targetName, watcher); + } catch (e) { + trace('addListenerWatcher error: ' + e.message); + watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + } + } + + removeListenerWatcher(targetName: string, watcher: Watcher) { + trace('removeListenerWatcher' + targetName); + try { + const client = this.getOrCreateClientForResource(targetName); + client.removeListenerWatcher(targetName, watcher); + } catch (e) { + } + } + + addClusterDropStats(lrsServer: XdsServerConfig, clusterName: string, edsServiceName: string): XdsClusterDropStats { + const client = this.getClient(lrsServer); + if (!client) { + return { + addUncategorizedCallDropped: () => {}, + addCallDropped: (category) => {}, + }; + } + return client.addClusterDropStats(clusterName, edsServiceName); + } + + addClusterLocalityStats(lrsServer: XdsServerConfig, clusterName: string, edsServiceName: string, locality: Locality__Output): XdsClusterLocalityStats { + const client = this.getClient(lrsServer); + if (!client) { + return { + addCallStarted: () => {}, + addCallFinished: (fail) => {}, + }; + } + return client.addClusterLocalityStats(clusterName, edsServiceName, locality); } } diff --git a/packages/grpc-js-xds/test/test-listener-resource-name.ts b/packages/grpc-js-xds/test/test-listener-resource-name.ts new file mode 100644 index 000000000..1359d0485 --- /dev/null +++ b/packages/grpc-js-xds/test/test-listener-resource-name.ts @@ -0,0 +1,123 @@ +/* + * 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. + * + */ + +import { BootstrapInfo, Node, validateBootstrapConfig } from "../src/xds-bootstrap"; +import { experimental } from "@grpc/grpc-js"; +import * as assert from 'assert'; +import GrpcUri = experimental.GrpcUri; +import { getListenerResourceName } from "../src/resolver-xds"; + +const testNode: Node = { + id: 'test', + locality: {} +}; + +describe('Listener resource name evaluation', () => { + describe('No new bootstrap fields', () => { + const bootstrap = validateBootstrapConfig({ + node: testNode, + xds_servers: [] + }); + it('xds:server.example.com', () => { + const target: GrpcUri = { + scheme: 'xds', + path: 'server.example.com' + }; + assert.strictEqual(getListenerResourceName(bootstrap, target), 'server.example.com'); + }); + it('xds://xds.authority.com/server.example.com', () => { + const target: GrpcUri = { + scheme: 'xds', + authority: 'xds.authority.com', + path: 'server.example.com' + }; + assert.throws(() => getListenerResourceName(bootstrap, target), /xds.authority.com/); + }); + }); + describe('New-style names', () => { + const bootstrap = validateBootstrapConfig({ + node: testNode, + xds_servers: [], + client_default_listener_resource_name_template: 'xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%s', + authorities: { + 'xds.authority.com': {} + } + }); + it('xds:server.example.com', () => { + const target: GrpcUri = { + scheme: 'xds', + path: 'server.example.com' + }; + assert.strictEqual(getListenerResourceName(bootstrap, target), 'xdstp://xds.authority.com/envoy.config.listener.v3.Listener/server.example.com'); + }); + it('xds://xds.authority.com/server.example.com', () => { + const target: GrpcUri = { + scheme: 'xds', + authority: 'xds.authority.com', + path: 'server.example.com' + }; + assert.strictEqual(getListenerResourceName(bootstrap, target), 'xdstp://xds.authority.com/envoy.config.listener.v3.Listener/server.example.com'); + }); + }); + describe('Multiple authorities', () => { + const bootstrap = validateBootstrapConfig({ + node: testNode, + xds_servers: [{ + "server_uri": "xds-server.authority.com", + "channel_creds": [ { "type": "google_default" } ] + }], + client_default_listener_resource_name_template: 'xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/client/%s?project_id=1234', + authorities: { + "xds.authority.com": { + "client_listener_resource_name_template": "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/client/%s?project_id=1234" + }, + + "xds.other.com": { + "xds_servers": [ + { + "server_uri": "xds-server.other.com", + "channel_creds": [ { "type": "google_default" } ] + } + ] + } + } + }); + it('xds:server.example.com', () => { + const target: GrpcUri = { + scheme: 'xds', + path: 'server.example.com' + }; + assert.strictEqual(getListenerResourceName(bootstrap, target), 'xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/client/server.example.com?project_id=1234'); + }); + it('xds://xds.authority.com/server.example.com', () => { + const target: GrpcUri = { + scheme: 'xds', + authority: 'xds.authority.com', + path: 'server.example.com' + }; + assert.strictEqual(getListenerResourceName(bootstrap, target), 'xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/client/server.example.com?project_id=1234'); + }); + it('xds://xds.other.com/server.other.com', () => { + const target: GrpcUri = { + scheme: 'xds', + authority: 'xds.other.com', + path: 'server.other.com' + }; + assert.strictEqual(getListenerResourceName(bootstrap, target), 'xdstp://xds.other.com/envoy.config.listener.v3.Listener/server.other.com'); + }); + }); +}); \ No newline at end of file From 856559cce1d27771d50dc97d130f5dc423dd4723 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 Apr 2023 14:34:06 -0700 Subject: [PATCH 476/694] grpc-js-xds: Fix handling of resource validation errors --- .../src/xds-stream-state/xds-stream-state.ts | 46 +++--- packages/grpc-js-xds/test/test-nack.ts | 156 ++++++++++++++++++ 2 files changed, 182 insertions(+), 20 deletions(-) create mode 100644 packages/grpc-js-xds/test/test-nack.ts diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index e20bc7e9b..b2d7b2279 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -15,7 +15,7 @@ * */ -import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; +import { experimental, logVerbosity, Metadata, status, StatusObject } from "@grpc/grpc-js"; import { Any__Output } from "../generated/google/protobuf/Any"; const TRACER_NAME = 'xds_client'; @@ -157,19 +157,32 @@ export abstract class BaseXdsStreamState implements XdsStreamState return Array.from(this.subscriptions.keys()); } handleResponses(responses: ResourcePair[]): HandleResponseResult { - const validResponses: ResponseType[] = []; let result: HandleResponseResult = { accepted: [], rejected: [], missing: [] } + const allResourceNames = new Set(); for (const {resource, raw} of responses) { const resourceName = this.getResourceName(resource); + allResourceNames.add(resourceName); + const subscriptionEntry = this.subscriptions.get(resourceName); if (this.validateResponse(resource)) { - validResponses.push(resource); result.accepted.push({ name: resourceName, raw: raw}); + if (subscriptionEntry) { + const watchers = subscriptionEntry.watchers; + for (const watcher of watchers) { + watcher.onValidUpdate(resource); + } + clearTimeout(subscriptionEntry.resourceTimer); + subscriptionEntry.cachedResponse = resource; + if (subscriptionEntry.deletionIgnored) { + experimental.log(logVerbosity.INFO, 'Received resource with previously ignored deletion: ' + resourceName); + subscriptionEntry.deletionIgnored = false; + } + } } else { this.trace('Validation failed for message ' + JSON.stringify(resource)); result.rejected.push({ @@ -177,23 +190,16 @@ export abstract class BaseXdsStreamState implements XdsStreamState raw: raw, error: `Validation failed for resource ${resourceName}` }); - } - } - const allResourceNames = new Set(); - for (const resource of validResponses) { - const resourceName = this.getResourceName(resource); - allResourceNames.add(resourceName); - const subscriptionEntry = this.subscriptions.get(resourceName); - if (subscriptionEntry) { - const watchers = subscriptionEntry.watchers; - for (const watcher of watchers) { - watcher.onValidUpdate(resource); - } - clearTimeout(subscriptionEntry.resourceTimer); - subscriptionEntry.cachedResponse = resource; - if (subscriptionEntry.deletionIgnored) { - experimental.log(logVerbosity.INFO, 'Received resource with previously ignored deletion: ' + resourceName); - subscriptionEntry.deletionIgnored = false; + if (subscriptionEntry) { + const watchers = subscriptionEntry.watchers; + for (const watcher of watchers) { + watcher.onTransientError({ + code: status.UNAVAILABLE, + details: `Validation failed for resource ${resourceName}`, + metadata: new Metadata() + }); + } + clearTimeout(subscriptionEntry.resourceTimer); } } } diff --git a/packages/grpc-js-xds/test/test-nack.ts b/packages/grpc-js-xds/test/test-nack.ts new file mode 100644 index 000000000..ad1aad448 --- /dev/null +++ b/packages/grpc-js-xds/test/test-nack.ts @@ -0,0 +1,156 @@ +/* + * 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. + * + */ + +import * as assert from 'assert'; +import { register } from "../src"; +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; + +register(); + +describe('Validation errors', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }); + it('Should continue to use a valid resource after receiving an invalid EDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid EDS resource + xdsServer.setEdsResource({cluster_name: cluster.getEndpointConfig().cluster_name, endpoints: [{}]}); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); + it('Should continue to use a valid resource after receiving an invalid CDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid CDS resource + xdsServer.setCdsResource({name: cluster.getClusterConfig().name}); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); + it('Should continue to use a valid resource after receiving an invalid RDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid RDS resource + xdsServer.setRdsResource({name: routeGroup.getRouteConfiguration().name, virtual_hosts: [{domains: ['**']}]}); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); + it('Should continue to use a valid resource after receiving an invalid LDS update', done => { + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + client = new XdsTestClient('route1', xdsServer); + client.startCalls(100); + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + // After backends receive calls, set invalid LDS resource + xdsServer.setLdsResource({name: routeGroup.getListener().name}); + let seenNack = false; + xdsServer.addResponseListener((typeUrl, responseState) => { + if (responseState.state === 'NACKED') { + if (seenNack) { + return; + } + seenNack = true; + routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { + client.stopCalls(); + done(); + }); + } + }); + }, reason => done(reason)); + }, reason => done(reason)); + }); +}); \ No newline at end of file From 48ef1ed202e19c36e934015b6b1ed039cf7d2f00 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 20 Apr 2023 14:35:39 -0700 Subject: [PATCH 477/694] grpc-js-xds: Bump version to 1.8.2 --- packages/grpc-js-xds/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 9511776c3..7c735b652 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.8.1", + "version": "1.8.2", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { From dfccd687f0bc1df7d8d7dd599249b098a772cf53 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Apr 2023 16:21:12 -0700 Subject: [PATCH 478/694] Address review comments --- .../src/xds-stream-state/xds-stream-state.ts | 8 +++----- packages/grpc-js-xds/test/test-nack.ts | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index b2d7b2279..b04adb79a 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -172,14 +172,13 @@ export abstract class BaseXdsStreamState implements XdsStreamState name: resourceName, raw: raw}); if (subscriptionEntry) { - const watchers = subscriptionEntry.watchers; - for (const watcher of watchers) { + for (const watcher of subscriptionEntry.watchers) { watcher.onValidUpdate(resource); } clearTimeout(subscriptionEntry.resourceTimer); subscriptionEntry.cachedResponse = resource; if (subscriptionEntry.deletionIgnored) { - experimental.log(logVerbosity.INFO, 'Received resource with previously ignored deletion: ' + resourceName); + experimental.log(logVerbosity.INFO, `Received resource with previously ignored deletion: ${resourceName}`); subscriptionEntry.deletionIgnored = false; } } @@ -191,8 +190,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState error: `Validation failed for resource ${resourceName}` }); if (subscriptionEntry) { - const watchers = subscriptionEntry.watchers; - for (const watcher of watchers) { + for (const watcher of subscriptionEntry.watchers) { watcher.onTransientError({ code: status.UNAVAILABLE, details: `Validation failed for resource ${resourceName}`, diff --git a/packages/grpc-js-xds/test/test-nack.ts b/packages/grpc-js-xds/test/test-nack.ts index ad1aad448..b5bfb773e 100644 --- a/packages/grpc-js-xds/test/test-nack.ts +++ b/packages/grpc-js-xds/test/test-nack.ts @@ -38,7 +38,7 @@ describe('Validation errors', () => { xdsServer?.shutdownServer(); }); it('Should continue to use a valid resource after receiving an invalid EDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); @@ -49,7 +49,8 @@ describe('Validation errors', () => { client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid EDS resource - xdsServer.setEdsResource({cluster_name: cluster.getEndpointConfig().cluster_name, endpoints: [{}]}); + const invalidEdsResource = {cluster_name: cluster.getEndpointConfig().cluster_name, endpoints: [{}]}; + xdsServer.setEdsResource(invalidEdsResource); let seenNack = false; xdsServer.addResponseListener((typeUrl, responseState) => { if (responseState.state === 'NACKED') { @@ -67,7 +68,7 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid CDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); @@ -78,7 +79,8 @@ describe('Validation errors', () => { client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid CDS resource - xdsServer.setCdsResource({name: cluster.getClusterConfig().name}); + const invalidCdsResource = {name: cluster.getClusterConfig().name}; + xdsServer.setCdsResource(invalidCdsResource); let seenNack = false; xdsServer.addResponseListener((typeUrl, responseState) => { if (responseState.state === 'NACKED') { @@ -96,7 +98,7 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid RDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); @@ -107,7 +109,8 @@ describe('Validation errors', () => { client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid RDS resource - xdsServer.setRdsResource({name: routeGroup.getRouteConfiguration().name, virtual_hosts: [{domains: ['**']}]}); + const invalidRdsResource = {name: routeGroup.getRouteConfiguration().name, virtual_hosts: [{domains: ['**']}]}; + xdsServer.setRdsResource(invalidRdsResource); let seenNack = false; xdsServer.addResponseListener((typeUrl, responseState) => { if (responseState.state === 'NACKED') { @@ -125,7 +128,7 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid LDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); @@ -136,7 +139,8 @@ describe('Validation errors', () => { client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid LDS resource - xdsServer.setLdsResource({name: routeGroup.getListener().name}); + const invalidLdsResource = {name: routeGroup.getListener().name}; + xdsServer.setLdsResource(invalidLdsResource); let seenNack = false; xdsServer.addResponseListener((typeUrl, responseState) => { if (responseState.state === 'NACKED') { From edeeda6424d568b80eb4478b63afba05c51904a5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Apr 2023 16:22:49 -0700 Subject: [PATCH 479/694] Add trailing newline in packages/grpc-js-xds/test/test-nack.ts Co-authored-by: Sergii Tkachenko --- packages/grpc-js-xds/test/test-nack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/test/test-nack.ts b/packages/grpc-js-xds/test/test-nack.ts index b5bfb773e..9395628a6 100644 --- a/packages/grpc-js-xds/test/test-nack.ts +++ b/packages/grpc-js-xds/test/test-nack.ts @@ -157,4 +157,4 @@ describe('Validation errors', () => { }, reason => done(reason)); }, reason => done(reason)); }); -}); \ No newline at end of file +}); From 2f869495cc59fa24d56ea270baecfca683a7febf Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Apr 2023 17:05:28 -0700 Subject: [PATCH 480/694] Update tests with master test framework changes --- packages/grpc-js-xds/test/test-nack.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-xds/test/test-nack.ts b/packages/grpc-js-xds/test/test-nack.ts index 9395628a6..9e69e0dcf 100644 --- a/packages/grpc-js-xds/test/test-nack.ts +++ b/packages/grpc-js-xds/test/test-nack.ts @@ -19,7 +19,7 @@ import * as assert from 'assert'; import { register } from "../src"; import { Backend } from "./backend"; import { XdsTestClient } from "./client"; -import { FakeCluster, FakeRouteGroup } from "./framework"; +import { FakeEdsCluster, FakeRouteGroup } from "./framework"; import { XdsServer } from "./xds-server"; register(); @@ -38,7 +38,7 @@ describe('Validation errors', () => { xdsServer?.shutdownServer(); }); it('Should continue to use a valid resource after receiving an invalid EDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); @@ -68,7 +68,7 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid CDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); @@ -98,7 +98,7 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid RDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); @@ -128,7 +128,7 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid LDS update', done => { - const cluster = new FakeCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); From 85d227b1d3a8c6cea31db06f64ef21b1316cd595 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Apr 2023 17:27:44 -0700 Subject: [PATCH 481/694] Update test logic to account for recent validation changes --- packages/grpc-js-xds/test/test-nack.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/test/test-nack.ts b/packages/grpc-js-xds/test/test-nack.ts index 9e69e0dcf..ae2a9b3e4 100644 --- a/packages/grpc-js-xds/test/test-nack.ts +++ b/packages/grpc-js-xds/test/test-nack.ts @@ -17,6 +17,7 @@ import * as assert from 'assert'; import { register } from "../src"; +import { Cluster } from '../src/generated/envoy/config/cluster/v3/Cluster'; import { Backend } from "./backend"; import { XdsTestClient } from "./client"; import { FakeEdsCluster, FakeRouteGroup } from "./framework"; @@ -79,7 +80,7 @@ describe('Validation errors', () => { client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid CDS resource - const invalidCdsResource = {name: cluster.getClusterConfig().name}; + const invalidCdsResource: Cluster = {name: cluster.getClusterConfig().name, type: 'EDS'}; xdsServer.setCdsResource(invalidCdsResource); let seenNack = false; xdsServer.addResponseListener((typeUrl, responseState) => { From bc2447ccf6cc37998298f000ec1e8d591c487678 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 28 Apr 2023 10:58:09 -0700 Subject: [PATCH 482/694] proto-loader: Update to yargs@17.x --- packages/proto-loader/bin/proto-loader-gen-types.ts | 2 +- packages/proto-loader/package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 0b899476d..944790ad5 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -835,7 +835,7 @@ async function runScript() { boolean: true, default: false, }; - const argv = yargs + const argv = await yargs .parserConfiguration({ 'parse-positional-numbers': false }) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index b39e0204d..254cb6295 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.6", + "version": "0.7.7", "author": "Google Inc.", "contributors": [ { @@ -49,14 +49,14 @@ "lodash.camelcase": "^4.3.0", "long": "^4.0.0", "protobufjs": "^7.0.0", - "yargs": "^16.2.0" + "yargs": "^17.7.2" }, "devDependencies": { "@types/lodash.camelcase": "^4.3.4", "@types/mkdirp": "^1.0.1", "@types/mocha": "^5.2.7", "@types/node": "^10.17.26", - "@types/yargs": "^16.0.4", + "@types/yargs": "^17.0.24", "clang-format": "^1.2.2", "gts": "^3.1.0", "rimraf": "^3.0.2", From 821ccfa5deef0c39149f6534f8bd337372901784 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 28 Apr 2023 15:00:05 -0700 Subject: [PATCH 483/694] PSM Interop: Increase old driver QPS to 75 --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index a65447dde..402678e23 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -60,7 +60,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_clu --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ - --qps=50 \ + --qps=75 \ ${XDS_V3_OPT-} \ --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/github/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ From 0933633424b5eeec56d047d5e9fd2dd09dff4ac9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 28 Apr 2023 15:00:05 -0700 Subject: [PATCH 484/694] PSM Interop: Increase old driver QPS to 75 --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index af22f584f..7e6794a31 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -60,7 +60,7 @@ GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weigh --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \ --gcp_suffix=$(date '+%s') \ --verbose \ - --qps=50 \ + --qps=75 \ ${XDS_V3_OPT-} \ --client_cmd="$(which node) --enable-source-maps --prof --logfile=${KOKORO_ARTIFACTS_DIR}/github/grpc/reports/prof.log grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client \ --server=xds:///{server_uri} \ From 9d1b8493a23f93bee27af7da377379c633063b4e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 28 Apr 2023 10:07:12 -0700 Subject: [PATCH 485/694] Implement federation support (continued) --- packages/grpc-js-xds/src/load-balancer-cds.ts | 94 ++-- packages/grpc-js-xds/src/load-balancer-lrs.ts | 20 +- .../src/load-balancer-xds-cluster-impl.ts | 26 +- .../src/load-balancer-xds-cluster-resolver.ts | 30 +- packages/grpc-js-xds/src/resolver-xds.ts | 6 +- packages/grpc-js-xds/src/resource-cache.ts | 75 +++ packages/grpc-js-xds/src/resources.ts | 42 ++ packages/grpc-js-xds/src/xds-bootstrap.ts | 28 +- packages/grpc-js-xds/src/xds-client.ts | 86 ++-- packages/grpc-js-xds/src/xds-client2.ts | 450 ++++++++++++++++++ .../cluster-resource-type.ts | 239 ++++++++++ .../endpoint-resource-type.ts | 129 +++++ .../listener-resource-type.ts | 134 ++++++ .../route-config-resource-type.ts | 196 ++++++++ .../xds-resource-type/xds-resource-type.ts | 32 ++ .../src/xds-stream-state/cds-state.ts | 194 ++++++-- .../src/xds-stream-state/eds-state.ts | 20 +- .../src/xds-stream-state/lds-state.ts | 31 +- .../src/xds-stream-state/rds-state.ts | 32 +- .../src/xds-stream-state/xds-stream-state.ts | 48 +- 20 files changed, 1663 insertions(+), 249 deletions(-) create mode 100644 packages/grpc-js-xds/src/resource-cache.ts create mode 100644 packages/grpc-js-xds/src/xds-client2.ts create mode 100644 packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts create mode 100644 packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts create mode 100644 packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts create mode 100644 packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts create mode 100644 packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index d1e8e0de7..92559e911 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -16,7 +16,7 @@ */ import { connectivityState, status, Metadata, logVerbosity, experimental } from '@grpc/grpc-js'; -import { getSingletonXdsClient, XdsSingleServerClient } from './xds-client'; +import { getSingletonXdsClient, XdsClient } from './xds-client'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import SubchannelAddress = experimental.SubchannelAddress; import UnavailablePicker = experimental.UnavailablePicker; @@ -35,6 +35,7 @@ import { Duration__Output } from './generated/google/protobuf/Duration'; import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler, XdsClusterResolverLoadBalancingConfig } from './load-balancer-xds-cluster-resolver'; import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources'; +import { CdsUpdate, OutlierDetectionUpdate } from './xds-stream-state/cds-state'; const TRACER_NAME = 'cds_balancer'; @@ -76,7 +77,7 @@ function durationToMs(duration: Duration__Output): number { return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; } -function translateOutlierDetectionConfig(outlierDetection: OutlierDetection__Output | null): OutlierDetectionLoadBalancingConfig | undefined { +function translateOutlierDetectionConfig(outlierDetection: OutlierDetectionUpdate | undefined): OutlierDetectionLoadBalancingConfig | undefined { if (!EXPERIMENTAL_OUTLIER_DETECTION) { return undefined; } @@ -84,42 +85,20 @@ function translateOutlierDetectionConfig(outlierDetection: OutlierDetection__Out /* No-op outlier detection config, with all fields unset. */ return new OutlierDetectionLoadBalancingConfig(null, null, null, null, null, null, []); } - let successRateConfig: Partial | null = null; - /* Success rate ejection is enabled by default, so we only disable it if - * enforcing_success_rate is set and it has the value 0 */ - if (!outlierDetection.enforcing_success_rate || outlierDetection.enforcing_success_rate.value > 0) { - successRateConfig = { - enforcement_percentage: outlierDetection.enforcing_success_rate?.value, - minimum_hosts: outlierDetection.success_rate_minimum_hosts?.value, - request_volume: outlierDetection.success_rate_request_volume?.value, - stdev_factor: outlierDetection.success_rate_stdev_factor?.value - }; - } - let failurePercentageConfig: Partial | null = null; - /* Failure percentage ejection is disabled by default, so we only enable it - * if enforcing_failure_percentage is set and it has a value greater than 0 */ - if (outlierDetection.enforcing_failure_percentage && outlierDetection.enforcing_failure_percentage.value > 0) { - failurePercentageConfig = { - enforcement_percentage: outlierDetection.enforcing_failure_percentage.value, - minimum_hosts: outlierDetection.failure_percentage_minimum_hosts?.value, - request_volume: outlierDetection.failure_percentage_request_volume?.value, - threshold: outlierDetection.failure_percentage_threshold?.value - } - } return new OutlierDetectionLoadBalancingConfig( - outlierDetection.interval ? durationToMs(outlierDetection.interval) : null, - outlierDetection.base_ejection_time ? durationToMs(outlierDetection.base_ejection_time) : null, - outlierDetection.max_ejection_time ? durationToMs(outlierDetection.max_ejection_time) : null, - outlierDetection.max_ejection_percent?.value ?? null, - successRateConfig, - failurePercentageConfig, + outlierDetection.intervalMs, + outlierDetection.baseEjectionTimeMs, + outlierDetection.maxEjectionTimeMs, + outlierDetection.maxEjectionPercent, + outlierDetection.successRateConfig, + outlierDetection.failurePercentageConfig, [] ); } interface ClusterEntry { - watcher: Watcher; - latestUpdate?: Cluster__Output; + watcher: Watcher; + latestUpdate?: CdsUpdate; children: string[]; } @@ -144,34 +123,19 @@ function isClusterTreeFullyUpdated(tree: ClusterTree, root: string): boolean { return true; } -function generateDiscoveryMechanismForCluster(config: Cluster__Output): DiscoveryMechanism { - let maxConcurrentRequests: number | undefined = undefined; - for (const threshold of config.circuit_breakers?.thresholds ?? []) { - if (threshold.priority === 'DEFAULT') { - maxConcurrentRequests = threshold.max_requests?.value; - } - } - if (config.type === 'EDS') { - // EDS cluster - return { - cluster: config.name, - lrs_load_reporting_server_name: config.lrs_server?.self ? '' : undefined, - max_concurrent_requests: maxConcurrentRequests, - type: 'EDS', - eds_service_name: config.eds_cluster_config!.service_name === '' ? undefined : config.eds_cluster_config!.service_name, - outlier_detection: translateOutlierDetectionConfig(config.outlier_detection) - }; - } else { - // Logical DNS cluster - const socketAddress = config.load_assignment!.endpoints[0].lb_endpoints[0].endpoint!.address!.socket_address!; - return { - cluster: config.name, - lrs_load_reporting_server_name: config.lrs_server?.self ? '' : undefined, - max_concurrent_requests: maxConcurrentRequests, - type: 'LOGICAL_DNS', - dns_hostname: `${socketAddress.address}:${socketAddress.port_value}` - }; +function generateDiscoverymechanismForCdsUpdate(config: CdsUpdate): DiscoveryMechanism { + if (config.type === 'AGGREGATE') { + throw new Error('Cannot generate DiscoveryMechanism for AGGREGATE cluster'); } + return { + cluster: config.name, + lrs_load_reporting_server: config.lrsLoadReportingServer, + max_concurrent_requests: config.maxConcurrentRequests, + type: config.type, + eds_service_name: config.edsServiceName, + dns_hostname: config.dnsHostname, + outlier_detection: translateOutlierDetectionConfig(config.outlierDetectionUpdate) + }; } const RECURSION_DEPTH_LIMIT = 15; @@ -203,7 +167,7 @@ function getDiscoveryMechanismList(tree: ClusterTree, root: string): DiscoveryMe trace('Visit leaf ' + node); // individual cluster const config = tree[node].latestUpdate!; - return [generateDiscoveryMechanismForCluster(config)]; + return [generateDiscoverymechanismForCdsUpdate(config)]; } } return getDiscoveryMechanismListHelper(root, 0); @@ -216,7 +180,7 @@ export class CdsLoadBalancer implements LoadBalancer { private latestConfig: CdsLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown } = {}; - private xdsClient: XdsSingleServerClient | null = null; + private xdsClient: XdsClient | null = null; private clusterTree: ClusterTree = {}; @@ -231,11 +195,11 @@ export class CdsLoadBalancer implements LoadBalancer { return; } trace('Adding watcher for cluster ' + cluster); - const watcher: Watcher = { + const watcher: Watcher = { onValidUpdate: (update) => { this.clusterTree[cluster].latestUpdate = update; - if (update.cluster_discovery_type === 'cluster_type') { - const children = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, update.cluster_type!.typed_config!.value).clusters; + if (update.type === 'AGGREGATE') { + const children = update.aggregateChildren trace('Received update for aggregate cluster ' + cluster + ' with children [' + children + ']'); this.clusterTree[cluster].children = children; children.forEach(child => this.addCluster(child)); @@ -315,7 +279,7 @@ export class CdsLoadBalancer implements LoadBalancer { } trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); this.latestAttributes = attributes; - this.xdsClient = attributes.xdsClient as XdsSingleServerClient; + this.xdsClient = attributes.xdsClient as XdsClient; /* If the cluster is changing, disable the old watcher before adding the new * one */ diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 24d7bfc5e..a2deb72c3 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -17,7 +17,8 @@ import { connectivityState as ConnectivityState, StatusObject, status as Status, experimental } from '@grpc/grpc-js'; import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; -import { XdsClusterLocalityStats, XdsSingleServerClient, getSingletonXdsClient } from './xds-client'; +import { validateXdsServerConfig, XdsServerConfig } from './xds-bootstrap'; +import { XdsClusterLocalityStats, XdsClient, getSingletonXdsClient } from './xds-client'; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; @@ -46,14 +47,14 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig { [TYPE_NAME]: { cluster_name: this.clusterName, eds_service_name: this.edsServiceName, - lrs_load_reporting_server_name: this.lrsLoadReportingServerName, + lrs_load_reporting_server_name: this.lrsLoadReportingServer, locality: this.locality, child_policy: this.childPolicy.map(policy => policy.toJsonObject()) } } } - constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServerName: string, private locality: Locality__Output, private childPolicy: LoadBalancingConfig[]) {} + constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, private locality: Locality__Output, private childPolicy: LoadBalancingConfig[]) {} getClusterName() { return this.clusterName; @@ -63,8 +64,8 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig { return this.edsServiceName; } - getLrsLoadReportingServerName() { - return this.lrsLoadReportingServerName; + getLrsLoadReportingServer() { + return this.lrsLoadReportingServer; } getLocality() { @@ -82,9 +83,6 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig { if (!('eds_service_name' in obj && typeof obj.eds_service_name === 'string')) { throw new Error('lrs config must have a string field eds_service_name'); } - if (!('lrs_load_reporting_server_name' in obj && typeof obj.lrs_load_reporting_server_name === 'string')) { - throw new Error('lrs config must have a string field lrs_load_reporting_server_name'); - } if (!('locality' in obj && obj.locality !== null && typeof obj.locality === 'object')) { throw new Error('lrs config must have an object field locality'); } @@ -100,7 +98,7 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig { if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { throw new Error('lrs config must have a child_policy array'); } - return new LrsLoadBalancingConfig(obj.cluster_name, obj.eds_service_name, obj.lrs_load_reporting_server_name, { + return new LrsLoadBalancingConfig(obj.cluster_name, obj.eds_service_name, validateXdsServerConfig(obj.lrs_load_reporting_server), { region: obj.locality.region ?? '', zone: obj.locality.zone ?? '', sub_zone: obj.locality.sub_zone ?? '' @@ -169,8 +167,8 @@ export class LrsLoadBalancer implements LoadBalancer { if (!(lbConfig instanceof LrsLoadBalancingConfig)) { return; } - this.localityStatsReporter = (attributes.xdsClient as XdsSingleServerClient).addClusterLocalityStats( - lbConfig.getLrsLoadReportingServerName(), + this.localityStatsReporter = (attributes.xdsClient as XdsClient).addClusterLocalityStats( + lbConfig.getLrsLoadReportingServer(), lbConfig.getClusterName(), lbConfig.getEdsServiceName(), lbConfig.getLocality() diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index 6b8d5fae8..6599ba758 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -16,7 +16,8 @@ */ import { experimental, logVerbosity, status as Status, Metadata, connectivityState } from "@grpc/grpc-js"; -import { getSingletonXdsClient, XdsSingleServerClient, XdsClusterDropStats } from "./xds-client"; +import { validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; +import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from "./xds-client"; import LoadBalancingConfig = experimental.LoadBalancingConfig; import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; @@ -72,15 +73,15 @@ export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { if (this.edsServiceName !== undefined) { jsonObj.eds_service_name = this.edsServiceName; } - if (this.lrsLoadReportingServerName !== undefined) { - jsonObj.lrs_load_reporting_server_name = this.lrsLoadReportingServerName; + if (this.lrsLoadReportingServer !== undefined) { + jsonObj.lrs_load_reporting_server_name = this.lrsLoadReportingServer; } return { [TYPE_NAME]: jsonObj }; } - constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServerName?: string, maxConcurrentRequests?: number) { + constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServer?: XdsServerConfig, maxConcurrentRequests?: number) { this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; } @@ -92,8 +93,8 @@ export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { return this.edsServiceName; } - getLrsLoadReportingServerName() { - return this.lrsLoadReportingServerName; + getLrsLoadReportingServer() { + return this.lrsLoadReportingServer; } getMaxConcurrentRequests() { @@ -115,9 +116,6 @@ export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { if ('eds_service_name' in obj && !(obj.eds_service_name === undefined || typeof obj.eds_service_name === 'string')) { throw new Error('xds_cluster_impl config eds_service_name field must be a string if provided'); } - if ('lrs_load_reporting_server_name' in obj && (!obj.lrs_load_reporting_server_name === undefined || typeof obj.lrs_load_reporting_server_name === 'string')) { - throw new Error('xds_cluster_impl config lrs_load_reporting_server_name must be a string if provided'); - } if ('max_concurrent_requests' in obj && (!obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { throw new Error('xds_cluster_impl config max_concurrent_requests must be a number if provided'); } @@ -127,7 +125,7 @@ export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { throw new Error('xds_cluster_impl config must have an array field child_policy'); } - return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), obj.child_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server_name, obj.max_concurrent_requests); + return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), obj.child_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server ? validateXdsServerConfig(obj.lrs_load_reporting_server) : undefined, obj.max_concurrent_requests); } } @@ -222,7 +220,7 @@ class XdsClusterImplBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; private latestConfig: XdsClusterImplLoadBalancingConfig | null = null; private clusterDropStats: XdsClusterDropStats | null = null; - private xdsClient: XdsSingleServerClient | null = null; + private xdsClient: XdsClient | null = null; constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { @@ -243,11 +241,11 @@ class XdsClusterImplBalancer implements LoadBalancer { } trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); this.latestConfig = lbConfig; - this.xdsClient = attributes.xdsClient as XdsSingleServerClient; + this.xdsClient = attributes.xdsClient as XdsClient; - if (lbConfig.getLrsLoadReportingServerName()) { + if (lbConfig.getLrsLoadReportingServer()) { this.clusterDropStats = this.xdsClient.addClusterDropStats( - lbConfig.getLrsLoadReportingServerName()!, + lbConfig.getLrsLoadReportingServer()!, lbConfig.getCluster(), lbConfig.getEdsServiceName() ?? '' ); diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index 93977ab45..daee96825 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -23,7 +23,7 @@ import { ClusterLoadAssignment__Output } from "./generated/envoy/config/endpoint import { LrsLoadBalancingConfig } from "./load-balancer-lrs"; import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from "./load-balancer-priority"; import { WeightedTarget, WeightedTargetLoadBalancingConfig } from "./load-balancer-weighted-target"; -import { getSingletonXdsClient, XdsSingleServerClient } from "./xds-client"; +import { getSingletonXdsClient, XdsClient } from "./xds-client"; import { DropCategory, XdsClusterImplLoadBalancingConfig } from "./load-balancer-xds-cluster-impl"; import { Watcher } from "./xds-stream-state/xds-stream-state"; @@ -37,6 +37,7 @@ import createResolver = experimental.createResolver; import ChannelControlHelper = experimental.ChannelControlHelper; import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; import subchannelAddressToString = experimental.subchannelAddressToString; +import { serverConfigEqual, validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; const TRACER_NAME = 'xds_cluster_resolver'; @@ -46,7 +47,7 @@ function trace(text: string): void { export interface DiscoveryMechanism { cluster: string; - lrs_load_reporting_server_name?: string; + lrs_load_reporting_server?: XdsServerConfig; max_concurrent_requests?: number; type: 'EDS' | 'LOGICAL_DNS'; eds_service_name?: string; @@ -61,9 +62,6 @@ function validateDiscoveryMechanism(obj: any): DiscoveryMechanism { if (!('type' in obj && (obj.type === 'EDS' || obj.type === 'LOGICAL_DNS'))) { throw new Error('discovery_mechanisms entry must have a field "type" with the value "EDS" or "LOGICAL_DNS"'); } - if ('lrs_load_reporting_server_name' in obj && typeof obj.lrs_load_reporting_server_name !== 'string') { - throw new Error('discovery_mechanisms entry lrs_load_reporting_server_name field must be a string if provided'); - } if ('max_concurrent_requests' in obj && typeof obj.max_concurrent_requests !== "number") { throw new Error('discovery_mechanisms entry max_concurrent_requests field must be a number if provided'); } @@ -78,7 +76,7 @@ function validateDiscoveryMechanism(obj: any): DiscoveryMechanism { if (!(outlierDetectionConfig instanceof OutlierDetectionLoadBalancingConfig)) { throw new Error('eds config outlier_detection must be a valid outlier detection config if provided'); } - return {...obj, outlier_detection: outlierDetectionConfig}; + return {...obj, lrs_load_reporting_server: validateXdsServerConfig(obj.lrs_load_reporting_server), outlier_detection: outlierDetectionConfig}; } return obj; } @@ -243,7 +241,7 @@ export class XdsClusterResolver implements LoadBalancer { private discoveryMechanismList: DiscoveryMechanismEntry[] = []; private latestConfig: XdsClusterResolverLoadBalancingConfig | null = null; private latestAttributes: { [key: string]: unknown; } = {}; - private xdsClient: XdsSingleServerClient | null = null; + private xdsClient: XdsClient | null = null; private childBalancer: ChildLoadBalancerHandler; constructor(private readonly channelControlHelper: ChannelControlHelper) { @@ -313,8 +311,8 @@ export class XdsClusterResolver implements LoadBalancer { const childTargets = new Map(); for (const localityObj of priorityEntry.localities) { let childPolicy: LoadBalancingConfig[]; - if (entry.discoveryMechanism.lrs_load_reporting_server_name !== undefined) { - childPolicy = [new LrsLoadBalancingConfig(entry.discoveryMechanism.cluster, entry.discoveryMechanism.eds_service_name ?? '', entry.discoveryMechanism.lrs_load_reporting_server_name!, localityObj.locality, endpointPickingPolicy)]; + if (entry.discoveryMechanism.lrs_load_reporting_server !== undefined) { + childPolicy = [new LrsLoadBalancingConfig(entry.discoveryMechanism.cluster, entry.discoveryMechanism.eds_service_name ?? '', entry.discoveryMechanism.lrs_load_reporting_server, localityObj.locality, endpointPickingPolicy)]; } else { childPolicy = endpointPickingPolicy; } @@ -334,7 +332,7 @@ export class XdsClusterResolver implements LoadBalancer { newLocalityPriorities.set(localityToName(localityObj.locality), priority); } const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); - const xdsClusterImplConfig = new XdsClusterImplLoadBalancingConfig(entry.discoveryMechanism.cluster, priorityEntry.dropCategories, [weightedTargetConfig], entry.discoveryMechanism.eds_service_name, entry.discoveryMechanism.lrs_load_reporting_server_name, entry.discoveryMechanism.max_concurrent_requests); + const xdsClusterImplConfig = new XdsClusterImplLoadBalancingConfig(entry.discoveryMechanism.cluster, priorityEntry.dropCategories, [weightedTargetConfig], entry.discoveryMechanism.eds_service_name, entry.discoveryMechanism.lrs_load_reporting_server, entry.discoveryMechanism.max_concurrent_requests); let outlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined; if (EXPERIMENTAL_OUTLIER_DETECTION) { outlierDetectionConfig = entry.discoveryMechanism.outlier_detection?.copyWithChildPolicy([xdsClusterImplConfig]); @@ -368,7 +366,7 @@ export class XdsClusterResolver implements LoadBalancer { trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2)); this.latestConfig = lbConfig; this.latestAttributes = attributes; - this.xdsClient = attributes.xdsClient as XdsSingleServerClient; + this.xdsClient = attributes.xdsClient as XdsClient; if (this.discoveryMechanismList.length === 0) { for (const mechanism of lbConfig.getDiscoveryMechanisms()) { const mechanismEntry: DiscoveryMechanismEntry = { @@ -448,6 +446,14 @@ export class XdsClusterResolver implements LoadBalancer { } } +function maybeServerConfigEqual(config1: XdsServerConfig | undefined, config2: XdsServerConfig | undefined) { + if (config1 !== undefined && config2 !== undefined) { + return serverConfigEqual(config1, config2); + } else { + return config1 === config2; + } +} + export class XdsClusterResolverChildPolicyHandler extends ChildLoadBalancerHandler { protected configUpdateRequiresNewPolicyInstance(oldConfig: LoadBalancingConfig, newConfig: LoadBalancingConfig): boolean { if (!(oldConfig instanceof XdsClusterResolverLoadBalancingConfig && newConfig instanceof XdsClusterResolverLoadBalancingConfig)) { @@ -463,7 +469,7 @@ export class XdsClusterResolverChildPolicyHandler extends ChildLoadBalancerHandl oldDiscoveryMechanism.cluster !== newDiscoveryMechanism.cluster || oldDiscoveryMechanism.eds_service_name !== newDiscoveryMechanism.eds_service_name || oldDiscoveryMechanism.dns_hostname !== newDiscoveryMechanism.dns_hostname || - oldDiscoveryMechanism.lrs_load_reporting_server_name !== newDiscoveryMechanism.lrs_load_reporting_server_name) { + !maybeServerConfigEqual(oldDiscoveryMechanism.lrs_load_reporting_server, newDiscoveryMechanism.lrs_load_reporting_server)) { return true; } } diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 423ade15c..d6572dcac 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -18,7 +18,7 @@ import * as protoLoader from '@grpc/proto-loader'; import { RE2 } from 're2-wasm'; -import { getSingletonXdsClient, XdsSingleServerClient } from './xds-client'; +import { getSingletonXdsClient, XdsClient } from './xds-client'; import { StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions } from '@grpc/grpc-js'; import Resolver = experimental.Resolver; import GrpcUri = experimental.GrpcUri; @@ -273,7 +273,7 @@ class XdsResolver implements Resolver { private bootstrapInfo: BootstrapInfo | null = null; - private xdsClient: XdsSingleServerClient; + private xdsClient: XdsClient; constructor( private target: GrpcUri, @@ -283,7 +283,7 @@ class XdsResolver implements Resolver { if (channelOptions[BOOTSTRAP_CONFIG_KEY]) { const parsedConfig = JSON.parse(channelOptions[BOOTSTRAP_CONFIG_KEY]); this.bootstrapInfo = validateBootstrapConfig(parsedConfig); - this.xdsClient = new XdsSingleServerClient(this.bootstrapInfo); + this.xdsClient = new XdsClient(this.bootstrapInfo); } else { this.xdsClient = getSingletonXdsClient(); } diff --git a/packages/grpc-js-xds/src/resource-cache.ts b/packages/grpc-js-xds/src/resource-cache.ts new file mode 100644 index 000000000..97ca98b1a --- /dev/null +++ b/packages/grpc-js-xds/src/resource-cache.ts @@ -0,0 +1,75 @@ +/* + * 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. + * + */ + +import { XdsResourceKey, xdsResourceKeyEqual, XdsResourceName } from "./resources"; + +interface ResourceCacheEntry { + key: XdsResourceKey; + value: ResourceType; + refCount: number; +} + +export class ResourceCache { + /** + * Map authority to a list of key/value pairs + */ + private cache: Map[]> = new Map(); + getAndRef(name: XdsResourceName): ResourceType | undefined { + const mapEntry = this.cache.get(name.authority); + if (!mapEntry) { + return undefined; + } + for (const entry of mapEntry) { + if (xdsResourceKeyEqual(name.key, entry.key)) { + entry.refCount += 1; + return entry.value; + } + } + return undefined; + } + + set(name: XdsResourceName, value: ResourceType): void { + const mapEntry = this.cache.get(name.authority); + if (!mapEntry) { + this.cache.set(name.authority, [{key: name.key, value: value, refCount: 1}]); + return; + } + for (const entry of mapEntry) { + if (xdsResourceKeyEqual(name.key, entry.key)) { + entry.value = value; + return; + } + } + mapEntry.push({key: name.key, value: value, refCount: 1}); + } + + unref(name: XdsResourceName): void { + const mapEntry = this.cache.get(name.authority); + if (!mapEntry) { + return; + } + for (let i = 0; i < mapEntry.length; i++) { + if (xdsResourceKeyEqual(name.key, mapEntry[i].key)) { + mapEntry[i].refCount -= 1; + if (mapEntry[i].refCount === 0) { + mapEntry.splice(i, 1); + } + return; + } + } + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts index e4d464b6d..83291d70a 100644 --- a/packages/grpc-js-xds/src/resources.ts +++ b/packages/grpc-js-xds/src/resources.ts @@ -16,6 +16,7 @@ */ // This is a non-public, unstable API, but it's very convenient +import { URI } from 'vscode-uri'; import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; @@ -23,6 +24,7 @@ import { Listener__Output } from './generated/envoy/config/listener/v3/Listener' import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; import { ClusterConfig__Output } from './generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig'; import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; +import { EXPERIMENTAL_FEDERATION } from './environment'; export const EDS_TYPE_URL = 'type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment'; export const CDS_TYPE_URL = 'type.googleapis.com/envoy.config.cluster.v3.Cluster'; @@ -94,4 +96,44 @@ export function decodeSingleResource { + const edsState = new EdsState(xdsServerConfig, () => { this.updateNames('eds'); }); - const cdsState = new CdsState(() => { + const cdsState = new CdsState(xdsServerConfig, () => { this.updateNames('cds'); }); - const rdsState = new RdsState(() => { + const rdsState = new RdsState(xdsServerConfig, () => { this.updateNames('rds'); }); - const ldsState = new LdsState(rdsState, () => { + const ldsState = new LdsState(xdsServerConfig, rdsState, () => { this.updateNames('lds'); }); this.adsState = { @@ -807,12 +808,12 @@ class XdsSingleServerClient { this.adsState.eds.removeWatcher(edsServiceName, watcher); } - addClusterWatcher(clusterName: string, watcher: Watcher) { + addClusterWatcher(clusterName: string, watcher: Watcher) { this.trace('Watcher added for cluster ' + clusterName); this.adsState.cds.addWatcher(clusterName, watcher); } - removeClusterWatcher(clusterName: string, watcher: Watcher) { + removeClusterWatcher(clusterName: string, watcher: Watcher) { this.trace('Watcher removed for cluster ' + clusterName); this.adsState.cds.removeWatcher(clusterName, watcher); } @@ -912,31 +913,24 @@ class XdsSingleServerClient { } } -const KNOWN_SERVER_FEATURES = ['ignore_resource_deletion']; - -function serverConfigEqual(config1: XdsServerConfig, config2: XdsServerConfig): boolean { - if (config1.serverUri !== config2.serverUri) { - return false; - } - for (const feature of KNOWN_SERVER_FEATURES) { - if ((feature in config1.serverFeatures) !== (feature in config2.serverFeatures)) { - return false; - } - } - if (config1.channelCreds.length !== config2.channelCreds.length) { - return false; - } - for (const [index, creds1] of config1.channelCreds.entries()) { - const creds2 = config2.channelCreds[index]; - if (creds1.type !== creds2.type) { - return false; - } - if (JSON.stringify(creds1) !== JSON.stringify(creds2)) { - return false; - } - } - return true; -} +/* Structure: + * serverConfig + * single server client + * response validation (for ACK/NACK) + * response parsing (server config in CDS update for LRS) + * authority + * EdsStreamState + * watchers + * cache + * CdsStreamState + * ... + * RdsStreamState + * ... + * LdsStreamState + * ... + * server reference + * update CSDS + */ interface ClientMapEntry { serverConfig: XdsServerConfig; @@ -968,16 +962,20 @@ export class XdsClient { private getOrCreateClientForResource(resourceName: string): XdsSingleServerClient { const bootstrapInfo = this.getBootstrapInfo(); let serverConfig: XdsServerConfig; - if (resourceName.startsWith('xdstp:')) { - const match = resourceName.match(/xdstp:\/\/([^/]+)\//); - if (!match) { - throw new Error(`Parse error: Resource ${resourceName} has no authority`); - } - const authority = match[1]; - if (authority in bootstrapInfo.authorities) { - serverConfig = bootstrapInfo.authorities[authority].xdsServers?.[0] ?? bootstrapInfo.xdsServers[0]; + if (EXPERIMENTAL_FEDERATION) { + if (resourceName.startsWith('xdstp:')) { + const match = resourceName.match(/xdstp:\/\/([^/]+)\//); + if (!match) { + throw new Error(`Parse error: Resource ${resourceName} has no authority`); + } + const authority = match[1]; + if (authority in bootstrapInfo.authorities) { + serverConfig = bootstrapInfo.authorities[authority].xdsServers?.[0] ?? bootstrapInfo.xdsServers[0]; + } else { + throw new Error(`Authority ${authority} in resource ${resourceName} not found in authorities list`); + } } else { - throw new Error(`Authority ${authority} in resource ${resourceName} not found in authorities list`); + serverConfig = bootstrapInfo.xdsServers[0]; } } else { serverConfig = bootstrapInfo.xdsServers[0]; @@ -1018,7 +1016,7 @@ export class XdsClient { } } - addClusterWatcher(clusterName: string, watcher: Watcher) { + addClusterWatcher(clusterName: string, watcher: Watcher) { trace('addClusterWatcher(' + clusterName + ')'); try { const client = this.getOrCreateClientForResource(clusterName); @@ -1029,7 +1027,7 @@ export class XdsClient { } } - removeClusterWatcher(clusterName: string, watcher: Watcher) { + removeClusterWatcher(clusterName: string, watcher: Watcher) { trace('removeClusterWatcher(' + clusterName + ')'); try { const client = this.getOrCreateClientForResource(clusterName); diff --git a/packages/grpc-js-xds/src/xds-client2.ts b/packages/grpc-js-xds/src/xds-client2.ts new file mode 100644 index 000000000..4e6bc10a0 --- /dev/null +++ b/packages/grpc-js-xds/src/xds-client2.ts @@ -0,0 +1,450 @@ +/* + * 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. + * + */ + +import { ClientDuplexStream, StatusObject, experimental, loadPackageDefinition, logVerbosity } from "@grpc/grpc-js"; +import { XdsResourceType } from "./xds-resource-type/xds-resource-type"; +import { XdsResourceName, parseXdsResourceName, xdsResourceNameToString } from "./resources"; +import { Node } from "./generated/envoy/config/core/v3/Node"; +import { BootstrapInfo, XdsServerConfig, loadBootstrapInfo, serverConfigEqual } from "./xds-bootstrap"; +import BackoffTimeout = experimental.BackoffTimeout; +import { DiscoveryRequest } from "./generated/envoy/service/discovery/v3/DiscoveryRequest"; +import { DiscoveryResponse__Output } from "./generated/envoy/service/discovery/v3/DiscoveryResponse"; +import * as adsTypes from './generated/ads'; +import * as lrsTypes from './generated/lrs'; +import * as protoLoader from '@grpc/proto-loader'; +import { EXPERIMENTAL_FEDERATION } from "./environment"; + +const TRACER_NAME = 'xds_client'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +let loadedProtos: adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType | null = null; + +function loadAdsProtos(): adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType { + if (loadedProtos !== null) { + return loadedProtos; + } + return (loadPackageDefinition(protoLoader + .loadSync( + [ + 'envoy/service/discovery/v3/ads.proto', + 'envoy/service/load_stats/v3/lrs.proto', + ], + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + json: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/googleapis/', + __dirname + '/../../deps/protoc-gen-validate/', + ], + } + )) as unknown) as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType; +} + +const clientVersion = require('../../package.json').version; + +export interface ResourceWatcherInterface { + onGenericResourceChanged(resource: object): void; + onError(status: StatusObject): void; + onResourceDoesNotExist(): void; +} + +export interface BasicWatcher { + onResourceChanged(resource: UpdateType): void; + onError(status: StatusObject): void; + onResourceDoesNotExist(): void; +} + +export class Watcher implements ResourceWatcherInterface { + constructor(private internalWatcher: BasicWatcher) {} + onGenericResourceChanged(resource: object): void { + this.internalWatcher.onResourceChanged(resource as UpdateType); + } + onError(status: StatusObject) { + this.internalWatcher.onError(status); + } + onResourceDoesNotExist() { + this.internalWatcher.onResourceDoesNotExist(); + } +} + +const RESOURCE_TIMEOUT_MS = 15_000; + +class ResourceTimer { + private timer: NodeJS.Timer | null = null; + private resourceSeen = false; + constructor(private callState: AdsCallState, private type: XdsResourceType, private name: XdsResourceName) {} + + maybeCancelTimer() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + } + + markSeen() { + this.resourceSeen = true; + this.maybeCancelTimer(); + } + + markSubscriptionSendStarted() { + this.maybeStartTimer(); + } + + private maybeStartTimer() { + if (this.resourceSeen) { + return; + } + if (this.timer) { + return; + } + const authorityState = this.callState.client.xdsClient.authorityStateMap.get(this.name.authority); + if (!authorityState) { + return; + } + const resourceState = authorityState.resourceMap.get(this.type)?.get(this.name.key); + if (resourceState?.cachedResource) { + return; + } + this.timer = setTimeout(() => { + this.onTimer(); + }, RESOURCE_TIMEOUT_MS); + } + + private onTimer() { + const authorityState = this.callState.client.xdsClient.authorityStateMap.get(this.name.authority); + const resourceState = authorityState?.resourceMap.get(this.type)?.get(this.name.key); + if (!resourceState) { + return; + } + resourceState.meta.clientStatus = 'DOES_NOT_EXIST'; + for (const watcher of resourceState.watchers) { + watcher.onResourceDoesNotExist(); + } + } +} + +type AdsCall = ClientDuplexStream; + +interface ResourceTypeState { + nonce?: string; + /** + * authority -> key -> timer + */ + subscribedResources: Map>; +} + +class AdsCallState { + private typeStates: Map = new Map(); + constructor(public client: XdsSingleServerClient, private call: AdsCall, private node: Node) { + // Populate subscription map with existing subscriptions + for (const [authority, authorityState] of client.xdsClient.authorityStateMap) { + if (authorityState.client !== client) { + continue; + } + for (const [type, typeMap] of authorityState.resourceMap) { + let typeState = this.typeStates.get(type); + if (!typeState) { + typeState = { + nonce: '', + subscribedResources: new Map() + }; + } + const authorityMap: Map = new Map(); + for (const key of typeMap.keys()) { + const timer = new ResourceTimer(this, type, {authority, key}); + authorityMap.set(key, timer); + } + typeState.subscribedResources.set(authority, authorityMap); + } + } + } + + hasSubscribedResources(): boolean { + for (const typeState of this.typeStates.values()) { + for (const authorityMap of typeState.subscribedResources.values()) { + if (authorityMap.size > 0) { + return true; + } + } + } + return false; + } + + subscribe(type: XdsResourceType, name: XdsResourceName) { + let typeState = this.typeStates.get(type); + if (!typeState) { + typeState = { + nonce: '', + subscribedResources: new Map() + }; + } + let authorityMap = typeState.subscribedResources.get(name.authority); + if (!authorityMap) { + authorityMap = new Map(); + typeState.subscribedResources.set(name.authority, authorityMap); + } + if (!authorityMap.has(name.key)) { + const timer = new ResourceTimer(this, type, name); + authorityMap.set(name.key, timer); + } + } + + unsubscribe(type: XdsResourceType, name: XdsResourceName) { + this.typeStates.get(type)?.subscribedResources.get(name.authority)?.delete(name.key); + } + + resourceNamesForRequest(type: XdsResourceType): string[] { + const typeState = this.typeStates.get(type); + if (!typeState) { + return []; + } + const result: string[] = []; + for (const [authority, authorityMap] of typeState.subscribedResources) { + for (const [key, timer] of authorityMap) { + timer.markSubscriptionSendStarted(); + result.push(xdsResourceNameToString({authority, key}, type.getTypeUrl())); + } + } + return result; + } + + updateNames(type: XdsResourceType) { + this.call.write({ + node: this.node, + type_url: type.getTypeUrl(), + response_nonce: this.typeStates.get(type)?.nonce, + resource_names: this.resourceNamesForRequest(type) + }); + } +} + +class XdsSingleServerClient { + private adsNode: Node; + private lrsNode: Node; + private ignoreResourceDeletion: boolean; + + private adsBackoff: BackoffTimeout; + private lrsBackoff: BackoffTimeout; + + private adsCallState: AdsCallState | null = null; + + /** + * The number of authorities that are using this client. Streams should only + * be started if refcount > 0 + */ + private refcount = 0; + + /** + * Map of type to latest accepted version string for that type + */ + public resourceTypeVersionMap: Map = new Map(); + constructor(public xdsClient: XdsClient, bootstrapNode: Node, private xdsServerConfig: XdsServerConfig) { + this.adsBackoff = new BackoffTimeout(() => { + this.maybeStartAdsStream(); + }); + this.adsBackoff.unref(); + this.lrsBackoff = new BackoffTimeout(() => { + this.maybeStartLrsStream(); + }); + this.lrsBackoff.unref(); + this.ignoreResourceDeletion = xdsServerConfig.serverFeatures.includes('ignore_resource_deletion'); + const userAgentName = 'gRPC Node Pure JS'; + this.adsNode = { + ...bootstrapNode, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lb.does_not_support_overprovisioning'], + }; + this.lrsNode = { + ...bootstrapNode, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lrs.supports_send_all_clusters'], + }; + } + + private trace(text: string) { + trace(this.xdsServerConfig.serverUri + ' ' + text); + } + + subscribe(type: XdsResourceType, name: XdsResourceName) { + this.adsCallState?.subscribe(type, name); + } + + unsubscribe(type: XdsResourceType, name: XdsResourceName) { + this.adsCallState?.unsubscribe(type, name); + } + + ref() { + this.refcount += 1; + } + + unref() { + this.refcount -= 1; + } +} + +interface ClientMapEntry { + serverConfig: XdsServerConfig; + client: XdsSingleServerClient; +} + +type ClientResourceStatus = 'REQUESTED' | 'DOES_NOT_EXIST' | 'ACKED' | 'NACKED'; + +interface ResourceMetadata { + clientStatus: ClientResourceStatus; + updateTime: Date | null; + version: string | null; + failedVersion: string | null; + failedDetails: string | null; + failedUpdateTime: string | null; +} + +interface ResourceState { + watchers: Set; + cachedResource: object | null; + meta: ResourceMetadata; + deletionIgnored: boolean; +} + +interface AuthorityState { + client: XdsSingleServerClient; + /** + * type -> key -> state + */ + resourceMap: Map>; +} + +export class XdsClient { + /** + * authority -> authority state + */ + public authorityStateMap: Map = new Map(); + private clients: ClientMapEntry[] = []; + + constructor(private bootstrapInfoOverride?: BootstrapInfo) {} + + private getBootstrapInfo() { + if (this.bootstrapInfoOverride) { + return this.bootstrapInfoOverride; + } else { + return loadBootstrapInfo(); + } + } + + private getOrCreateClient(authority: string): XdsSingleServerClient { + const bootstrapInfo = this.getBootstrapInfo(); + let serverConfig: XdsServerConfig; + if (authority === ':old') { + serverConfig = bootstrapInfo.xdsServers[0]; + } else { + if (authority in bootstrapInfo.authorities) { + serverConfig = bootstrapInfo.authorities[authority].xdsServers?.[0] ?? bootstrapInfo.xdsServers[0]; + } else { + throw new Error(`Authority ${authority} not found in bootstrap authorities list`); + } + } + for (const entry of this.clients) { + if (serverConfigEqual(serverConfig, entry.serverConfig)) { + return entry.client; + } + } + const client = new XdsSingleServerClient(this, bootstrapInfo.node, serverConfig); + this.clients.push({client, serverConfig}); + return client; + } + + watchResource(type: XdsResourceType, name: string, watcher: ResourceWatcherInterface) { + const resourceName = parseXdsResourceName(name, type.getTypeUrl()); + let authorityState = this.authorityStateMap.get(resourceName.authority); + if (!authorityState) { + authorityState = { + client: this.getOrCreateClient(resourceName.authority), + resourceMap: new Map() + }; + authorityState.client.ref(); + } + let keyMap = authorityState.resourceMap.get(type); + if (!keyMap) { + keyMap = new Map(); + authorityState.resourceMap.set(type, keyMap); + } + let entry = keyMap.get(resourceName.key); + let isNewSubscription = false; + if (!entry) { + isNewSubscription = true; + entry = { + watchers: new Set(), + cachedResource: null, + deletionIgnored: false, + meta: { + clientStatus: 'REQUESTED', + updateTime: null, + version: null, + failedVersion: null, + failedUpdateTime: null, + failedDetails: null + } + }; + keyMap.set(resourceName.key, entry); + } + entry.watchers.add(watcher); + if (entry.cachedResource) { + process.nextTick(() => { + if (entry?.cachedResource) { + watcher.onGenericResourceChanged(entry.cachedResource); + } + }); + } + if (isNewSubscription) { + authorityState.client.subscribe(type, resourceName); + } + } + + cancelResourceWatch(type: XdsResourceType, name: string, watcher: ResourceWatcherInterface) { + const resourceName = parseXdsResourceName(name, type.getTypeUrl()); + const authorityState = this.authorityStateMap.get(resourceName.authority); + if (!authorityState) { + return; + } + const entry = authorityState.resourceMap.get(type)?.get(resourceName.key); + if (entry) { + entry.watchers.delete(watcher); + if (entry.watchers.size === 0) { + authorityState.resourceMap.get(type)!.delete(resourceName.key); + authorityState.client.unsubscribe(type, resourceName); + if (authorityState.resourceMap.get(type)!.size === 0) { + authorityState.resourceMap.delete(type); + if (authorityState.resourceMap.size === 0) { + authorityState.client.unref(); + this.authorityStateMap.delete(resourceName.authority); + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts new file mode 100644 index 000000000..5a0187261 --- /dev/null +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -0,0 +1,239 @@ +/* + * 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. + * + */ + +import { CDS_TYPE_URL, CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from "../resources"; +import { XdsResourceType } from "./xds-resource-type"; +import { experimental } from "@grpc/grpc-js"; +import { XdsServerConfig } from "../xds-bootstrap"; +import { Duration__Output } from "../generated/google/protobuf/Duration"; +import { OutlierDetection__Output } from "../generated/envoy/config/cluster/v3/OutlierDetection"; +import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; + +import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; +import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; +import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; +import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; + +export interface OutlierDetectionUpdate { + intervalMs: number | null; + baseEjectionTimeMs: number | null; + maxEjectionTimeMs: number | null; + maxEjectionPercent: number | null; + successRateConfig: Partial | null; + failurePercentageConfig: Partial | null; +} + +export interface CdsUpdate { + type: 'AGGREGATE' | 'EDS' | 'LOGICAL_DNS'; + name: string; + aggregateChildren: string[]; + lrsLoadReportingServer?: XdsServerConfig; + maxConcurrentRequests?: number; + edsServiceName?: string; + dnsHostname?: string; + outlierDetectionUpdate?: OutlierDetectionUpdate; +} + +function durationToMs(duration: Duration__Output): number { + return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; +} + +function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Output | null): OutlierDetectionUpdate | undefined { + if (!EXPERIMENTAL_OUTLIER_DETECTION) { + return undefined; + } + if (!outlierDetection) { + /* No-op outlier detection config, with all fields unset. */ + return { + intervalMs: null, + baseEjectionTimeMs: null, + maxEjectionTimeMs: null, + maxEjectionPercent: null, + successRateConfig: null, + failurePercentageConfig: null + }; + } + let successRateConfig: Partial | null = null; + /* Success rate ejection is enabled by default, so we only disable it if + * enforcing_success_rate is set and it has the value 0 */ + if (!outlierDetection.enforcing_success_rate || outlierDetection.enforcing_success_rate.value > 0) { + successRateConfig = { + enforcement_percentage: outlierDetection.enforcing_success_rate?.value, + minimum_hosts: outlierDetection.success_rate_minimum_hosts?.value, + request_volume: outlierDetection.success_rate_request_volume?.value, + stdev_factor: outlierDetection.success_rate_stdev_factor?.value + }; + } + let failurePercentageConfig: Partial | null = null; + /* Failure percentage ejection is disabled by default, so we only enable it + * if enforcing_failure_percentage is set and it has a value greater than 0 */ + if (outlierDetection.enforcing_failure_percentage && outlierDetection.enforcing_failure_percentage.value > 0) { + failurePercentageConfig = { + enforcement_percentage: outlierDetection.enforcing_failure_percentage.value, + minimum_hosts: outlierDetection.failure_percentage_minimum_hosts?.value, + request_volume: outlierDetection.failure_percentage_request_volume?.value, + threshold: outlierDetection.failure_percentage_threshold?.value + } + } + return { + intervalMs: outlierDetection.interval ? durationToMs(outlierDetection.interval) : null, + baseEjectionTimeMs: outlierDetection.base_ejection_time ? durationToMs(outlierDetection.base_ejection_time) : null, + maxEjectionTimeMs: outlierDetection.max_ejection_time ? durationToMs(outlierDetection.max_ejection_time) : null, + maxEjectionPercent : outlierDetection.max_ejection_percent?.value ?? null, + successRateConfig: successRateConfig, + failurePercentageConfig: failurePercentageConfig + }; +} + + +export class ClusterResourceType extends XdsResourceType { + private static singleton: ClusterResourceType = new ClusterResourceType(); + + private constructor() { + super(); + } + + static get() { + return ClusterResourceType.singleton; + } + + getTypeUrl(): string { + return CDS_TYPE_URL; + } + + private validateNonnegativeDuration(duration: Duration__Output | null): boolean { + if (!duration) { + return true; + } + /* The maximum values here come from the official Protobuf documentation: + * https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration + */ + return Number(duration.seconds) >= 0 && + Number(duration.seconds) <= 315_576_000_000 && + duration.nanos >= 0 && + duration.nanos <= 999_999_999; + } + + private validatePercentage(percentage: UInt32Value__Output | null): boolean { + if (!percentage) { + return true; + } + return percentage.value >=0 && percentage.value <= 100; + } + + private validateResource(message: Cluster__Output): CdsUpdate | null { + if (message.lb_policy !== 'ROUND_ROBIN') { + return null; + } + if (message.lrs_server) { + if (!message.lrs_server.self) { + return null; + } + } + if (EXPERIMENTAL_OUTLIER_DETECTION) { + if (message.outlier_detection) { + if (!this.validateNonnegativeDuration(message.outlier_detection.interval)) { + return null; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.base_ejection_time)) { + return null; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.max_ejection_time)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.max_ejection_percent)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_success_rate)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.failure_percentage_threshold)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_failure_percentage)) { + return null; + } + } + } + if (message.cluster_discovery_type === 'cluster_type') { + if (!(message.cluster_type?.typed_config && message.cluster_type.typed_config.type_url === CLUSTER_CONFIG_TYPE_URL)) { + return null; + } + const clusterConfig = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, message.cluster_type.typed_config.value); + if (clusterConfig.clusters.length === 0) { + return null; + } + return { + type: 'AGGREGATE', + name: message.name, + aggregateChildren: clusterConfig.clusters, + outlierDetectionUpdate: convertOutlierDetectionUpdate(null) + }; + } else { + let maxConcurrentRequests: number | undefined = undefined; + for (const threshold of message.circuit_breakers?.thresholds ?? []) { + if (threshold.priority === 'DEFAULT') { + maxConcurrentRequests = threshold.max_requests?.value; + } + } + if (message.type === 'EDS') { + if (!message.eds_cluster_config?.eds_config?.ads) { + return null; + } + return { + type: 'EDS', + name: message.name, + aggregateChildren: [], + maxConcurrentRequests: maxConcurrentRequests, + edsServiceName: message.eds_cluster_config.service_name === '' ? undefined : message.eds_cluster_config.service_name, + lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, + outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) + } + } else if (message.type === 'LOGICAL_DNS') { + if (!message.load_assignment) { + return null; + } + if (message.load_assignment.endpoints.length !== 1) { + return null; + } + if (message.load_assignment.endpoints[0].lb_endpoints.length !== 1) { + return null; + } + const socketAddress = message.load_assignment.endpoints[0].lb_endpoints[0].endpoint?.address?.socket_address; + if (!socketAddress) { + return null; + } + if (socketAddress.address === '') { + return null; + } + if (socketAddress.port_specifier !== 'port_value') { + return null; + } + return { + type: 'LOGICAL_DNS', + name: message.name, + aggregateChildren: [], + maxConcurrentRequests: maxConcurrentRequests, + dnsHostname: `${socketAddress.address}:${socketAddress.port_value}`, + lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, + outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) + }; + } + } + return null; + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts new file mode 100644 index 000000000..7067f14ab --- /dev/null +++ b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts @@ -0,0 +1,129 @@ +import { experimental, logVerbosity } from "@grpc/grpc-js"; +import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; +import { XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; +import { Locality__Output } from "../generated/envoy/config/core/v3/Locality"; +import { SocketAddress__Output } from "../generated/envoy/config/core/v3/SocketAddress"; +import { isIPv4, isIPv6 } from "net"; +import { Any__Output } from "../generated/google/protobuf/Any"; +import { EDS_TYPE_URL, decodeSingleResource } from "../resources"; +import { Watcher, XdsClient } from "../xds-client2"; + +const TRACER_NAME = 'xds_client'; + +const UINT32_MAX = 0xFFFFFFFF; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +function localitiesEqual(a: Locality__Output, b: Locality__Output) { + return a.region === b.region && a.sub_zone === b.sub_zone && a.zone === b.zone; +} + +function addressesEqual(a: SocketAddress__Output, b: SocketAddress__Output) { + return a.address === b.address && a.port_value === b.port_value; +} + +export class EndpointResourceType extends XdsResourceType { + private static singleton: EndpointResourceType = new EndpointResourceType(); + + private constructor() { + super(); + } + + static get() { + return EndpointResourceType.singleton; + } + + getTypeUrl(): string { + return EDS_TYPE_URL; + } + + private validateResource(message: ClusterLoadAssignment__Output): ClusterLoadAssignment__Output | null { + const seenLocalities: {locality: Locality__Output, priority: number}[] = []; + const seenAddresses: SocketAddress__Output[] = []; + const priorityTotalWeights: Map = new Map(); + for (const endpoint of message.endpoints) { + if (!endpoint.locality) { + trace('EDS validation: endpoint locality unset'); + return null; + } + for (const {locality, priority} of seenLocalities) { + if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) { + trace('EDS validation: endpoint locality duplicated: ' + JSON.stringify(locality) + ', priority=' + priority); + return null; + } + } + seenLocalities.push({locality: endpoint.locality, priority: endpoint.priority}); + for (const lb of endpoint.lb_endpoints) { + const socketAddress = lb.endpoint?.address?.socket_address; + if (!socketAddress) { + trace('EDS validation: endpoint socket_address not set'); + return null; + } + if (socketAddress.port_specifier !== 'port_value') { + trace('EDS validation: socket_address.port_specifier !== "port_value"'); + return null; + } + if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) { + trace('EDS validation: address not a valid IPv4 or IPv6 address: ' + socketAddress.address); + return null; + } + for (const address of seenAddresses) { + if (addressesEqual(socketAddress, address)) { + trace('EDS validation: duplicate address seen: ' + address); + return null; + } + } + seenAddresses.push(socketAddress); + } + priorityTotalWeights.set(endpoint.priority, (priorityTotalWeights.get(endpoint.priority) ?? 0) + (endpoint.load_balancing_weight?.value ?? 0)); + } + for (const totalWeight of priorityTotalWeights.values()) { + if (totalWeight > UINT32_MAX) { + trace('EDS validation: total weight > UINT32_MAX') + return null; + } + } + for (const priority of priorityTotalWeights.keys()) { + if (priority > 0 && !priorityTotalWeights.has(priority - 1)) { + trace('EDS validation: priorities not contiguous'); + return null; + } + } + return message; + } + + decode(resource: Any__Output): XdsDecodeResult { + if (resource.type_url !== EDS_TYPE_URL) { + throw new Error( + `ADS Error: Invalid resource type ${resource.type_url}, expected ${EDS_TYPE_URL}` + ); + } + const message = decodeSingleResource(EDS_TYPE_URL, resource.value); + const validatedMessage = this.validateResource(message); + if (validatedMessage) { + return { + name: validatedMessage.cluster_name, + value: validatedMessage + }; + } else { + return { + name: message.cluster_name, + error: 'Listener message validation failed' + }; + } + } + + allResourcesRequiredInSotW(): boolean { + return true; + } + + static startWatch(client: XdsClient, name: string, watcher: Watcher) { + client.watchResource(EndpointResourceType.get(), name, watcher); + } + + static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { + client.cancelResourceWatch(EndpointResourceType.get(), name, watcher); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts new file mode 100644 index 000000000..f6bf8002a --- /dev/null +++ b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts @@ -0,0 +1,134 @@ +/* + * 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. + * + */ + +import { logVerbosity, experimental } from "@grpc/grpc-js"; +import { EXPERIMENTAL_FAULT_INJECTION } from "../environment"; +import { Listener__Output } from "../generated/envoy/config/listener/v3/Listener"; +import { Any__Output } from "../generated/google/protobuf/Any"; +import { HTTP_CONNECTION_MANGER_TYPE_URL, LDS_TYPE_URL, decodeSingleResource } from "../resources"; +import { XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; +import { getTopLevelFilterUrl, validateTopLevelFilter } from "../http-filter"; +import { RouteConfigurationResourceType } from "./route-config-resource-type"; +import { Watcher, XdsClient } from "../xds-client2"; + +const TRACER_NAME = 'xds_client'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; + +export class ListenerResourceType extends XdsResourceType { + private static singleton: ListenerResourceType = new ListenerResourceType(); + private constructor() { + super(); + } + + static get() { + return ListenerResourceType.singleton; + } + getTypeUrl(): string { + return LDS_TYPE_URL; + } + + private validateResource(message: Listener__Output): Listener__Output | null { + if ( + !( + message.api_listener?.api_listener && + message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL + ) + ) { + return null; + } + const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, message.api_listener!.api_listener.value); + if (EXPERIMENTAL_FAULT_INJECTION) { + const filterNames = new Set(); + for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { + if (filterNames.has(httpFilter.name)) { + trace('LDS response validation failed: duplicate HTTP filter name ' + httpFilter.name); + return null; + } + filterNames.add(httpFilter.name); + if (!validateTopLevelFilter(httpFilter)) { + trace('LDS response validation failed: ' + httpFilter.name + ' filter validation failed'); + return null; + } + /* Validate that the last filter, and only the last filter, is the + * router filter. */ + const filterUrl = getTopLevelFilterUrl(httpFilter.typed_config!) + if (index < httpConnectionManager.http_filters.length - 1) { + if (filterUrl === ROUTER_FILTER_URL) { + trace('LDS response validation failed: router filter is before end of list'); + return null; + } + } else { + if (filterUrl !== ROUTER_FILTER_URL) { + trace('LDS response validation failed: final filter is ' + filterUrl); + return null; + } + } + } + } + switch (httpConnectionManager.route_specifier) { + case 'rds': + if (!httpConnectionManager.rds?.config_source?.ads) { + return null; + } + return message; + case 'route_config': + if (!RouteConfigurationResourceType.get().validateResource(httpConnectionManager.route_config!)) { + return null; + } + return message; + } + return null; + } + + decode(resource: Any__Output): XdsDecodeResult { + if (resource.type_url !== LDS_TYPE_URL) { + throw new Error( + `ADS Error: Invalid resource type ${resource.type_url}, expected ${LDS_TYPE_URL}` + ); + } + const message = decodeSingleResource(LDS_TYPE_URL, resource.value); + const validatedMessage = this.validateResource(message); + if (validatedMessage) { + return { + name: validatedMessage.name, + value: validatedMessage + }; + } else { + return { + name: message.name, + error: 'Listener message validation failed' + }; + } + } + + allResourcesRequiredInSotW(): boolean { + return true; + } + + static startWatch(client: XdsClient, name: string, watcher: Watcher) { + client.watchResource(ListenerResourceType.get(), name, watcher); + } + + static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { + client.cancelResourceWatch(ListenerResourceType.get(), name, watcher); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts new file mode 100644 index 000000000..b4fc274be --- /dev/null +++ b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts @@ -0,0 +1,196 @@ +/* + * 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. + * + */ + +import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from "../environment"; +import { RetryPolicy__Output } from "../generated/envoy/config/route/v3/RetryPolicy"; +import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; +import { Any__Output } from "../generated/google/protobuf/Any"; +import { Duration__Output } from "../generated/google/protobuf/Duration"; +import { validateOverrideFilter } from "../http-filter"; +import { RDS_TYPE_URL, decodeSingleResource } from "../resources"; +import { Watcher, XdsClient } from "../xds-client2"; +import { XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; + +const SUPPORTED_PATH_SPECIFIERS = ['prefix', 'path', 'safe_regex']; +const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ + 'exact_match', + 'safe_regex_match', + 'range_match', + 'present_match', + 'prefix_match', + 'suffix_match']; +const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; + +const UINT32_MAX = 0xFFFFFFFF; + +function durationToMs(duration: Duration__Output | null): number | null { + if (duration === null) { + return null; + } + return (Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000) | 0; +} + +export class RouteConfigurationResourceType extends XdsResourceType { + private static singleton: RouteConfigurationResourceType = new RouteConfigurationResourceType(); + + private constructor() { + super(); + } + + static get() { + return RouteConfigurationResourceType.singleton; + } + + getTypeUrl(): string { + return RDS_TYPE_URL; + } + + private validateRetryPolicy(policy: RetryPolicy__Output | null): boolean { + if (policy === null) { + return true; + } + const numRetries = policy.num_retries?.value ?? 1 + if (numRetries < 1) { + return false; + } + if (policy.retry_back_off) { + if (!policy.retry_back_off.base_interval) { + return false; + } + const baseInterval = durationToMs(policy.retry_back_off.base_interval)!; + const maxInterval = durationToMs(policy.retry_back_off.max_interval) ?? (10 * baseInterval); + if (!(maxInterval >= baseInterval) && (baseInterval > 0)) { + return false; + } + } + return true; + } + + public validateResource(message: RouteConfiguration__Output): RouteConfiguration__Output | null { + // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation + for (const virtualHost of message.virtual_hosts) { + for (const domainPattern of virtualHost.domains) { + const starIndex = domainPattern.indexOf('*'); + const lastStarIndex = domainPattern.lastIndexOf('*'); + // A domain pattern can have at most one wildcard * + if (starIndex !== lastStarIndex) { + return null; + } + // A wildcard * can either be absent or at the beginning or end of the pattern + if (!(starIndex === -1 || starIndex === 0 || starIndex === domainPattern.length - 1)) { + return null; + } + } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return null; + } + } + } + if (EXPERIMENTAL_RETRY) { + if (!this.validateRetryPolicy(virtualHost.retry_policy)) { + return null; + } + } + for (const route of virtualHost.routes) { + const match = route.match; + if (!match) { + return null; + } + if (SUPPORTED_PATH_SPECIFIERS.indexOf(match.path_specifier) < 0) { + return null; + } + for (const headers of match.headers) { + if (SUPPPORTED_HEADER_MATCH_SPECIFIERS.indexOf(headers.header_match_specifier) < 0) { + return null; + } + } + if (route.action !== 'route') { + return null; + } + if ((route.route === undefined) || (route.route === null) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { + return null; + } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return null; + } + } + } + if (EXPERIMENTAL_RETRY) { + if (!this.validateRetryPolicy(route.route.retry_policy)) { + return null; + } + } + if (route.route!.cluster_specifier === 'weighted_clusters') { + let weightSum = 0; + for (const clusterWeight of route.route.weighted_clusters!.clusters) { + weightSum += clusterWeight.weight?.value ?? 0; + } + if (weightSum === 0 || weightSum > UINT32_MAX) { + return null; + } + if (EXPERIMENTAL_FAULT_INJECTION) { + for (const weightedCluster of route.route!.weighted_clusters!.clusters) { + for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) { + if (!validateOverrideFilter(filterConfig)) { + return null; + } + } + } + } + } + } + } + return message; + } + + decode(resource: Any__Output): XdsDecodeResult { + if (resource.type_url !== RDS_TYPE_URL) { + throw new Error( + `ADS Error: Invalid resource type ${resource.type_url}, expected ${RDS_TYPE_URL}` + ); + } + const message = decodeSingleResource(RDS_TYPE_URL, resource.value); + const validatedMessage = this.validateResource(message); + if (validatedMessage) { + return { + name: validatedMessage.name, + value: validatedMessage + }; + } else { + return { + name: message.name, + error: 'Listener message validation failed' + }; + } + } + + allResourcesRequiredInSotW(): boolean { + return false; + } + + static startWatch(client: XdsClient, name: string, watcher: Watcher) { + client.watchResource(RouteConfigurationResourceType.get(), name, watcher); + } + + static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { + client.cancelResourceWatch(RouteConfigurationResourceType.get(), name, watcher); + } +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts new file mode 100644 index 000000000..b8daf5401 --- /dev/null +++ b/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts @@ -0,0 +1,32 @@ +/* + * 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. + * + */ + +import { Any__Output } from "../generated/google/protobuf/Any"; + +export interface XdsDecodeResult { + name: string; + value?: object; + error?: string; +} + +export abstract class XdsResourceType { + abstract getTypeUrl(): string; + + abstract decode(resource: Any__Output): XdsDecodeResult; + + abstract allResourcesRequiredInSotW(): boolean; +} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts index 656a25418..3e45a8fae 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts @@ -15,14 +15,88 @@ * */ +import { FailurePercentageEjectionConfig, SuccessRateEjectionConfig } from "@grpc/grpc-js/build/src/load-balancer-outlier-detection"; import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; +import { OutlierDetection__Output } from "../generated/envoy/config/cluster/v3/OutlierDetection"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from "../resources"; +import { XdsServerConfig } from "../xds-bootstrap"; import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; -export class CdsState extends BaseXdsStreamState implements XdsStreamState { +export interface OutlierDetectionUpdate { + intervalMs: number | null; + baseEjectionTimeMs: number | null; + maxEjectionTimeMs: number | null; + maxEjectionPercent: number | null; + successRateConfig: Partial | null; + failurePercentageConfig: Partial | null; +} + +export interface CdsUpdate { + type: 'AGGREGATE' | 'EDS' | 'LOGICAL_DNS'; + name: string; + aggregateChildren: string[]; + lrsLoadReportingServer?: XdsServerConfig; + maxConcurrentRequests?: number; + edsServiceName?: string; + dnsHostname?: string; + outlierDetectionUpdate?: OutlierDetectionUpdate; +} + +function durationToMs(duration: Duration__Output): number { + return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; +} + +function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Output | null): OutlierDetectionUpdate | undefined { + if (!EXPERIMENTAL_OUTLIER_DETECTION) { + return undefined; + } + if (!outlierDetection) { + /* No-op outlier detection config, with all fields unset. */ + return { + intervalMs: null, + baseEjectionTimeMs: null, + maxEjectionTimeMs: null, + maxEjectionPercent: null, + successRateConfig: null, + failurePercentageConfig: null + }; + } + let successRateConfig: Partial | null = null; + /* Success rate ejection is enabled by default, so we only disable it if + * enforcing_success_rate is set and it has the value 0 */ + if (!outlierDetection.enforcing_success_rate || outlierDetection.enforcing_success_rate.value > 0) { + successRateConfig = { + enforcement_percentage: outlierDetection.enforcing_success_rate?.value, + minimum_hosts: outlierDetection.success_rate_minimum_hosts?.value, + request_volume: outlierDetection.success_rate_request_volume?.value, + stdev_factor: outlierDetection.success_rate_stdev_factor?.value + }; + } + let failurePercentageConfig: Partial | null = null; + /* Failure percentage ejection is disabled by default, so we only enable it + * if enforcing_failure_percentage is set and it has a value greater than 0 */ + if (outlierDetection.enforcing_failure_percentage && outlierDetection.enforcing_failure_percentage.value > 0) { + failurePercentageConfig = { + enforcement_percentage: outlierDetection.enforcing_failure_percentage.value, + minimum_hosts: outlierDetection.failure_percentage_minimum_hosts?.value, + request_volume: outlierDetection.failure_percentage_request_volume?.value, + threshold: outlierDetection.failure_percentage_threshold?.value + } + } + return { + intervalMs: outlierDetection.interval ? durationToMs(outlierDetection.interval) : null, + baseEjectionTimeMs: outlierDetection.base_ejection_time ? durationToMs(outlierDetection.base_ejection_time) : null, + maxEjectionTimeMs: outlierDetection.max_ejection_time ? durationToMs(outlierDetection.max_ejection_time) : null, + maxEjectionPercent : outlierDetection.max_ejection_percent?.value ?? null, + successRateConfig: successRateConfig, + failurePercentageConfig: failurePercentageConfig + }; +} + +export class CdsState extends BaseXdsStreamState implements XdsStreamState { protected isStateOfTheWorld(): boolean { return true; } @@ -53,75 +127,105 @@ export class CdsState extends BaseXdsStreamState implements Xds return percentage.value >=0 && percentage.value <= 100; } - public validateResponse(message: Cluster__Output): boolean { + public validateResponse(message: Cluster__Output): CdsUpdate | null { + if (message.lb_policy !== 'ROUND_ROBIN') { + return null; + } + if (message.lrs_server) { + if (!message.lrs_server.self) { + return null; + } + } + if (EXPERIMENTAL_OUTLIER_DETECTION) { + if (message.outlier_detection) { + if (!this.validateNonnegativeDuration(message.outlier_detection.interval)) { + return null; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.base_ejection_time)) { + return null; + } + if (!this.validateNonnegativeDuration(message.outlier_detection.max_ejection_time)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.max_ejection_percent)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_success_rate)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.failure_percentage_threshold)) { + return null; + } + if (!this.validatePercentage(message.outlier_detection.enforcing_failure_percentage)) { + return null; + } + } + } if (message.cluster_discovery_type === 'cluster_type') { if (!(message.cluster_type?.typed_config && message.cluster_type.typed_config.type_url === CLUSTER_CONFIG_TYPE_URL)) { - return false; + return null; } const clusterConfig = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, message.cluster_type.typed_config.value); if (clusterConfig.clusters.length === 0) { - return false; + return null; } + return { + type: 'AGGREGATE', + name: message.name, + aggregateChildren: clusterConfig.clusters, + outlierDetectionUpdate: convertOutlierDetectionUpdate(null) + }; } else { + let maxConcurrentRequests: number | undefined = undefined; + for (const threshold of message.circuit_breakers?.thresholds ?? []) { + if (threshold.priority === 'DEFAULT') { + maxConcurrentRequests = threshold.max_requests?.value; + } + } if (message.type === 'EDS') { if (!message.eds_cluster_config?.eds_config?.ads) { - return false; + return null; + } + return { + type: 'EDS', + name: message.name, + aggregateChildren: [], + maxConcurrentRequests: maxConcurrentRequests, + edsServiceName: message.eds_cluster_config.service_name === '' ? undefined : message.eds_cluster_config.service_name, + lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, + outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) } } else if (message.type === 'LOGICAL_DNS') { if (!message.load_assignment) { - return false; + return null; } if (message.load_assignment.endpoints.length !== 1) { - return false; + return null; } if (message.load_assignment.endpoints[0].lb_endpoints.length !== 1) { - return false; + return null; } const socketAddress = message.load_assignment.endpoints[0].lb_endpoints[0].endpoint?.address?.socket_address; if (!socketAddress) { - return false; + return null; } if (socketAddress.address === '') { - return false; + return null; } if (socketAddress.port_specifier !== 'port_value') { - return false; - } - } - } - if (message.lb_policy !== 'ROUND_ROBIN') { - return false; - } - if (message.lrs_server) { - if (!message.lrs_server.self) { - return false; - } - } - if (EXPERIMENTAL_OUTLIER_DETECTION) { - if (message.outlier_detection) { - if (!this.validateNonnegativeDuration(message.outlier_detection.interval)) { - return false; - } - if (!this.validateNonnegativeDuration(message.outlier_detection.base_ejection_time)) { - return false; - } - if (!this.validateNonnegativeDuration(message.outlier_detection.max_ejection_time)) { - return false; - } - if (!this.validatePercentage(message.outlier_detection.max_ejection_percent)) { - return false; - } - if (!this.validatePercentage(message.outlier_detection.enforcing_success_rate)) { - return false; - } - if (!this.validatePercentage(message.outlier_detection.failure_percentage_threshold)) { - return false; - } - if (!this.validatePercentage(message.outlier_detection.enforcing_failure_percentage)) { - return false; + return null; } + return { + type: 'LOGICAL_DNS', + name: message.name, + aggregateChildren: [], + maxConcurrentRequests: maxConcurrentRequests, + dnsHostname: `${socketAddress.address}:${socketAddress.port_value}`, + lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, + outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) + }; } } - return true; + return null; } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts index b043ebbc0..9bf8773a8 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts @@ -39,7 +39,7 @@ function addressesEqual(a: SocketAddress__Output, b: SocketAddress__Output) { return a.address === b.address && a.port_value === b.port_value; } -export class EdsState extends BaseXdsStreamState implements XdsStreamState { +export class EdsState extends BaseXdsStreamState implements XdsStreamState { protected getResourceName(resource: ClusterLoadAssignment__Output): string { return resource.cluster_name; } @@ -62,12 +62,12 @@ export class EdsState extends BaseXdsStreamState for (const endpoint of message.endpoints) { if (!endpoint.locality) { trace('EDS validation: endpoint locality unset'); - return false; + return null; } for (const {locality, priority} of seenLocalities) { if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) { trace('EDS validation: endpoint locality duplicated: ' + JSON.stringify(locality) + ', priority=' + priority); - return false; + return null; } } seenLocalities.push({locality: endpoint.locality, priority: endpoint.priority}); @@ -75,20 +75,20 @@ export class EdsState extends BaseXdsStreamState const socketAddress = lb.endpoint?.address?.socket_address; if (!socketAddress) { trace('EDS validation: endpoint socket_address not set'); - return false; + return null; } if (socketAddress.port_specifier !== 'port_value') { trace('EDS validation: socket_address.port_specifier !== "port_value"'); - return false; + return null; } if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) { trace('EDS validation: address not a valid IPv4 or IPv6 address: ' + socketAddress.address); - return false; + return null; } for (const address of seenAddresses) { if (addressesEqual(socketAddress, address)) { trace('EDS validation: duplicate address seen: ' + address); - return false; + return null; } } seenAddresses.push(socketAddress); @@ -98,15 +98,15 @@ export class EdsState extends BaseXdsStreamState for (const totalWeight of priorityTotalWeights.values()) { if (totalWeight > UINT32_MAX) { trace('EDS validation: total weight > UINT32_MAX') - return false; + return null; } } for (const priority of priorityTotalWeights.keys()) { if (priority > 0 && !priorityTotalWeights.has(priority - 1)) { trace('EDS validation: priorities not contiguous'); - return false; + return null; } } - return true; + return message; } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts index c215076db..e81152e4f 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts @@ -22,6 +22,7 @@ import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from '../resources'; import { getTopLevelFilterUrl, validateTopLevelFilter } from '../http-filter'; import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; +import { XdsServerConfig } from "../xds-bootstrap"; const TRACER_NAME = 'xds_client'; @@ -31,7 +32,7 @@ function trace(text: string): void { const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; -export class LdsState extends BaseXdsStreamState implements XdsStreamState { +export class LdsState extends BaseXdsStreamState implements XdsStreamState { protected getResourceName(resource: Listener__Output): string { return resource.name; } @@ -42,18 +43,18 @@ export class LdsState extends BaseXdsStreamState implements Xd return true; } - constructor(private rdsState: RdsState, updateResourceNames: () => void) { - super(updateResourceNames); + constructor(xdsServer: XdsServerConfig, private rdsState: RdsState, updateResourceNames: () => void) { + super(xdsServer, updateResourceNames); } - public validateResponse(message: Listener__Output): boolean { + public validateResponse(message: Listener__Output): Listener__Output | null { if ( !( message.api_listener?.api_listener && message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL ) ) { - return false; + return null; } const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, message.api_listener!.api_listener.value); if (EXPERIMENTAL_FAULT_INJECTION) { @@ -61,12 +62,12 @@ export class LdsState extends BaseXdsStreamState implements Xd for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { if (filterNames.has(httpFilter.name)) { trace('LDS response validation failed: duplicate HTTP filter name ' + httpFilter.name); - return false; + return null; } filterNames.add(httpFilter.name); if (!validateTopLevelFilter(httpFilter)) { trace('LDS response validation failed: ' + httpFilter.name + ' filter validation failed'); - return false; + return null; } /* Validate that the last filter, and only the last filter, is the * router filter. */ @@ -74,22 +75,28 @@ export class LdsState extends BaseXdsStreamState implements Xd if (index < httpConnectionManager.http_filters.length - 1) { if (filterUrl === ROUTER_FILTER_URL) { trace('LDS response validation failed: router filter is before end of list'); - return false; + return null; } } else { if (filterUrl !== ROUTER_FILTER_URL) { trace('LDS response validation failed: final filter is ' + filterUrl); - return false; + return null; } } } } switch (httpConnectionManager.route_specifier) { case 'rds': - return !!httpConnectionManager.rds?.config_source?.ads; + if (!httpConnectionManager.rds?.config_source?.ads) { + return null; + } + return message; case 'route_config': - return this.rdsState.validateResponse(httpConnectionManager.route_config!); + if (!this.rdsState.validateResponse(httpConnectionManager.route_config!)) { + return null; + } + return message; } - return false; + return null; } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index fef694517..e5ef604f1 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -41,7 +41,7 @@ function durationToMs(duration: Duration__Output | null): number | null { return (Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000) | 0; } -export class RdsState extends BaseXdsStreamState implements XdsStreamState { +export class RdsState extends BaseXdsStreamState implements XdsStreamState { protected isStateOfTheWorld(): boolean { return false; } @@ -73,7 +73,7 @@ export class RdsState extends BaseXdsStreamState imp return true; } - validateResponse(message: RouteConfiguration__Output): boolean { + validateResponse(message: RouteConfiguration__Output) { // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation for (const virtualHost of message.virtual_hosts) { for (const domainPattern of virtualHost.domains) { @@ -81,54 +81,54 @@ export class RdsState extends BaseXdsStreamState imp const lastStarIndex = domainPattern.lastIndexOf('*'); // A domain pattern can have at most one wildcard * if (starIndex !== lastStarIndex) { - return false; + return null; } // A wildcard * can either be absent or at the beginning or end of the pattern if (!(starIndex === -1 || starIndex === 0 || starIndex === domainPattern.length - 1)) { - return false; + return null; } } if (EXPERIMENTAL_FAULT_INJECTION) { for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { - return false; + return null; } } } if (EXPERIMENTAL_RETRY) { if (!this.validateRetryPolicy(virtualHost.retry_policy)) { - return false; + return null; } } for (const route of virtualHost.routes) { const match = route.match; if (!match) { - return false; + return null; } if (SUPPORTED_PATH_SPECIFIERS.indexOf(match.path_specifier) < 0) { - return false; + return null; } for (const headers of match.headers) { if (SUPPPORTED_HEADER_MATCH_SPECIFIERS.indexOf(headers.header_match_specifier) < 0) { - return false; + return null; } } if (route.action !== 'route') { - return false; + return null; } if ((route.route === undefined) || (route.route === null) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { - return false; + return null; } if (EXPERIMENTAL_FAULT_INJECTION) { for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { - return false; + return null; } } } if (EXPERIMENTAL_RETRY) { if (!this.validateRetryPolicy(route.route.retry_policy)) { - return false; + return null; } } if (route.route!.cluster_specifier === 'weighted_clusters') { @@ -137,13 +137,13 @@ export class RdsState extends BaseXdsStreamState imp weightSum += clusterWeight.weight?.value ?? 0; } if (weightSum === 0 || weightSum > UINT32_MAX) { - return false; + return null; } if (EXPERIMENTAL_FAULT_INJECTION) { for (const weightedCluster of route.route!.weighted_clusters!.clusters) { for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) { if (!validateOverrideFilter(filterConfig)) { - return false; + return null; } } } @@ -151,6 +151,6 @@ export class RdsState extends BaseXdsStreamState imp } } } - return true; + return message; } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts index 86b81f01c..69b6bb715 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts @@ -17,6 +17,9 @@ import { experimental, logVerbosity, Metadata, status, StatusObject } from "@grpc/grpc-js"; import { Any__Output } from "../generated/google/protobuf/Any"; +import { ResourceCache } from "../resource-cache"; +import { XdsServerConfig } from "../xds-bootstrap"; +import { XdsResourceKey } from "../resources"; const TRACER_NAME = 'xds_client'; @@ -53,7 +56,7 @@ export interface HandleResponseResult { missing: string[]; } -export interface XdsStreamState { +export interface XdsStreamState { versionInfo: string; nonce: string; getResourceNames(): string[]; @@ -67,34 +70,45 @@ export interface XdsStreamState { reportStreamError(status: StatusObject): void; reportAdsStreamStart(): void; - addWatcher(name: string, watcher: Watcher): void; - removeWatcher(resourceName: string, watcher: Watcher): void; + addWatcher(name: string, watcher: Watcher): void; + removeWatcher(resourceName: string, watcher: Watcher): void; } -interface SubscriptionEntry { - watchers: Watcher[]; - cachedResponse: ResponseType | null; +export interface XdsSubscriptionTracker { + getResourceNames(): string[]; + handleResourceUpdates(resourceList: ResourcePair[]): void; + + reportStreamError(status: StatusObject): void; + reportAdsStreamStart(): void; + + addWatcher(key: string, watcher: Watcher): void; + removeWatcher(key: string, watcher: Watcher): void; +} + +interface SubscriptionEntry { + watchers: Watcher[]; + cachedResponse: UpdateType | null; resourceTimer: NodeJS.Timer; deletionIgnored: boolean; } const RESOURCE_TIMEOUT_MS = 15_000; -export abstract class BaseXdsStreamState implements XdsStreamState { +export abstract class BaseXdsStreamState implements XdsStreamState { versionInfo = ''; nonce = ''; - private subscriptions: Map> = new Map>(); + private subscriptions: Map> = new Map>(); private isAdsStreamRunning = false; private ignoreResourceDeletion = false; - constructor(private updateResourceNames: () => void) {} + constructor(protected xdsServer: XdsServerConfig, private updateResourceNames: () => void) {} protected trace(text: string) { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, this.getProtocolName() + ' | ' + text); } - private startResourceTimer(subscriptionEntry: SubscriptionEntry) { + private startResourceTimer(subscriptionEntry: SubscriptionEntry) { clearTimeout(subscriptionEntry.resourceTimer); subscriptionEntry.resourceTimer = setTimeout(() => { for (const watcher of subscriptionEntry.watchers) { @@ -103,7 +117,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState }, RESOURCE_TIMEOUT_MS); } - addWatcher(name: string, watcher: Watcher): void { + addWatcher(name: string, watcher: Watcher): void { this.trace('Adding watcher for name ' + name); let subscriptionEntry = this.subscriptions.get(name); let addedName = false; @@ -134,7 +148,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState this.updateResourceNames(); } } - removeWatcher(resourceName: string, watcher: Watcher): void { + removeWatcher(resourceName: string, watcher: Watcher): void { this.trace('Removing watcher for name ' + resourceName); const subscriptionEntry = this.subscriptions.get(resourceName); if (subscriptionEntry !== undefined) { @@ -167,7 +181,8 @@ export abstract class BaseXdsStreamState implements XdsStreamState const resourceName = this.getResourceName(resource); allResourceNames.add(resourceName); const subscriptionEntry = this.subscriptions.get(resourceName); - if (this.validateResponse(resource)) { + const update = this.validateResponse(resource); + if (update) { result.accepted.push({ name: resourceName, raw: raw}); @@ -176,11 +191,11 @@ export abstract class BaseXdsStreamState implements XdsStreamState /* Use process.nextTick to prevent errors from the watcher from * bubbling up through here. */ process.nextTick(() => { - watcher.onValidUpdate(resource); + watcher.onValidUpdate(update); }); } clearTimeout(subscriptionEntry.resourceTimer); - subscriptionEntry.cachedResponse = resource; + subscriptionEntry.cachedResponse = update; if (subscriptionEntry.deletionIgnored) { experimental.log(logVerbosity.INFO, `Received resource with previously ignored deletion: ${resourceName}`); subscriptionEntry.deletionIgnored = false; @@ -277,7 +292,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState * the RDS validateResponse. * @param resource The resource object sent by the xDS server */ - public abstract validateResponse(resource: ResponseType): boolean; + public abstract validateResponse(resource: ResponseType): UpdateType | null; /** * Get the name of a resource object. The name is some field of the object, so * getting it depends on the specific type. @@ -285,6 +300,7 @@ export abstract class BaseXdsStreamState implements XdsStreamState */ protected abstract getResourceName(resource: ResponseType): string; protected abstract getProtocolName(): string; + protected abstract getTypeUrl(): string; /** * Indicates whether responses are "state of the world", i.e. that they * contain all resources and that omitted previously-seen resources should From 2b455e7d18d1c108bd1ca901678c757434c054b7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 31 May 2023 14:05:10 -0700 Subject: [PATCH 486/694] grpc-js: Fix a couple of minor issues --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/client.ts | 36 ++++++++++++++------- packages/grpc-js/src/load-balancing-call.ts | 8 +++-- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index dd341ef03..7d8c1a597 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.14", + "version": "1.8.15", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index f96f8fdf0..bdbdc6e97 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -323,7 +323,7 @@ export class Client { emitter.call = call; let responseMessage: ResponseType | null = null; let receivedStatus = false; - const callerStackError = new Error(); + let callerStackError: Error | null = new Error(); call.start(callProperties.metadata, { onReceiveMetadata: (metadata) => { emitter.emit('metadata', metadata); @@ -342,19 +342,22 @@ export class Client { receivedStatus = true; if (status.code === Status.OK) { if (responseMessage === null) { - const callerStack = getErrorStackString(callerStackError); + const callerStack = getErrorStackString(callerStackError!); callProperties.callback!(callErrorFromStatus({ code: Status.INTERNAL, details: 'No message received', metadata: status.metadata - }, callerStack)); + }, /*callerStack*/'')); } else { callProperties.callback!(null, responseMessage); } } else { - const callerStack = getErrorStackString(callerStackError); - callProperties.callback!(callErrorFromStatus(status, callerStack)); + const callerStack = getErrorStackString(callerStackError!); + callProperties.callback!(callErrorFromStatus(status, /*callerStack*/'')); } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; emitter.emit('status', status); }, }); @@ -448,7 +451,7 @@ export class Client { emitter.call = call; let responseMessage: ResponseType | null = null; let receivedStatus = false; - const callerStackError = new Error(); + let callerStackError: Error | null = new Error(); call.start(callProperties.metadata, { onReceiveMetadata: (metadata) => { emitter.emit('metadata', metadata); @@ -467,7 +470,7 @@ export class Client { receivedStatus = true; if (status.code === Status.OK) { if (responseMessage === null) { - const callerStack = getErrorStackString(callerStackError); + const callerStack = getErrorStackString(callerStackError!); callProperties.callback!(callErrorFromStatus({ code: Status.INTERNAL, details: 'No message received', @@ -477,9 +480,12 @@ export class Client { callProperties.callback!(null, responseMessage); } } else { - const callerStack = getErrorStackString(callerStackError); + const callerStack = getErrorStackString(callerStackError!); callProperties.callback!(callErrorFromStatus(status, callerStack)); } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; emitter.emit('status', status); }, }); @@ -577,7 +583,7 @@ export class Client { * call after that. */ stream.call = call; let receivedStatus = false; - const callerStackError = new Error(); + let callerStackError: Error | null = new Error(); call.start(callProperties.metadata, { onReceiveMetadata(metadata: Metadata) { stream.emit('metadata', metadata); @@ -593,9 +599,12 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { - const callerStack = getErrorStackString(callerStackError); + const callerStack = getErrorStackString(callerStackError!); stream.emit('error', callErrorFromStatus(status, callerStack)); } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; stream.emit('status', status); }, }); @@ -673,7 +682,7 @@ export class Client { * call after that. */ stream.call = call; let receivedStatus = false; - const callerStackError = new Error(); + let callerStackError: Error | null = new Error(); call.start(callProperties.metadata, { onReceiveMetadata(metadata: Metadata) { stream.emit('metadata', metadata); @@ -688,9 +697,12 @@ export class Client { receivedStatus = true; stream.push(null); if (status.code !== Status.OK) { - const callerStack = getErrorStackString(callerStackError); + const callerStack = getErrorStackString(callerStackError!); stream.emit('error', callErrorFromStatus(status, callerStack)); } + /* Avoid retaining the callerStackError object in the call context of + * the status event handler. */ + callerStackError = null; stream.emit('status', status); }, }); diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index f74933983..d88bdb809 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -216,14 +216,18 @@ export class LoadBalancingCall implements Call { break; case PickResultType.DROP: const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); - this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'DROP'); + setImmediate(() => { + this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'DROP'); + }); break; case PickResultType.TRANSIENT_FAILURE: if (this.metadata.getOptions().waitForReady) { this.channel.queueCallForPick(this); } else { const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); - this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'PROCESSED'); + setImmediate(() => { + this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'PROCESSED'); + }); } break; case PickResultType.QUEUE: From cd87b1287fec340b71e96ad7a2745b9c2057da1b Mon Sep 17 00:00:00 2001 From: Xuan Wang Date: Thu, 1 Jun 2023 00:00:47 +0000 Subject: [PATCH 487/694] Don't fail target if sub-target already failed --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index b87e3306c..4a83d44d5 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -170,9 +170,6 @@ main() { run_test $test || (( ++failed_tests )) done echo "Failed test suites: ${failed_tests}" - if (( failed_tests > 0 )); then - exit 1 - fi } main "$@" From 039032cdfb87b455d883e813002b4ed52fb472c9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 1 Jun 2023 09:43:16 -0700 Subject: [PATCH 488/694] Merge pull request #2457 from XuanWang-Amos/xds_duplicate_bugs PSM Interop: Don't fail target if sub-target already failed --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index b87e3306c..4a83d44d5 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -170,9 +170,6 @@ main() { run_test $test || (( ++failed_tests )) done echo "Failed test suites: ${failed_tests}" - if (( failed_tests > 0 )); then - exit 1 - fi } main "$@" From 81e1f75b628e0b2e097ddc1c8029baf973b53d60 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 1 Jun 2023 17:16:24 -0700 Subject: [PATCH 489/694] grpc-js-xds: Support string_match in header matching --- packages/grpc-js-xds/src/matcher.ts | 48 +++++++++++++++---- packages/grpc-js-xds/src/resolver-xds.ts | 28 +++++++++-- .../src/xds-stream-state/rds-state.ts | 3 +- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/packages/grpc-js-xds/src/matcher.ts b/packages/grpc-js-xds/src/matcher.ts index b173c30e0..148df7f85 100644 --- a/packages/grpc-js-xds/src/matcher.ts +++ b/packages/grpc-js-xds/src/matcher.ts @@ -37,14 +37,19 @@ export interface ValueMatcher { } export class ExactValueMatcher implements ValueMatcher { - constructor(private targetValue: string) {} + constructor(private targetValue: string, private ignoreCase: boolean) { + } apply(value: string) { - return value === this.targetValue; + if (this.ignoreCase) { + return value.toLowerCase() === this.targetValue.toLowerCase(); + } else { + return value === this.targetValue; + } } toString() { - return 'Exact(' + this.targetValue + ')'; + return 'Exact(' + this.targetValue + ', ignore_case=' + this.ignoreCase + ')'; } } @@ -94,26 +99,51 @@ export class PresentValueMatcher implements ValueMatcher { } export class PrefixValueMatcher implements ValueMatcher { - constructor(private prefix: string) {} + constructor(private prefix: string, private ignoreCase: boolean) { + } apply(value: string) { - return value.startsWith(this.prefix); + if (this.ignoreCase) { + return value.toLowerCase().startsWith(this.prefix.toLowerCase()); + } else { + return value.startsWith(this.prefix); + } } toString() { - return 'Prefix(' + this.prefix + ')'; + return 'Prefix(' + this.prefix + ', ignore_case=' + this.ignoreCase + ')'; } } export class SuffixValueMatcher implements ValueMatcher { - constructor(private suffix: string) {} + constructor(private suffix: string, private ignoreCase: boolean) {} apply(value: string) { - return value.endsWith(this.suffix); + if (this.ignoreCase) { + return value.toLowerCase().endsWith(this.suffix.toLowerCase()); + } else { + return value.endsWith(this.suffix); + } + } + + toString() { + return 'Suffix(' + this.suffix + ', ignore_case=' + this.ignoreCase + ')'; + } +} + +export class ContainsValueMatcher implements ValueMatcher { + constructor(private contains: string, private ignoreCase: boolean) {} + + apply(value: string) { + if (this.ignoreCase) { + return value.toLowerCase().includes(this.contains.toLowerCase()); + } else { + return value.includes(this.contains); + } } toString() { - return 'Suffix(' + this.suffix + ')'; + return 'Contains(' + this.contains + + ', ignore_case=' + this.ignoreCase + ')'; } } diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 9879a2c6e..6c2ba4325 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -37,7 +37,7 @@ import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderM import ConfigSelector = experimental.ConfigSelector; import LoadBalancingConfig = experimental.LoadBalancingConfig; import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluster-manager'; -import { ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; +import { ContainsValueMatcher, ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; import { envoyFractionToFraction, Fraction } from "./fraction"; import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resources'; @@ -136,7 +136,7 @@ function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Match let valueChecker: ValueMatcher; switch (headerMatch.header_match_specifier) { case 'exact_match': - valueChecker = new ExactValueMatcher(headerMatch.exact_match!); + valueChecker = new ExactValueMatcher(headerMatch.exact_match!, false); break; case 'safe_regex_match': valueChecker = new SafeRegexValueMatcher(headerMatch.safe_regex_match!.regex); @@ -150,10 +150,30 @@ function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Match valueChecker = new PresentValueMatcher(); break; case 'prefix_match': - valueChecker = new PrefixValueMatcher(headerMatch.prefix_match!); + valueChecker = new PrefixValueMatcher(headerMatch.prefix_match!, false); break; case 'suffix_match': - valueChecker = new SuffixValueMatcher(headerMatch.suffix_match!); + valueChecker = new SuffixValueMatcher(headerMatch.suffix_match!, false); + break; + case 'string_match': + const stringMatch = headerMatch.string_match! + switch (stringMatch.match_pattern) { + case 'exact': + valueChecker = new ExactValueMatcher(stringMatch.exact!, stringMatch.ignore_case); + break; + case 'safe_regex': + valueChecker = new SafeRegexValueMatcher(stringMatch.safe_regex!.regex); + break; + case 'prefix': + valueChecker = new PrefixValueMatcher(stringMatch.prefix!, stringMatch.ignore_case); + break; + case 'suffix': + valueChecker = new SuffixValueMatcher(stringMatch.suffix!, stringMatch.ignore_case); + break; + case 'contains': + valueChecker = new ContainsValueMatcher(stringMatch.contains!, stringMatch.ignore_case); + break; + } break; default: valueChecker = new RejectValueMatcher(); diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index fef694517..57e25edd6 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -29,7 +29,8 @@ const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ 'range_match', 'present_match', 'prefix_match', - 'suffix_match']; + 'suffix_match', + 'string_match']; const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; const UINT32_MAX = 0xFFFFFFFF; From fb98794f7b4c6be95dd0b70784d28cbe2d1c35c1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 13 Jun 2023 17:43:05 -0700 Subject: [PATCH 490/694] grpc-js-xds: Complete federation implementation --- packages/grpc-js-xds/package.json | 3 +- packages/grpc-js-xds/src/csds.ts | 146 +- packages/grpc-js-xds/src/load-balancer-cds.ts | 23 +- .../src/load-balancer-xds-cluster-resolver.ts | 20 +- packages/grpc-js-xds/src/resolver-xds.ts | 38 +- packages/grpc-js-xds/src/resource-cache.ts | 75 - packages/grpc-js-xds/src/resources.ts | 23 +- packages/grpc-js-xds/src/xds-bootstrap.ts | 22 +- packages/grpc-js-xds/src/xds-client.ts | 1574 +++++++++-------- packages/grpc-js-xds/src/xds-client2.ts | 450 ----- .../cluster-resource-type.ts | 54 +- .../endpoint-resource-type.ts | 10 +- .../listener-resource-type.ts | 10 +- .../route-config-resource-type.ts | 13 +- .../xds-resource-type/xds-resource-type.ts | 61 +- .../src/xds-stream-state/cds-state.ts | 231 --- .../src/xds-stream-state/eds-state.ts | 112 -- .../src/xds-stream-state/lds-state.ts | 102 -- .../src/xds-stream-state/rds-state.ts | 157 -- .../src/xds-stream-state/xds-stream-state.ts | 310 ---- packages/grpc-js-xds/test/client.ts | 8 +- packages/grpc-js-xds/test/framework.ts | 47 +- .../grpc-js-xds/test/test-cluster-type.ts | 32 +- packages/grpc-js-xds/test/test-core.ts | 6 +- packages/grpc-js-xds/test/test-federation.ts | 209 +++ .../test/test-listener-resource-name.ts | 2 + packages/grpc-js-xds/test/test-nack.ts | 24 +- packages/grpc-js-xds/test/xds-server.ts | 15 +- 28 files changed, 1423 insertions(+), 2354 deletions(-) delete mode 100644 packages/grpc-js-xds/src/resource-cache.ts delete mode 100644 packages/grpc-js-xds/src/xds-client2.ts delete mode 100644 packages/grpc-js-xds/src/xds-stream-state/cds-state.ts delete mode 100644 packages/grpc-js-xds/src/xds-stream-state/eds-state.ts delete mode 100644 packages/grpc-js-xds/src/xds-stream-state/lds-state.ts delete mode 100644 packages/grpc-js-xds/src/xds-stream-state/rds-state.ts delete mode 100644 packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts create mode 100644 packages/grpc-js-xds/test/test-federation.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 7c735b652..7fd7e700d 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -45,7 +45,8 @@ "dependencies": { "@grpc/proto-loader": "^0.6.0", "google-auth-library": "^7.0.2", - "re2-wasm": "^1.0.1" + "re2-wasm": "^1.0.1", + "vscode-uri": "^3.0.7" }, "peerDependencies": { "@grpc/grpc-js": "~1.8.0" diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts index 95a3bf7b7..06fb5c50b 100644 --- a/packages/grpc-js-xds/src/csds.ts +++ b/packages/grpc-js-xds/src/csds.ts @@ -15,19 +15,18 @@ * */ -import { Node } from "./generated/envoy/config/core/v3/Node"; import { ClientConfig, _envoy_service_status_v3_ClientConfig_GenericXdsConfig as GenericXdsConfig } from "./generated/envoy/service/status/v3/ClientConfig"; import { ClientStatusDiscoveryServiceHandlers } from "./generated/envoy/service/status/v3/ClientStatusDiscoveryService"; import { ClientStatusRequest__Output } from "./generated/envoy/service/status/v3/ClientStatusRequest"; import { ClientStatusResponse } from "./generated/envoy/service/status/v3/ClientStatusResponse"; import { Timestamp } from "./generated/google/protobuf/Timestamp"; -import { AdsTypeUrl, CDS_TYPE_URL, EDS_TYPE_URL, LDS_TYPE_URL, RDS_TYPE_URL } from "./resources"; -import { HandleResponseResult } from "./xds-stream-state/xds-stream-state"; +import { xdsResourceNameToString } from "./resources"; import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, status, experimental, loadPackageDefinition, logVerbosity } from '@grpc/grpc-js'; import { loadSync } from "@grpc/proto-loader"; import { ProtoGrpcType as CsdsProtoGrpcType } from "./generated/csds"; import registerAdminService = experimental.registerAdminService; +import { XdsClient } from "./xds-client"; const TRACER_NAME = 'csds'; @@ -47,115 +46,46 @@ function dateToProtoTimestamp(date?: Date | null): Timestamp | null { } } -let clientNode: Node | null = null; +const registeredClients: XdsClient[] = []; -const configStatus = { - [EDS_TYPE_URL]: new Map(), - [CDS_TYPE_URL]: new Map(), - [RDS_TYPE_URL]: new Map(), - [LDS_TYPE_URL]: new Map() -}; - -/** - * This function only accepts a v3 Node message, because we are only supporting - * v3 CSDS and it only handles v3 Nodes. If the client is actually using v2 xDS - * APIs, it should just provide the equivalent v3 Node message. - * @param node The Node message for the client that is requesting resources - */ -export function setCsdsClientNode(node: Node) { - clientNode = node; -} - -/** - * Update the config status maps from the list of names of requested resources - * for a specific type URL. These lists are the source of truth for determining - * what resources will be listed in the CSDS response. Any resource that is not - * in this list will never actually be applied anywhere. - * @param typeUrl The resource type URL - * @param names The list of resource names that are being requested - */ -export function updateCsdsRequestedNameList(typeUrl: AdsTypeUrl, names: string[]) { - trace('Update type URL ' + typeUrl + ' with names [' + names + ']'); - const currentTime = dateToProtoTimestamp(new Date()); - const configMap = configStatus[typeUrl]; - for (const name of names) { - if (!configMap.has(name)) { - configMap.set(name, { - type_url: typeUrl, - name: name, - last_updated: currentTime, - client_status: 'REQUESTED' - }); - } - } - for (const name of configMap.keys()) { - if (!names.includes(name)) { - configMap.delete(name); - } - } +export function registerXdsClientWithCsds(client: XdsClient) { + registeredClients.push(client); } -/** - * Update the config status maps from the result of parsing a single ADS - * response. All resources that validated are considered "ACKED", and all - * resources that failed validation are considered "NACKED". - * @param typeUrl The type URL of resources in this response - * @param versionInfo The version info field from this response - * @param updates The lists of resources that passed and failed validation - */ -export function updateCsdsResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, updates: HandleResponseResult) { - const currentTime = dateToProtoTimestamp(new Date()); - const configMap = configStatus[typeUrl]; - for (const {name, raw} of updates.accepted) { - const mapEntry = configMap.get(name); - if (mapEntry) { - trace('Updated ' + typeUrl + ' resource ' + name + ' to state ACKED'); - mapEntry.client_status = 'ACKED'; - mapEntry.version_info = versionInfo; - mapEntry.xds_config = raw; - mapEntry.error_state = null; - mapEntry.last_updated = currentTime; - } - } - for (const {name, error, raw} of updates.rejected) { - const mapEntry = configMap.get(name); - if (mapEntry) { - trace('Updated ' + typeUrl + ' resource ' + name + ' to state NACKED'); - mapEntry.client_status = 'NACKED'; - mapEntry.error_state = { - failed_configuration: raw, - last_update_attempt: currentTime, - details: error, - version_info: versionInfo - }; - } - } - for (const name of updates.missing) { - const mapEntry = configMap.get(name); - if (mapEntry) { - trace('Updated ' + typeUrl + ' resource ' + name + ' to state DOES_NOT_EXIST'); - mapEntry.client_status = 'DOES_NOT_EXIST'; - mapEntry.version_info = versionInfo; - mapEntry.xds_config = null; - mapEntry.error_state = null; - mapEntry.last_updated = currentTime; +function getCurrentConfigList(): ClientConfig[] { + const result: ClientConfig[] = []; + for (const client of registeredClients) { + if (!client.adsNode) { + continue; } - } -} - -function getCurrentConfig(): ClientConfig { - const genericConfigList: GenericXdsConfig[] = []; - for (const configMap of Object.values(configStatus)) { - for (const configValue of configMap.values()) { - genericConfigList.push(configValue); + const genericConfigList: GenericXdsConfig[] = []; + for (const [authority, authorityState] of client.authorityStateMap) { + for (const [type, typeMap] of authorityState.resourceMap) { + for (const [key, resourceState] of typeMap) { + const typeUrl = type.getFullTypeUrl(); + const meta = resourceState.meta; + genericConfigList.push({ + name: xdsResourceNameToString({authority, key}, typeUrl), + client_status: meta.clientStatus, + version_info: meta.version, + xds_config: meta.clientStatus === 'ACKED' ? meta.rawResource : undefined, + last_updated: meta.updateTime ? dateToProtoTimestamp(meta.updateTime) : undefined, + error_state: meta.clientStatus === 'NACKED' ? { + details: meta.failedDetails, + failed_configuration: meta.rawResource, + last_update_attempt: meta.failedUpdateTime ? dateToProtoTimestamp(meta.failedUpdateTime) : undefined, + version_info: meta.failedVersion + } : undefined + }); + } + } } + result.push({ + node: client.adsNode, + generic_xds_configs: genericConfigList + }); } - const config = { - node: clientNode, - generic_xds_configs: genericConfigList - }; - trace('Sending current config ' + JSON.stringify(config, undefined, 2)); - return config; + return result; } const csdsImplementation: ClientStatusDiscoveryServiceHandlers = { @@ -169,7 +99,7 @@ const csdsImplementation: ClientStatusDiscoveryServiceHandlers = { return; } callback(null, { - config: [getCurrentConfig()] + config: getCurrentConfigList() }); }, StreamClientStatus(call: ServerDuplexStream) { @@ -182,7 +112,7 @@ const csdsImplementation: ClientStatusDiscoveryServiceHandlers = { return; } call.write({ - config: [getCurrentConfig()] + config: getCurrentConfigList() }); }); call.on('end', () => { diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 92559e911..6f791299c 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -16,7 +16,7 @@ */ import { connectivityState, status, Metadata, logVerbosity, experimental } from '@grpc/grpc-js'; -import { getSingletonXdsClient, XdsClient } from './xds-client'; +import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import SubchannelAddress = experimental.SubchannelAddress; import UnavailablePicker = experimental.UnavailablePicker; @@ -29,13 +29,12 @@ import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBa import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; import QueuePicker = experimental.QueuePicker; -import { Watcher } from './xds-stream-state/xds-stream-state'; import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; import { Duration__Output } from './generated/google/protobuf/Duration'; import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler, XdsClusterResolverLoadBalancingConfig } from './load-balancer-xds-cluster-resolver'; import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources'; -import { CdsUpdate, OutlierDetectionUpdate } from './xds-stream-state/cds-state'; +import { CdsUpdate, ClusterResourceType, OutlierDetectionUpdate } from './xds-resource-type/cluster-resource-type'; const TRACER_NAME = 'cds_balancer'; @@ -195,8 +194,8 @@ export class CdsLoadBalancer implements LoadBalancer { return; } trace('Adding watcher for cluster ' + cluster); - const watcher: Watcher = { - onValidUpdate: (update) => { + const watcher: Watcher = new Watcher({ + onResourceChanged: (update) => { this.clusterTree[cluster].latestUpdate = update; if (update.type === 'AGGREGATE') { const children = update.aggregateChildren @@ -227,6 +226,7 @@ export class CdsLoadBalancer implements LoadBalancer { } }, onResourceDoesNotExist: () => { + trace('Received onResourceDoesNotExist update for cluster ' + cluster); if (cluster in this.clusterTree) { this.clusterTree[cluster].latestUpdate = undefined; this.clusterTree[cluster].children = []; @@ -234,8 +234,9 @@ export class CdsLoadBalancer implements LoadBalancer { this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `CDS resource ${cluster} does not exist`, metadata: new Metadata()})); this.childBalancer.destroy(); }, - onTransientError: (statusObj) => { + onError: (statusObj) => { if (!this.updatedChild) { + trace('Transitioning to transient failure due to onError update for cluster' + cluster); this.channelControlHelper.updateState( connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({ @@ -246,19 +247,23 @@ export class CdsLoadBalancer implements LoadBalancer { ); } } - }; + }); this.clusterTree[cluster] = { watcher: watcher, children: [] }; - this.xdsClient?.addClusterWatcher(cluster, watcher); + if (this.xdsClient) { + ClusterResourceType.startWatch(this.xdsClient, cluster, watcher); + } } private removeCluster(cluster: string) { if (!(cluster in this.clusterTree)) { return; } - this.xdsClient?.removeClusterWatcher(cluster, this.clusterTree[cluster].watcher); + if (this.xdsClient) { + ClusterResourceType.cancelWatch(this.xdsClient, cluster, this.clusterTree[cluster].watcher); + } delete this.clusterTree[cluster]; } diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index daee96825..6803da5c2 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -23,9 +23,8 @@ import { ClusterLoadAssignment__Output } from "./generated/envoy/config/endpoint import { LrsLoadBalancingConfig } from "./load-balancer-lrs"; import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from "./load-balancer-priority"; import { WeightedTarget, WeightedTargetLoadBalancingConfig } from "./load-balancer-weighted-target"; -import { getSingletonXdsClient, XdsClient } from "./xds-client"; +import { getSingletonXdsClient, Watcher, XdsClient } from "./xds-client"; import { DropCategory, XdsClusterImplLoadBalancingConfig } from "./load-balancer-xds-cluster-impl"; -import { Watcher } from "./xds-stream-state/xds-stream-state"; import LoadBalancingConfig = experimental.LoadBalancingConfig; import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; @@ -38,6 +37,7 @@ import ChannelControlHelper = experimental.ChannelControlHelper; import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; import subchannelAddressToString = experimental.subchannelAddressToString; import { serverConfigEqual, validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; +import { EndpointResourceType } from "./xds-resource-type/endpoint-resource-type"; const TRACER_NAME = 'xds_cluster_resolver'; @@ -377,8 +377,8 @@ export class XdsClusterResolver implements LoadBalancer { }; if (mechanism.type === 'EDS') { const edsServiceName = mechanism.eds_service_name ?? mechanism.cluster; - const watcher: Watcher = { - onValidUpdate: update => { + const watcher: Watcher = new Watcher({ + onResourceChanged: update => { mechanismEntry.latestUpdate = getEdsPriorities(update); this.maybeUpdateChild(); }, @@ -386,15 +386,17 @@ export class XdsClusterResolver implements LoadBalancer { trace('Resource does not exist: ' + edsServiceName); mechanismEntry.latestUpdate = [{localities: [], dropCategories: []}]; }, - onTransientError: error => { + onError: error => { if (!mechanismEntry.latestUpdate) { trace('xDS request failed with error ' + error); mechanismEntry.latestUpdate = [{localities: [], dropCategories: []}]; } } - }; + }); mechanismEntry.watcher = watcher; - this.xdsClient?.addEndpointWatcher(edsServiceName, watcher); + if (this.xdsClient) { + EndpointResourceType.startWatch(this.xdsClient, edsServiceName, watcher); + } } else { const resolver = createResolver({scheme: 'dns', path: mechanism.dns_hostname!}, { onSuccessfulResolution: addressList => { @@ -434,7 +436,9 @@ export class XdsClusterResolver implements LoadBalancer { for (const mechanismEntry of this.discoveryMechanismList) { if (mechanismEntry.watcher) { const edsServiceName = mechanismEntry.discoveryMechanism.eds_service_name ?? mechanismEntry.discoveryMechanism.cluster; - this.xdsClient?.removeEndpointWatcher(edsServiceName, mechanismEntry.watcher); + if (this.xdsClient) { + EndpointResourceType.cancelWatch(this.xdsClient, edsServiceName, mechanismEntry.watcher); + } } mechanismEntry.resolver?.destroy(); } diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index f9d8a984b..a934e7285 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -18,7 +18,7 @@ import * as protoLoader from '@grpc/proto-loader'; import { RE2 } from 're2-wasm'; -import { getSingletonXdsClient, XdsClient } from './xds-client'; +import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client'; import { StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions } from '@grpc/grpc-js'; import Resolver = experimental.Resolver; import GrpcUri = experimental.GrpcUri; @@ -27,7 +27,6 @@ import uriToString = experimental.uriToString; import ServiceConfig = experimental.ServiceConfig; import registerResolver = experimental.registerResolver; import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; -import { Watcher } from './xds-stream-state/xds-stream-state'; import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; import { CdsLoadBalancingConfig } from './load-balancer-cds'; @@ -49,6 +48,8 @@ import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; import RetryPolicy = experimental.RetryPolicy; import { BootstrapInfo, loadBootstrapInfo, validateBootstrapConfig } from './xds-bootstrap'; +import { ListenerResourceType } from './xds-resource-type/listener-resource-type'; +import { RouteConfigurationResourceType } from './xds-resource-type/route-config-resource-type'; const TRACER_NAME = 'xds_resolver'; @@ -249,7 +250,7 @@ function formatTemplateString(templateString: string, value: string): string { } export function getListenerResourceName(bootstrapConfig: BootstrapInfo, target: GrpcUri): string { - if (target.authority) { + if (target.authority && target.authority !== '') { if (target.authority in bootstrapConfig.authorities) { return formatTemplateString(bootstrapConfig.authorities[target.authority].clientListenerResourceNameTemplate, target.path); } else { @@ -307,8 +308,8 @@ class XdsResolver implements Resolver { } else { this.xdsClient = getSingletonXdsClient(); } - this.ldsWatcher = { - onValidUpdate: (update: Listener__Output) => { + this.ldsWatcher = new Watcher({ + onResourceChanged: (update: Listener__Output) => { const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, update.api_listener!.api_listener!.value); const defaultTimeout = httpConnectionManager.common_http_protocol_options?.idle_timeout; if (defaultTimeout === null || defaultTimeout === undefined) { @@ -331,16 +332,16 @@ class XdsResolver implements Resolver { const routeConfigName = httpConnectionManager.rds!.route_config_name; if (this.latestRouteConfigName !== routeConfigName) { if (this.latestRouteConfigName !== null) { - this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + RouteConfigurationResourceType.cancelWatch(this.xdsClient, this.latestRouteConfigName, this.rdsWatcher); } - this.xdsClient.addRouteWatcher(httpConnectionManager.rds!.route_config_name, this.rdsWatcher); + RouteConfigurationResourceType.startWatch(this.xdsClient, routeConfigName, this.rdsWatcher); this.latestRouteConfigName = routeConfigName; } break; } case 'route_config': if (this.latestRouteConfigName) { - this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + RouteConfigurationResourceType.cancelWatch(this.xdsClient, this.latestRouteConfigName, this.rdsWatcher); } this.handleRouteConfig(httpConnectionManager.route_config!); break; @@ -348,7 +349,7 @@ class XdsResolver implements Resolver { // This is prevented by the validation rules } }, - onTransientError: (error: StatusObject) => { + onError: (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) { @@ -360,12 +361,12 @@ class XdsResolver implements Resolver { trace('Resolution error for target ' + uriToString(this.target) + ': LDS resource does not exist'); this.reportResolutionError(`Listener ${this.target} does not exist`); } - }; - this.rdsWatcher = { - onValidUpdate: (update: RouteConfiguration__Output) => { + }); + this.rdsWatcher = new Watcher({ + onResourceChanged: (update: RouteConfiguration__Output) => { this.handleRouteConfig(update); }, - onTransientError: (error: StatusObject) => { + onError: (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) { @@ -377,7 +378,7 @@ class XdsResolver implements Resolver { trace('Resolution error for target ' + uriToString(this.target) + ' and route config ' + this.latestRouteConfigName + ': RDS resource does not exist'); this.reportResolutionError(`Route config ${this.latestRouteConfigName} does not exist`); } - } + }); } private refCluster(clusterName: string) { @@ -629,7 +630,7 @@ class XdsResolver implements Resolver { try { this.listenerResourceName = getListenerResourceName(this.bootstrapInfo!, this.target); trace('Resolving target ' + uriToString(this.target) + ' with Listener resource name ' + this.listenerResourceName); - this.xdsClient.addListenerWatcher(this.listenerResourceName, this.ldsWatcher); + ListenerResourceType.startWatch(this.xdsClient, this.listenerResourceName, this.ldsWatcher); this.isLdsWatcherActive = true; } catch (e) { @@ -653,7 +654,8 @@ class XdsResolver implements Resolver { } else { if (!this.isLdsWatcherActive) { trace('Starting resolution for target ' + uriToString(this.target)); - this.xdsClient.addListenerWatcher(this.target.path, this.ldsWatcher); + ListenerResourceType.startWatch(this.xdsClient, this.target.path, this.ldsWatcher); + this.listenerResourceName = this.target.path; this.isLdsWatcherActive = true; } } @@ -661,10 +663,10 @@ class XdsResolver implements Resolver { destroy() { if (this.listenerResourceName) { - this.xdsClient.removeListenerWatcher(this.listenerResourceName, this.ldsWatcher); + ListenerResourceType.cancelWatch(this.xdsClient, this.listenerResourceName, this.ldsWatcher); } if (this.latestRouteConfigName) { - this.xdsClient.removeRouteWatcher(this.latestRouteConfigName, this.rdsWatcher); + RouteConfigurationResourceType.cancelWatch(this.xdsClient, this.latestRouteConfigName, this.rdsWatcher); } } diff --git a/packages/grpc-js-xds/src/resource-cache.ts b/packages/grpc-js-xds/src/resource-cache.ts deleted file mode 100644 index 97ca98b1a..000000000 --- a/packages/grpc-js-xds/src/resource-cache.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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. - * - */ - -import { XdsResourceKey, xdsResourceKeyEqual, XdsResourceName } from "./resources"; - -interface ResourceCacheEntry { - key: XdsResourceKey; - value: ResourceType; - refCount: number; -} - -export class ResourceCache { - /** - * Map authority to a list of key/value pairs - */ - private cache: Map[]> = new Map(); - getAndRef(name: XdsResourceName): ResourceType | undefined { - const mapEntry = this.cache.get(name.authority); - if (!mapEntry) { - return undefined; - } - for (const entry of mapEntry) { - if (xdsResourceKeyEqual(name.key, entry.key)) { - entry.refCount += 1; - return entry.value; - } - } - return undefined; - } - - set(name: XdsResourceName, value: ResourceType): void { - const mapEntry = this.cache.get(name.authority); - if (!mapEntry) { - this.cache.set(name.authority, [{key: name.key, value: value, refCount: 1}]); - return; - } - for (const entry of mapEntry) { - if (xdsResourceKeyEqual(name.key, entry.key)) { - entry.value = value; - return; - } - } - mapEntry.push({key: name.key, value: value, refCount: 1}); - } - - unref(name: XdsResourceName): void { - const mapEntry = this.cache.get(name.authority); - if (!mapEntry) { - return; - } - for (let i = 0; i < mapEntry.length; i++) { - if (xdsResourceKeyEqual(name.key, mapEntry[i].key)) { - mapEntry[i].refCount -= 1; - if (mapEntry[i].refCount === 0) { - mapEntry.splice(i, 1); - } - return; - } - } - } -} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts index 83291d70a..75cd550f3 100644 --- a/packages/grpc-js-xds/src/resources.ts +++ b/packages/grpc-js-xds/src/resources.ts @@ -15,8 +15,11 @@ * */ -// This is a non-public, unstable API, but it's very convenient import { URI } from 'vscode-uri'; +/* Since we are using an internal function from @grpc/proto-loader, we also + * need the top-level import to perform some setup operations. */ +import '@grpc/proto-loader'; +// This is a non-public, unstable API, but it's very convenient import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; @@ -62,6 +65,8 @@ export type AdsOutputType 0) { + const queryParams = uri.query.split('&'); + queryParams.sort(); + queryString = '?' + queryParams.join('&'); + } else { + queryString = ''; } - const queryParams = uri.query.split('&'); - queryParams.sort(); return { authority: uri.authority, - key: `${pathComponents[1]}?${queryParams.join('&')}` + key: `${pathComponents.slice(1).join('/')}${queryString}` }; } diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index b9d8c5fd6..327ee1b06 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -106,6 +106,11 @@ function validateChannelCredsConfig(obj: any): ChannelCredsConfig { }; } +const SUPPORTED_CHANNEL_CREDS_TYPES = [ + 'google_default', + 'insecure' +]; + export function validateXdsServerConfig(obj: any): XdsServerConfig { if (!('server_uri' in obj)) { throw new Error('server_uri field missing in xds_servers element'); @@ -123,9 +128,15 @@ export function validateXdsServerConfig(obj: any): XdsServerConfig { `xds_servers.channel_creds field: expected array, got ${typeof obj.channel_creds}` ); } - if (obj.channel_creds.length === 0) { + let foundSupported = false; + for (const cred of obj.channel_creds) { + if (SUPPORTED_CHANNEL_CREDS_TYPES.includes(cred.type)) { + foundSupported = true; + } + } + if (!foundSupported) { throw new Error( - 'xds_servers.channel_creds field: at least one entry is required' + `xds_servers.channel_creds field: must contain at least one entry with a type in [${SUPPORTED_CHANNEL_CREDS_TYPES}]` ); } if ('server_features' in obj) { @@ -318,6 +329,13 @@ export function validateBootstrapConfig(obj: any): BootstrapInfo { let loadedBootstrapInfo: BootstrapInfo | null = null; +/** + * Load the bootstrap information from the location determined by the + * GRPC_XDS_BOOTSTRAP environment variable, or if that is unset, from the + * GRPC_XDS_BOOTSTRAP_CONFIG environment variable. The value is cached, so any + * calls after the first will just return the cached value. + * @returns + */ export function loadBootstrapInfo(): BootstrapInfo { if (loadedBootstrapInfo !== null) { return loadedBootstrapInfo; diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index d600070aa..71b2a0966 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 gRPC authors. + * 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. @@ -15,39 +15,26 @@ * */ -import * as protoLoader from '@grpc/proto-loader'; -// This is a non-public, unstable API, but it's very convenient -import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; -import { loadPackageDefinition, StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ClientDuplexStream, ServiceError, ChannelCredentials, Channel, connectivityState } from '@grpc/grpc-js'; +import { Channel, ChannelCredentials, ClientDuplexStream, Metadata, StatusObject, connectivityState, experimental, loadPackageDefinition, logVerbosity, status } from "@grpc/grpc-js"; +import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type/xds-resource-type"; +import { XdsResourceName, parseXdsResourceName, xdsResourceNameToString } from "./resources"; +import { Node } from "./generated/envoy/config/core/v3/Node"; +import { BootstrapInfo, XdsServerConfig, loadBootstrapInfo, serverConfigEqual } from "./xds-bootstrap"; +import BackoffTimeout = experimental.BackoffTimeout; +import { DiscoveryRequest } from "./generated/envoy/service/discovery/v3/DiscoveryRequest"; +import { DiscoveryResponse__Output } from "./generated/envoy/service/discovery/v3/DiscoveryResponse"; import * as adsTypes from './generated/ads'; import * as lrsTypes from './generated/lrs'; -import { BootstrapInfo, loadBootstrapInfo, serverConfigEqual, XdsServerConfig } from './xds-bootstrap'; -import { Node } from './generated/envoy/config/core/v3/Node'; -import { AggregatedDiscoveryServiceClient } from './generated/envoy/service/discovery/v3/AggregatedDiscoveryService'; -import { DiscoveryRequest } from './generated/envoy/service/discovery/v3/DiscoveryRequest'; -import { DiscoveryResponse__Output } from './generated/envoy/service/discovery/v3/DiscoveryResponse'; -import { LoadReportingServiceClient } from './generated/envoy/service/load_stats/v3/LoadReportingService'; -import { LoadStatsRequest } from './generated/envoy/service/load_stats/v3/LoadStatsRequest'; -import { LoadStatsResponse__Output } from './generated/envoy/service/load_stats/v3/LoadStatsResponse'; -import { Locality, Locality__Output } from './generated/envoy/config/core/v3/Locality'; -import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; -import { Any__Output } from './generated/google/protobuf/Any'; -import BackoffTimeout = experimental.BackoffTimeout; -import ServiceConfig = experimental.ServiceConfig; -import { createGoogleDefaultCredentials } from './google-default-credentials'; -import { CdsLoadBalancingConfig } from './load-balancer-cds'; -import { EdsState } from './xds-stream-state/eds-state'; -import { CdsState, CdsUpdate } from './xds-stream-state/cds-state'; -import { RdsState } from './xds-stream-state/rds-state'; -import { LdsState } from './xds-stream-state/lds-state'; -import { HandleResponseResult, ResourcePair, Watcher } from './xds-stream-state/xds-stream-state'; -import { ClusterLoadAssignment__Output } from './generated/envoy/config/endpoint/v3/ClusterLoadAssignment'; -import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; -import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; -import { Duration } from './generated/google/protobuf/Duration'; -import { AdsOutputType, AdsTypeUrl, CDS_TYPE_URL, decodeSingleResource, EDS_TYPE_URL, LDS_TYPE_URL, RDS_TYPE_URL } from './resources'; -import { setCsdsClientNode, updateCsdsRequestedNameList, updateCsdsResourceResponse } from './csds'; -import { EXPERIMENTAL_FEDERATION } from './environment'; +import * as protoLoader from '@grpc/proto-loader'; +import { AggregatedDiscoveryServiceClient } from "./generated/envoy/service/discovery/v3/AggregatedDiscoveryService"; +import { LoadReportingServiceClient } from "./generated/envoy/service/load_stats/v3/LoadReportingService"; +import { createGoogleDefaultCredentials } from "./google-default-credentials"; +import { Any__Output } from "./generated/google/protobuf/Any"; +import { LoadStatsRequest } from "./generated/envoy/service/load_stats/v3/LoadStatsRequest"; +import { LoadStatsResponse__Output } from "./generated/envoy/service/load_stats/v3/LoadStatsResponse"; +import { Locality, Locality__Output } from "./generated/envoy/config/core/v3/Locality"; +import { Duration } from "./generated/google/protobuf/Duration"; +import { registerXdsClientWithCsds } from "./csds"; const TRACER_NAME = 'xds_client'; @@ -55,8 +42,6 @@ function trace(text: string): void { experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); } -const clientVersion = require('../../package.json').version; - let loadedProtos: adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType | null = null; function loadAdsProtos(): adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType { @@ -87,6 +72,443 @@ function loadAdsProtos(): adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType { )) as unknown) as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType; } +const clientVersion = require('../../package.json').version; + +export interface ResourceWatcherInterface { + onGenericResourceChanged(resource: object): void; + onError(status: StatusObject): void; + onResourceDoesNotExist(): void; +} + +export interface BasicWatcher { + onResourceChanged(resource: UpdateType): void; + onError(status: StatusObject): void; + onResourceDoesNotExist(): void; +} + +export class Watcher implements ResourceWatcherInterface { + constructor(private internalWatcher: BasicWatcher) {} + onGenericResourceChanged(resource: object): void { + this.internalWatcher.onResourceChanged(resource as unknown as UpdateType); + } + onError(status: StatusObject) { + this.internalWatcher.onError(status); + } + onResourceDoesNotExist() { + this.internalWatcher.onResourceDoesNotExist(); + } +} + +const RESOURCE_TIMEOUT_MS = 15_000; + +class ResourceTimer { + private timer: NodeJS.Timer | null = null; + private resourceSeen = false; + constructor(private callState: AdsCallState, private type: XdsResourceType, private name: XdsResourceName) {} + + maybeCancelTimer() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + } + + markSeen() { + this.resourceSeen = true; + this.maybeCancelTimer(); + } + + markAdsStreamStarted() { + this.maybeStartTimer(); + } + + private maybeStartTimer() { + if (this.resourceSeen) { + return; + } + if (this.timer) { + return; + } + const authorityState = this.callState.client.xdsClient.authorityStateMap.get(this.name.authority); + if (!authorityState) { + return; + } + const resourceState = authorityState.resourceMap.get(this.type)?.get(this.name.key); + if (resourceState?.cachedResource) { + return; + } + this.timer = setTimeout(() => { + this.onTimer(); + }, RESOURCE_TIMEOUT_MS); + } + + private onTimer() { + const authorityState = this.callState.client.xdsClient.authorityStateMap.get(this.name.authority); + const resourceState = authorityState?.resourceMap.get(this.type)?.get(this.name.key); + if (!resourceState) { + return; + } + resourceState.meta.clientStatus = 'DOES_NOT_EXIST'; + for (const watcher of resourceState.watchers) { + watcher.onResourceDoesNotExist(); + } + } +} + +interface AdsParseResult { + type?: XdsResourceType; + typeUrl?: string; + version?: string; + nonce?: string; + errors: string[]; + /** + * authority -> set of keys + */ + resourcesSeen: Map>; + haveValidResources: boolean; +} + +/** + * Responsible for parsing a single ADS response, one resource at a time + */ +class AdsResponseParser { + private result: AdsParseResult = { + errors: [], + resourcesSeen: new Map(), + haveValidResources: false + }; + private updateTime = new Date(); + + constructor(private adsCallState: AdsCallState) {} + + processAdsResponseFields(message: DiscoveryResponse__Output) { + const type = this.adsCallState.client.xdsClient.getResourceType(message.type_url); + if (!type) { + throw new Error(`Unexpected type URL ${message.type_url}`); + } + this.result.type = type; + this.result.typeUrl = message.type_url; + this.result.nonce = message.nonce; + this.result.version = message.version_info; + } + + parseResource(index: number, resource: Any__Output) { + const errorPrefix = `resource index ${index}:`; + if (resource.type_url !== this.result.typeUrl) { + this.result.errors.push(`${errorPrefix} incorrect resource type "${resource.type_url}" (should be "${this.result.typeUrl}")`); + return; + } + if (!this.result.type) { + return; + } + const decodeContext: XdsDecodeContext = { + server: this.adsCallState.client.xdsServerConfig + }; + let decodeResult: XdsDecodeResult; + try { + decodeResult = this.result.type.decode(decodeContext, resource); + } catch (e) { + this.result.errors.push(`${errorPrefix} ${e.message}`); + return; + } + let parsedName: XdsResourceName; + try { + parsedName = parseXdsResourceName(decodeResult.name, this.result.type!.getTypeUrl()); + } catch (e) { + this.result.errors.push(`${errorPrefix} ${e.message}`); + return; + } + this.adsCallState.typeStates.get(this.result.type!)?.subscribedResources.get(parsedName.authority)?.get(parsedName.key)?.markSeen(); + if (this.result.type.allResourcesRequiredInSotW()) { + if (!this.result.resourcesSeen.has(parsedName.authority)) { + this.result.resourcesSeen.set(parsedName.authority, new Set()); + } + this.result.resourcesSeen.get(parsedName.authority)!.add(parsedName.key); + } + const resourceState = this.adsCallState.client.xdsClient.authorityStateMap.get(parsedName.authority)?.resourceMap.get(this.result.type)?.get(parsedName.key); + if (!resourceState) { + // No subscription for this resource + return; + } + if (resourceState.deletionIgnored) { + experimental.log(logVerbosity.INFO, `Received resource with previously ignored deletion: ${decodeResult.name}`); + resourceState.deletionIgnored = false; + } + if (decodeResult.error) { + this.result.errors.push(`${errorPrefix} ${decodeResult.error}`); + process.nextTick(() => { + for (const watcher of resourceState.watchers) { + watcher.onError({code: status.UNAVAILABLE, details: decodeResult.error!, metadata: new Metadata()}); + } + }); + resourceState.meta.clientStatus = 'NACKED'; + resourceState.meta.failedVersion = this.result.version!; + resourceState.meta.failedDetails = decodeResult.error; + resourceState.meta.failedUpdateTime = this.updateTime; + return; + } + if (!decodeResult.value) { + return; + } + this.adsCallState.client.trace('Parsed resource of type ' + this.result.type.getTypeUrl() + ': ' + JSON.stringify(decodeResult.value, undefined, 2)); + this.result.haveValidResources = true; + if (this.result.type.resourcesEqual(resourceState.cachedResource, decodeResult.value)) { + return; + } + resourceState.cachedResource = decodeResult.value; + resourceState.meta = { + clientStatus: 'ACKED', + rawResource: resource, + updateTime: this.updateTime, + version: this.result.version! + }; + process.nextTick(() => { + for (const watcher of resourceState.watchers) { + watcher.onGenericResourceChanged(decodeResult.value!); + } + }); + } + + getResult() { + return this.result; + } +} + +type AdsCall = ClientDuplexStream; + +interface ResourceTypeState { + nonce?: string; + error?: string; + /** + * authority -> key -> timer + */ + subscribedResources: Map>; +} + +class AdsCallState { + public typeStates: Map = new Map(); + private receivedAnyResponse = false; + private sentInitialMessage = false; + constructor(public client: XdsSingleServerClient, private call: AdsCall, private node: Node) { + // Populate subscription map with existing subscriptions + for (const [authority, authorityState] of client.xdsClient.authorityStateMap) { + if (authorityState.client !== client) { + continue; + } + for (const [type, typeMap] of authorityState.resourceMap) { + for (const key of typeMap.keys()) { + this.subscribe(type, {authority, key}, true); + } + } + } + for (const type of this.typeStates.keys()) { + this.updateNames(type); + } + call.on('data', (message: DiscoveryResponse__Output) => { + this.handleResponseMessage(message); + }) + call.on('status', (status: StatusObject) => { + this.handleStreamStatus(status); + }); + call.on('error', () => {}); + } + + private trace(text: string) { + this.client.trace(text); + } + + private handleResponseMessage(message: DiscoveryResponse__Output) { + const parser = new AdsResponseParser(this); + let handledAdsResponseFields: boolean; + try { + parser.processAdsResponseFields(message); + handledAdsResponseFields = true; + } catch (e) { + this.trace('ADS response field parsing failed for type ' + message.type_url); + handledAdsResponseFields = false; + } + if (handledAdsResponseFields) { + for (const [index, resource] of message.resources.entries()) { + parser.parseResource(index, resource); + } + const result = parser.getResult(); + const typeState = this.typeStates.get(result.type!); + if (!typeState) { + this.trace('Type state not found for type ' + result.type!.getTypeUrl()); + return; + } + typeState.nonce = result.nonce; + if (result.errors.length > 0) { + typeState.error = `xDS response validation errors: [${result.errors.join('; ')}]`; + } else { + delete typeState.error; + } + // Delete resources not seen in update if needed + if (result.type!.allResourcesRequiredInSotW()) { + for (const [authority, authorityState] of this.client.xdsClient.authorityStateMap) { + if (authorityState.client !== this.client) { + continue; + } + const typeMap = authorityState.resourceMap.get(result.type!); + if (!typeMap) { + continue; + } + for (const [key, resourceState] of typeMap) { + if (!result.resourcesSeen.get(authority)?.has(key)) { + /* Do nothing for resources that have no cached value. Those are + * handled by the resource timer. */ + if (!resourceState.cachedResource) { + continue; + } + if (this.client.ignoreResourceDeletion) { + experimental.log(logVerbosity.ERROR, 'Ignoring nonexistent resource ' + xdsResourceNameToString({authority, key}, result.type!.getTypeUrl())); + resourceState.deletionIgnored = true; + } else { + resourceState.meta.clientStatus = 'DOES_NOT_EXIST'; + process.nextTick(() => { + for (const watcher of resourceState.watchers) { + watcher.onResourceDoesNotExist(); + } + }); + } + } + } + } + } + if (result.haveValidResources || result.errors.length === 0) { + this.client.resourceTypeVersionMap.set(result.type!, result.version!); + } + this.updateNames(result.type!); + } + } + + private* allWatchers() { + for (const [type, typeState] of this.typeStates) { + for (const [authority, authorityMap] of typeState.subscribedResources) { + for (const key of authorityMap.keys()) { + yield* this.client.xdsClient.authorityStateMap.get(authority)?.resourceMap.get(type)?.get(key)?.watchers ?? []; + } + } + } + } + + private handleStreamStatus(streamStatus: StatusObject) { + this.trace( + 'ADS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details + ); + if (streamStatus.code !== status.OK && !this.receivedAnyResponse) { + for (const watcher of this.allWatchers()) { + watcher.onError(streamStatus); + } + } + } + + hasSubscribedResources(): boolean { + for (const typeState of this.typeStates.values()) { + for (const authorityMap of typeState.subscribedResources.values()) { + if (authorityMap.size > 0) { + return true; + } + } + } + return false; + } + + subscribe(type: XdsResourceType, name: XdsResourceName, delaySend: boolean = false) { + let typeState = this.typeStates.get(type); + if (!typeState) { + typeState = { + nonce: '', + subscribedResources: new Map() + }; + this.typeStates.set(type, typeState); + } + let authorityMap = typeState.subscribedResources.get(name.authority); + if (!authorityMap) { + authorityMap = new Map(); + typeState.subscribedResources.set(name.authority, authorityMap); + } + if (!authorityMap.has(name.key)) { + const timer = new ResourceTimer(this, type, name); + authorityMap.set(name.key, timer); + if (!delaySend) { + this.updateNames(type); + } + } + } + + unsubscribe(type: XdsResourceType, name: XdsResourceName) { + const typeState = this.typeStates.get(type); + if (!typeState) { + return; + } + const authorityMap = typeState.subscribedResources.get(name.authority); + if (!authorityMap) { + return; + } + authorityMap.delete(name.key); + if (authorityMap.size === 0) { + typeState.subscribedResources.delete(name.authority); + } + if (typeState.subscribedResources.size === 0) { + this.typeStates.delete(type); + } + this.updateNames(type); + } + + resourceNamesForRequest(type: XdsResourceType): string[] { + const typeState = this.typeStates.get(type); + if (!typeState) { + return []; + } + const result: string[] = []; + for (const [authority, authorityMap] of typeState.subscribedResources) { + for (const [key, timer] of authorityMap) { + result.push(xdsResourceNameToString({authority, key}, type.getTypeUrl())); + } + } + return result; + } + + updateNames(type: XdsResourceType) { + const typeState = this.typeStates.get(type); + if (!typeState) { + return; + } + const request: DiscoveryRequest = { + node: this.sentInitialMessage ? null : this.node, + type_url: type.getFullTypeUrl(), + response_nonce: typeState.nonce, + resource_names: this.resourceNamesForRequest(type), + version_info: this.client.resourceTypeVersionMap.get(type), + error_detail: typeState.error ? { code: status.UNAVAILABLE, message: typeState.error} : null + }; + this.trace('Sending discovery request: ' + JSON.stringify(request, undefined, 2)); + this.call.write(request); + this.sentInitialMessage = true; + } + + end() { + this.call.end(); + } + + /** + * Should be called when the channel state is READY after starting the + * stream. + */ + markStreamStarted() { + for (const [type, typeState] of this.typeStates) { + for (const [authority, authorityMap] of typeState.subscribedResources) { + for (const resourceTimer of authorityMap.values()) { + resourceTimer.markAdsStreamStarted(); + } + } + } + } +} + +type LrsCall = ClientDuplexStream; + function localityEqual( loc1: Locality__Output, loc2: Locality__Output @@ -140,21 +562,25 @@ interface ClusterLocalityStats { callsSucceeded: number; callsFailed: number; callsInProgress: number; + refcount: number; } interface ClusterLoadReport { callsDropped: Map; uncategorizedCallsDropped: number; - localityStats: ClusterLocalityStats[]; + localityStats: Set; intervalStart: [number, number]; } +interface StatsMapEntry { + clusterName: string; + edsServiceName: string; + refCount: number; + stats: ClusterLoadReport; +} + class ClusterLoadReportMap { - private statsMap: { - clusterName: string; - edsServiceName: string; - stats: ClusterLoadReport; - }[] = []; + private statsMap: Set = new Set(); get( clusterName: string, @@ -171,24 +597,34 @@ class ClusterLoadReportMap { return undefined; } + /** + * Get the indicated map entry if it exists, or create a new one if it does + * not. Increments the refcount of that entry, so a call to this method + * should correspond to a later call to unref + * @param clusterName + * @param edsServiceName + * @returns + */ getOrCreate(clusterName: string, edsServiceName: string): ClusterLoadReport { for (const statsObj of this.statsMap) { if ( statsObj.clusterName === clusterName && statsObj.edsServiceName === edsServiceName ) { + statsObj.refCount += 1; return statsObj.stats; } } const newStats: ClusterLoadReport = { callsDropped: new Map(), uncategorizedCallsDropped: 0, - localityStats: [], + localityStats: new Set(), intervalStart: process.hrtime(), }; - this.statsMap.push({ + this.statsMap.add({ clusterName, edsServiceName, + refCount: 1, stats: newStats, }); return newStats; @@ -203,534 +639,99 @@ class ClusterLoadReportMap { clusterName: statsEntry.clusterName, edsServiceName: statsEntry.edsServiceName, }, - statsEntry.stats, - ]; - } - } -} - -type AdsServiceKind = 'eds' | 'cds' | 'rds' | 'lds'; - -interface AdsState { - eds: EdsState; - cds: CdsState; - rds: RdsState; - lds: LdsState; -} - -function getResponseMessages( - targetTypeUrl: T, - resources: Any__Output[] -): ResourcePair>[] { - const result: ResourcePair>[] = []; - for (const resource of resources) { - if (resource.type_url !== targetTypeUrl) { - throw new Error( - `ADS Error: Invalid resource type ${resource.type_url}, expected ${targetTypeUrl}` - ); - } - result.push({ - resource: decodeSingleResource(targetTypeUrl, resource.value), - raw: resource - }); - } - return result; -} - -class XdsSingleServerClient { - - private adsNode: Node; - /* These client objects need to be nullable so that they can be shut down - * when not in use. If the channel could enter the IDLE state it would remove - * that need. */ - private adsClient: AggregatedDiscoveryServiceClient | null = null; - private adsCall: ClientDuplexStream< - DiscoveryRequest, - DiscoveryResponse__Output - > | null = null; - private receivedAdsResponseOnCurrentStream = false; - - private lrsNode: Node; - private lrsClient: LoadReportingServiceClient | null = null; - private lrsCall: ClientDuplexStream< - LoadStatsRequest, - LoadStatsResponse__Output - > | null = null; - private latestLrsSettings: LoadStatsResponse__Output | null = null; - private receivedLrsSettingsForCurrentStream = false; - - private clusterStatsMap: ClusterLoadReportMap = new ClusterLoadReportMap(); - private statsTimer: NodeJS.Timer; - - private adsState: AdsState; - - private adsBackoff: BackoffTimeout; - private lrsBackoff: BackoffTimeout; - - constructor(bootstrapNode: Node, private xdsServerConfig: XdsServerConfig) { - const edsState = new EdsState(xdsServerConfig, () => { - this.updateNames('eds'); - }); - const cdsState = new CdsState(xdsServerConfig, () => { - this.updateNames('cds'); - }); - const rdsState = new RdsState(xdsServerConfig, () => { - this.updateNames('rds'); - }); - const ldsState = new LdsState(xdsServerConfig, rdsState, () => { - this.updateNames('lds'); - }); - this.adsState = { - eds: edsState, - cds: cdsState, - rds: rdsState, - lds: ldsState, - }; - - this.adsBackoff = new BackoffTimeout(() => { - this.maybeStartAdsStream(); - }); - this.adsBackoff.unref(); - this.lrsBackoff = new BackoffTimeout(() => { - this.maybeStartLrsStream(); - }); - this.lrsBackoff.unref(); - this.statsTimer = setInterval(() => {}, 0); - clearInterval(this.statsTimer); - if (xdsServerConfig.serverFeatures.indexOf('ignore_resource_deletion') >= 0) { - this.adsState.lds.enableIgnoreResourceDeletion(); - this.adsState.cds.enableIgnoreResourceDeletion(); - } - const userAgentName = 'gRPC Node Pure JS'; - this.adsNode = { - ...bootstrapNode, - user_agent_name: userAgentName, - user_agent_version: clientVersion, - client_features: ['envoy.lb.does_not_support_overprovisioning'], - }; - this.lrsNode = { - ...bootstrapNode, - user_agent_name: userAgentName, - user_agent_version: clientVersion, - client_features: ['envoy.lrs.supports_send_all_clusters'], - }; - setCsdsClientNode(this.adsNode); - this.trace('ADS Node: ' + JSON.stringify(this.adsNode, undefined, 2)); - this.trace('LRS Node: ' + JSON.stringify(this.lrsNode, undefined, 2)); - } - - private trace(text: string) { - trace(this.xdsServerConfig.serverUri + ' ' + text); - } - - private handleAdsConnectivityStateUpdate() { - if (!this.adsClient) { - return; - } - const state = this.adsClient.getChannel().getConnectivityState(false); - if (state === connectivityState.READY && this.adsCall) { - this.reportAdsStreamStarted(); - } - if (state === connectivityState.TRANSIENT_FAILURE) { - this.reportStreamError({ - code: status.UNAVAILABLE, - details: 'No connection established to xDS server', - metadata: new Metadata() - }); - } - this.adsClient.getChannel().watchConnectivityState(state, Infinity, () => { - this.handleAdsConnectivityStateUpdate(); - }); - } - - private handleAdsResponse(message: DiscoveryResponse__Output) { - this.receivedAdsResponseOnCurrentStream = true; - this.adsBackoff.reset(); - let handleResponseResult: { - result: HandleResponseResult; - serviceKind: AdsServiceKind; - } | null = null; - try { - switch (message.type_url) { - case EDS_TYPE_URL: - handleResponseResult = { - result: this.adsState.eds.handleResponses( - getResponseMessages(EDS_TYPE_URL, message.resources) - ), - serviceKind: 'eds' - }; - break; - case CDS_TYPE_URL: - handleResponseResult = { - result: this.adsState.cds.handleResponses( - getResponseMessages(CDS_TYPE_URL, message.resources) - ), - serviceKind: 'cds' - }; - break; - case RDS_TYPE_URL: - handleResponseResult = { - result: this.adsState.rds.handleResponses( - getResponseMessages(RDS_TYPE_URL, message.resources) - ), - serviceKind: 'rds' - }; - break; - case LDS_TYPE_URL: - handleResponseResult = { - result: this.adsState.lds.handleResponses( - getResponseMessages(LDS_TYPE_URL, message.resources) - ), - serviceKind: 'lds' - } - break; - } - } catch (e) { - this.trace('Nacking message with protobuf parsing error: ' + e.message); - this.nack(message.type_url, e.message); - return; - } - if (handleResponseResult === null) { - // Null handleResponseResult means that the type_url was unrecognized - this.trace('Nacking message with unknown type URL ' + message.type_url); - this.nack(message.type_url, `Unknown type_url ${message.type_url}`); - } else { - updateCsdsResourceResponse(message.type_url as AdsTypeUrl, message.version_info, handleResponseResult.result); - if (handleResponseResult.result.rejected.length > 0) { - // rejected.length > 0 means that at least one message validation failed - const errorString = `${handleResponseResult.serviceKind.toUpperCase()} Error: ${handleResponseResult.result.rejected[0].error}`; - this.trace('Nacking message with type URL ' + message.type_url + ': ' + errorString); - this.nack(message.type_url, errorString); - } else { - // If we get here, all message validation succeeded - this.trace('Acking message with type URL ' + message.type_url); - const serviceKind = handleResponseResult.serviceKind; - this.adsState[serviceKind].nonce = message.nonce; - this.adsState[serviceKind].versionInfo = message.version_info; - this.ack(serviceKind); - } - } - } - - private maybeCreateClients() { - if (this.adsClient !== null && this.lrsClient !== null) { - return; - } - const channelArgs = { - // 5 minutes - 'grpc.keepalive_time_ms': 5 * 60 * 1000 - } - const credentialsConfigs = this.xdsServerConfig.channelCreds; - let channelCreds: ChannelCredentials | null = null; - for (const config of credentialsConfigs) { - if (config.type === 'google_default') { - channelCreds = createGoogleDefaultCredentials(); - break; - } else if (config.type === 'insecure') { - channelCreds = ChannelCredentials.createInsecure(); - break; - } - } - if (channelCreds === null) { - this.trace('Failed to initialize xDS Client. No valid credentials types found.'); - // Bubble this error up to any listeners - this.reportStreamError({ - code: status.INTERNAL, - details: 'Failed to initialize xDS Client. No valid credentials types found.', - metadata: new Metadata(), - }); - return; - } - const serverUri = this.xdsServerConfig.serverUri - this.trace('Starting xDS client connected to server URI ' + this.xdsServerConfig.serverUri); - const channel = new Channel(serverUri, channelCreds, channelArgs); - const protoDefinitions = loadAdsProtos(); - if (this.adsClient === null) { - this.adsClient = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( - serverUri, - channelCreds, - {channelOverride: channel} - ); - channel.watchConnectivityState(channel.getConnectivityState(false), Infinity, () => { - this.handleAdsConnectivityStateUpdate(); - }); - } - if (this.lrsClient === null) { - this.lrsClient = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( - serverUri, - channelCreds, - {channelOverride: channel} - ); - } - } - - private handleAdsCallStatus(streamStatus: StatusObject) { - this.trace( - 'ADS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details - ); - this.adsCall = null; - if (streamStatus.code !== status.OK && !this.receivedAdsResponseOnCurrentStream) { - this.reportStreamError(streamStatus); - } - /* If the backoff timer is no longer running, we do not need to wait any - * more to start the new call. */ - if (!this.adsBackoff.isRunning()) { - this.maybeStartAdsStream(); - } - } - - private hasOutstandingResourceRequests() { - return (this.adsState.eds.getResourceNames().length > 0 || - this.adsState.cds.getResourceNames().length > 0 || - this.adsState.rds.getResourceNames().length > 0 || - this.adsState.lds.getResourceNames().length); - } - - /** - * Start the ADS stream if the client exists and there is not already an - * existing stream, and there are resources to request. - */ - private maybeStartAdsStream() { - if (!this.hasOutstandingResourceRequests()) { - return; - } - if (this.adsCall !== null) { - return; - } - this.maybeCreateClients(); - this.receivedAdsResponseOnCurrentStream = false; - const metadata = new Metadata({waitForReady: true}); - this.adsCall = this.adsClient!.StreamAggregatedResources(metadata); - this.adsCall.on('data', (message: DiscoveryResponse__Output) => { - this.handleAdsResponse(message); - }); - this.adsCall.on('status', (status: StatusObject) => { - this.handleAdsCallStatus(status); - }); - this.adsCall.on('error', () => {}); - this.trace('Started ADS stream'); - // Backoff relative to when we start the request - this.adsBackoff.runOnce(); - - const allServiceKinds: AdsServiceKind[] = ['eds', 'cds', 'rds', 'lds']; - for (const service of allServiceKinds) { - const state = this.adsState[service]; - if (state.getResourceNames().length > 0) { - this.updateNames(service); - } - } - if (this.adsClient!.getChannel().getConnectivityState(false) === connectivityState.READY) { - this.reportAdsStreamStarted(); - } - } - - private maybeSendAdsMessage(typeUrl: string, resourceNames: string[], responseNonce: string, versionInfo: string, errorMessage?: string) { - this.adsCall?.write({ - node: this.adsNode!, - type_url: typeUrl, - resource_names: resourceNames, - response_nonce: responseNonce, - version_info: versionInfo, - error_detail: errorMessage ? { message: errorMessage } : undefined - }); - } - - private getTypeUrl(serviceKind: AdsServiceKind): AdsTypeUrl { - switch (serviceKind) { - case 'eds': - return EDS_TYPE_URL; - case 'cds': - return CDS_TYPE_URL; - case 'rds': - return RDS_TYPE_URL; - case 'lds': - return LDS_TYPE_URL; + statsEntry.stats, + ]; } } - /** - * Acknowledge an update. This should be called after the local nonce and - * version info are updated so that it sends the post-update values. - */ - private ack(serviceKind: AdsServiceKind) { - this.updateNames(serviceKind); - } - - /** - * Reject an update. This should be called without updating the local - * nonce and version info. - */ - private nack(typeUrl: string, message: string) { - let resourceNames: string[]; - let nonce: string; - let versionInfo: string; - let serviceKind: AdsServiceKind | null; - switch (typeUrl) { - case EDS_TYPE_URL: - serviceKind = 'eds'; - break; - case CDS_TYPE_URL: - serviceKind = 'cds'; - break; - case RDS_TYPE_URL: - serviceKind = 'rds'; - break; - case LDS_TYPE_URL: - serviceKind = 'lds'; - break; - default: - serviceKind = null; - break; - } - if (serviceKind) { - this.adsState[serviceKind].reportStreamError({ - code: status.UNAVAILABLE, - details: message + ' Node ID=' + this.adsNode!.id, - metadata: new Metadata() - }); - resourceNames = this.adsState[serviceKind].getResourceNames(); - nonce = this.adsState[serviceKind].nonce; - versionInfo = this.adsState[serviceKind].versionInfo; - } else { - resourceNames = []; - nonce = ''; - versionInfo = ''; + unref(clusterName: string, edsServiceName: string) { + for (const statsObj of this.statsMap) { + if ( + statsObj.clusterName === clusterName && + statsObj.edsServiceName === edsServiceName + ) { + statsObj.refCount -=1; + if (statsObj.refCount === 0) { + this.statsMap.delete(statsObj); + } + return; + } } - this.maybeSendAdsMessage(typeUrl, resourceNames, nonce, versionInfo, message); - } - - private shutdown() { - this.adsCall?.end(); - this.adsCall = null; - this.lrsCall?.end(); - this.lrsCall = null; - this.adsClient?.close(); - this.adsClient = null; - this.lrsClient?.close(); - this.lrsClient = null; } - private updateNames(serviceKind: AdsServiceKind) { - if (!this.hasOutstandingResourceRequests()) { - this.shutdown(); - return; - } - this.maybeStartAdsStream(); - this.maybeStartLrsStream(); - if (!this.adsCall) { - /* If the stream is not set up yet at this point, shortcut the rest - * becuase nothing will actually be sent. This would mainly happen if - * the bootstrap file has not been read yet. In that case, the output - * of getTypeUrl is garbage and everything after that is invalid. */ - return; - } - this.trace('Sending update for ' + serviceKind + ' with names ' + this.adsState[serviceKind].getResourceNames()); - const typeUrl = this.getTypeUrl(serviceKind); - updateCsdsRequestedNameList(typeUrl, this.adsState[serviceKind].getResourceNames()); - this.maybeSendAdsMessage(typeUrl, this.adsState[serviceKind].getResourceNames(), this.adsState[serviceKind].nonce, this.adsState[serviceKind].versionInfo); + get size() { + return this.statsMap.size; } +} - private reportStreamError(status: StatusObject) { - status = {...status, details: status.details + ' Node ID=' + this.adsNode!.id}; - this.adsState.eds.reportStreamError(status); - this.adsState.cds.reportStreamError(status); - this.adsState.rds.reportStreamError(status); - this.adsState.lds.reportStreamError(status); +class LrsCallState { + private statsTimer: NodeJS.Timer | null = null; + private sentInitialMessage = false; + constructor(private client: XdsSingleServerClient, private call: LrsCall, private node: Node) { + call.on('data', (message: LoadStatsResponse__Output) => { + this.handleResponseMessage(message); + }) + call.on('status', (status: StatusObject) => { + this.handleStreamStatus(status); + }); + call.on('error', () => {}); } - private reportAdsStreamStarted() { - this.adsState.eds.reportAdsStreamStart(); - this.adsState.cds.reportAdsStreamStart(); - this.adsState.rds.reportAdsStreamStart(); - this.adsState.lds.reportAdsStreamStart(); + private handleStreamStatus(status: StatusObject) { + this.client.trace( + 'ADS stream ended. code=' + status.code + ' details= ' + status.details + ); } - private handleLrsResponse(message: LoadStatsResponse__Output) { - this.trace('Received LRS response'); - /* Once we get any response from the server, we assume that the stream is - * in a good state, so we can reset the backoff timer. */ - this.lrsBackoff.reset(); + private handleResponseMessage(message: LoadStatsResponse__Output) { + this.client.trace('Received LRS response'); + this.client.onLrsStreamReceivedMessage(); if ( - !this.receivedLrsSettingsForCurrentStream || + !this.statsTimer || message.load_reporting_interval?.seconds !== - this.latestLrsSettings?.load_reporting_interval?.seconds || + this.client.latestLrsSettings?.load_reporting_interval?.seconds || message.load_reporting_interval?.nanos !== - this.latestLrsSettings?.load_reporting_interval?.nanos + this.client.latestLrsSettings?.load_reporting_interval?.nanos ) { /* Only reset the timer if the interval has changed or was not set * before. */ - clearInterval(this.statsTimer); + if (this.statsTimer) { + clearInterval(this.statsTimer); + } /* Convert a google.protobuf.Duration to a number of milliseconds for * use with setInterval. */ const loadReportingIntervalMs = Number.parseInt(message.load_reporting_interval!.seconds) * 1000 + message.load_reporting_interval!.nanos / 1_000_000; - this.trace('Received LRS response with load reporting interval ' + loadReportingIntervalMs + ' ms'); + this.client.trace('Received LRS response with load reporting interval ' + loadReportingIntervalMs + ' ms'); this.statsTimer = setInterval(() => { this.sendStats(); }, loadReportingIntervalMs); } - this.latestLrsSettings = message; - this.receivedLrsSettingsForCurrentStream = true; - } - - private handleLrsCallStatus(streamStatus: StatusObject) { - this.trace( - 'LRS stream ended. code=' + streamStatus.code + ' details= ' + streamStatus.details - ); - this.lrsCall = null; - clearInterval(this.statsTimer); - /* If the backoff timer is no longer running, we do not need to wait any - * more to start the new call. */ - if (!this.lrsBackoff.isRunning()) { - this.maybeStartLrsStream(); - } + this.client.latestLrsSettings = message; } - private maybeStartLrsStream() { - if (!this.hasOutstandingResourceRequests()) { - return; - } - if (this.lrsCall) { - return; - } - this.maybeCreateClients(); - this.lrsCall = this.lrsClient!.streamLoadStats(); - this.receivedLrsSettingsForCurrentStream = false; - this.lrsCall.on('data', (message: LoadStatsResponse__Output) => { - this.handleLrsResponse(message); - }); - this.lrsCall.on('status', (status: StatusObject) => { - this.handleLrsCallStatus(status); + private sendLrsMessage(clusterStats: ClusterStats[]) { + this.call.write({ + node: this.sentInitialMessage ? this.node : null, + cluster_stats: clusterStats }); - this.lrsCall.on('error', () => {}); - this.trace('Starting LRS stream'); - this.lrsBackoff.runOnce(); - /* Send buffered stats information when starting LRS stream. If there is no - * buffered stats information, it will still send the node field. */ - this.sendStats(); } - private maybeSendLrsMessage(clusterStats: ClusterStats[]) { - this.lrsCall?.write({ - node: this.lrsNode!, - cluster_stats: clusterStats - }); + private get latestLrsSettings() { + return this.client.latestLrsSettings; } private sendStats() { - if (this.lrsCall === null) { - return; - } if (!this.latestLrsSettings) { - this.maybeSendLrsMessage([]); + this.sendLrsMessage([]); return; } const clusterStats: ClusterStats[] = []; for (const [ { clusterName, edsServiceName }, stats, - ] of this.clusterStatsMap.entries()) { + ] of this.client.clusterStatsMap.entries()) { if ( this.latestLrsSettings.send_all_clusters || this.latestLrsSettings.clusters.indexOf(clusterName) > 0 @@ -788,69 +789,190 @@ class XdsSingleServerClient { } } } - this.trace('Sending LRS stats ' + JSON.stringify(clusterStats, undefined, 2)); - this.maybeSendLrsMessage(clusterStats); + this.client.trace('Sending LRS stats ' + JSON.stringify(clusterStats, undefined, 2)); + this.sendLrsMessage(clusterStats); + } +} - addEndpointWatcher( - edsServiceName: string, - watcher: Watcher - ) { - this.trace('Watcher added for endpoint ' + edsServiceName); - this.adsState.eds.addWatcher(edsServiceName, watcher); +class XdsSingleServerClient { + public ignoreResourceDeletion: boolean; + + private adsBackoff: BackoffTimeout; + private lrsBackoff: BackoffTimeout; + + private adsClient: AggregatedDiscoveryServiceClient; + private adsCallState: AdsCallState | null = null; + + private lrsClient: LoadReportingServiceClient; + private lrsCallState: LrsCallState | null = null; + public clusterStatsMap = new ClusterLoadReportMap(); + public latestLrsSettings: LoadStatsResponse__Output | null = null; + + /** + * The number of authorities that are using this client. Streams should only + * be started if refcount > 0 + */ + private refcount = 0; + + /** + * Map of type to latest accepted version string for that type + */ + public resourceTypeVersionMap: Map = new Map(); + constructor(public xdsClient: XdsClient, bootstrapNode: Node, public xdsServerConfig: XdsServerConfig) { + this.adsBackoff = new BackoffTimeout(() => { + this.maybeStartAdsStream(); + }); + this.adsBackoff.unref(); + this.lrsBackoff = new BackoffTimeout(() => { + this.maybeStartLrsStream(); + }); + this.lrsBackoff.unref(); + this.ignoreResourceDeletion = xdsServerConfig.serverFeatures.includes('ignore_resource_deletion'); + const channelArgs = { + // 5 minutes + 'grpc.keepalive_time_ms': 5 * 60 * 1000 + } + const credentialsConfigs = xdsServerConfig.channelCreds; + let channelCreds: ChannelCredentials | null = null; + for (const config of credentialsConfigs) { + if (config.type === 'google_default') { + channelCreds = createGoogleDefaultCredentials(); + break; + } else if (config.type === 'insecure') { + channelCreds = ChannelCredentials.createInsecure(); + break; + } + } + const serverUri = this.xdsServerConfig.serverUri + this.trace('Starting xDS client connected to server URI ' + this.xdsServerConfig.serverUri); + /* Bootstrap validation rules guarantee that a matching channel credentials + * config exists in the list. */ + const channel = new Channel(serverUri, channelCreds!, channelArgs); + const protoDefinitions = loadAdsProtos(); + this.adsClient = new protoDefinitions.envoy.service.discovery.v3.AggregatedDiscoveryService( + serverUri, + channelCreds!, + {channelOverride: channel} + ); + channel.watchConnectivityState(channel.getConnectivityState(false), Infinity, () => { + this.handleAdsConnectivityStateUpdate(); + }); + this.lrsClient = new protoDefinitions.envoy.service.load_stats.v3.LoadReportingService( + serverUri, + channelCreds!, + {channelOverride: channel} + ); } - removeEndpointWatcher( - edsServiceName: string, - watcher: Watcher - ) { - this.trace('Watcher removed for endpoint ' + edsServiceName); - this.adsState.eds.removeWatcher(edsServiceName, watcher); + private handleAdsConnectivityStateUpdate() { + const state = this.adsClient.getChannel().getConnectivityState(false); + if (state === connectivityState.READY) { + this.adsCallState?.markStreamStarted(); + } + if (state === connectivityState.TRANSIENT_FAILURE) { + for (const authorityState of this.xdsClient.authorityStateMap.values()) { + if (authorityState.client !== this) { + continue; + } + for (const typeMap of authorityState.resourceMap.values()) { + for (const resourceState of typeMap.values()) { + for (const watcher of resourceState.watchers) { + watcher.onError({ + code: status.UNAVAILABLE, + details: 'No connection established to xDS server', + metadata: new Metadata() + }); + } + } + } + } + } + this.adsClient.getChannel().watchConnectivityState(state, Infinity, () => { + this.handleAdsConnectivityStateUpdate(); + }); + } + + onAdsStreamReceivedMessage() { + this.adsBackoff.stop(); + this.adsBackoff.reset(); + } + + handleAdsStreamEnd() { + /* The backoff timer would start the stream when it finishes. If it is not + * running, restart the stream immediately. */ + if (!this.adsBackoff.isRunning()) { + this.maybeStartAdsStream(); + } + } + + private maybeStartAdsStream() { + if (this.adsCallState || this.refcount < 1) { + return; + } + const metadata = new Metadata({waitForReady: true}); + const call = this.adsClient.StreamAggregatedResources(metadata); + this.adsCallState = new AdsCallState(this, call, this.xdsClient.adsNode!); + this.adsBackoff.runOnce(); } - addClusterWatcher(clusterName: string, watcher: Watcher) { - this.trace('Watcher added for cluster ' + clusterName); - this.adsState.cds.addWatcher(clusterName, watcher); + onLrsStreamReceivedMessage() { + this.adsBackoff.stop(); + this.adsBackoff.reset(); + } + + handleLrsStreamEnd() { + /* The backoff timer would start the stream when it finishes. If it is not + * running, restart the stream immediately. */ + if (!this.lrsBackoff.isRunning()) { + this.maybeStartLrsStream(); + } } - removeClusterWatcher(clusterName: string, watcher: Watcher) { - this.trace('Watcher removed for cluster ' + clusterName); - this.adsState.cds.removeWatcher(clusterName, watcher); + private maybeStartLrsStream() { + if (this.lrsCallState || this.refcount < 1 || this.clusterStatsMap.size < 1) { + return; + } + const metadata = new Metadata({waitForReady: true}); + const call = this.lrsClient.StreamLoadStats(metadata); + this.lrsCallState = new LrsCallState(this, call, this.xdsClient.lrsNode!); + this.lrsBackoff.runOnce(); } - addRouteWatcher(routeConfigName: string, watcher: Watcher) { - this.trace('Watcher added for route ' + routeConfigName); - this.adsState.rds.addWatcher(routeConfigName, watcher); + trace(text: string) { + trace(this.xdsServerConfig.serverUri + ' ' + text); } - removeRouteWatcher(routeConfigName: string, watcher: Watcher) { - this.trace('Watcher removed for route ' + routeConfigName); - this.adsState.rds.removeWatcher(routeConfigName, watcher); + subscribe(type: XdsResourceType, name: XdsResourceName) { + this.trace('subscribe(type=' + type.getTypeUrl() + ', name=' + xdsResourceNameToString(name, type.getTypeUrl()) + ')'); + this.trace(JSON.stringify(name)); + this.maybeStartAdsStream(); + this.adsCallState?.subscribe(type, name); } - addListenerWatcher(targetName: string, watcher: Watcher) { - this.trace('Watcher added for listener ' + targetName); - this.adsState.lds.addWatcher(targetName, watcher); + unsubscribe(type: XdsResourceType, name: XdsResourceName) { + this.trace('unsubscribe(type=' + type.getTypeUrl() + ', name=' + xdsResourceNameToString(name, type.getTypeUrl()) + ')'); + this.adsCallState?.unsubscribe(type, name); + if (this.adsCallState && !this.adsCallState.hasSubscribedResources()) { + this.adsCallState.end(); + this.adsCallState = null; + } } - removeListenerWatcher(targetName: string, watcher: Watcher) { - this.trace('Watcher removed for listener ' + targetName); - this.adsState.lds.removeWatcher(targetName, watcher); + ref() { + this.refcount += 1; + } + + unref() { + this.refcount -= 1; } - /** - * - * @param lrsServer The target name of the server to send stats to. An empty - * string indicates that the default LRS client should be used. Currently - * only the empty string is supported here. - * @param clusterName - * @param edsServiceName - */ addClusterDropStats( clusterName: string, edsServiceName: string ): XdsClusterDropStats { this.trace('addClusterDropStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ')'); + this.maybeStartLrsStream(); const clusterStats = this.clusterStatsMap.getOrCreate( clusterName, edsServiceName @@ -866,12 +988,18 @@ class XdsSingleServerClient { }; } + removeClusterDropStats(clusterName: string, edsServiceName: string) { + this.trace('removeClusterDropStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ')'); + this.clusterStatsMap.unref(clusterName, edsServiceName); + } + addClusterLocalityStats( clusterName: string, edsServiceName: string, locality: Locality__Output ): XdsClusterLocalityStats { this.trace('addClusterLocalityStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ', locality=' + JSON.stringify(locality) + ')'); + this.maybeStartLrsStream(); const clusterStats = this.clusterStatsMap.getOrCreate( clusterName, edsServiceName @@ -890,8 +1018,9 @@ class XdsSingleServerClient { callsStarted: 0, callsSucceeded: 0, callsFailed: 0, + refcount: 0, }; - clusterStats.localityStats.push(localityStats); + clusterStats.localityStats.add(localityStats); } /* Help the compiler understand that this object is always non-null in the * closure */ @@ -911,168 +1040,219 @@ class XdsSingleServerClient { }, }; } -} -/* Structure: - * serverConfig - * single server client - * response validation (for ACK/NACK) - * response parsing (server config in CDS update for LRS) - * authority - * EdsStreamState - * watchers - * cache - * CdsStreamState - * ... - * RdsStreamState - * ... - * LdsStreamState - * ... - * server reference - * update CSDS - */ + removeClusterLocalityStats( + clusterName: string, + edsServiceName: string, + locality: Locality__Output + ) { + this.trace('removeClusterLocalityStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ', locality=' + JSON.stringify(locality) + ')'); + const clusterStats = this.clusterStatsMap.get(clusterName, edsServiceName); + if (!clusterStats) { + return; + } + for (const statsObj of clusterStats.localityStats) { + if (localityEqual(locality, statsObj.locality)) { + statsObj.refcount -= 1; + if (statsObj.refcount === 0) { + clusterStats.localityStats.delete(statsObj); + } + break; + } + } + this.clusterStatsMap.unref(clusterName, edsServiceName); + } +} interface ClientMapEntry { serverConfig: XdsServerConfig; client: XdsSingleServerClient; } -export class XdsClient { - private clientMap: ClientMapEntry[] = []; +type ClientResourceStatus = 'REQUESTED' | 'DOES_NOT_EXIST' | 'ACKED' | 'NACKED'; + +interface ResourceMetadata { + clientStatus: ClientResourceStatus; + rawResource?: Any__Output; + updateTime?: Date; + version?: string; + failedVersion?: string; + failedDetails?: string; + failedUpdateTime?: Date; +} + +interface ResourceState { + watchers: Set; + cachedResource: object | null; + meta: ResourceMetadata; + deletionIgnored: boolean; +} + +interface AuthorityState { + client: XdsSingleServerClient; + /** + * type -> key -> state + */ + resourceMap: Map>; +} - constructor(private bootstrapInfoOverride?: BootstrapInfo) {} +const userAgentName = 'gRPC Node Pure JS'; + +export class XdsClient { + /** + * authority -> authority state + */ + public authorityStateMap: Map = new Map(); + private clients: ClientMapEntry[] = []; + private typeRegistry: Map = new Map(); + private bootstrapInfo: BootstrapInfo | null = null; + + constructor(bootstrapInfoOverride?: BootstrapInfo) { + if (bootstrapInfoOverride) { + this.bootstrapInfo = bootstrapInfoOverride; + } + registerXdsClientWithCsds(this); + } private getBootstrapInfo() { - if (this.bootstrapInfoOverride) { - return this.bootstrapInfoOverride; - } else { - return loadBootstrapInfo(); + if (!this.bootstrapInfo) { + this.bootstrapInfo = loadBootstrapInfo(); } + return this.bootstrapInfo; } - private getClient(serverConfig: XdsServerConfig): XdsSingleServerClient | null { - for (const entry of this.clientMap) { - if (serverConfigEqual(serverConfig, entry.serverConfig)) { - return entry.client; - } + get adsNode(): Node | undefined { + if (!this.bootstrapInfo) { + return undefined; + } + return { + ...this.bootstrapInfo.node, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lb.does_not_support_overprovisioning'], + } + } + + get lrsNode(): Node | undefined { + if (!this.bootstrapInfo) { + return undefined; } - return null; + return { + ...this.bootstrapInfo.node, + user_agent_name: userAgentName, + user_agent_version: clientVersion, + client_features: ['envoy.lrs.supports_send_all_clusters'], + }; } - private getOrCreateClientForResource(resourceName: string): XdsSingleServerClient { + private getOrCreateClient(authority: string): XdsSingleServerClient { const bootstrapInfo = this.getBootstrapInfo(); let serverConfig: XdsServerConfig; - if (EXPERIMENTAL_FEDERATION) { - if (resourceName.startsWith('xdstp:')) { - const match = resourceName.match(/xdstp:\/\/([^/]+)\//); - if (!match) { - throw new Error(`Parse error: Resource ${resourceName} has no authority`); - } - const authority = match[1]; - if (authority in bootstrapInfo.authorities) { - serverConfig = bootstrapInfo.authorities[authority].xdsServers?.[0] ?? bootstrapInfo.xdsServers[0]; - } else { - throw new Error(`Authority ${authority} in resource ${resourceName} not found in authorities list`); - } + if (authority === 'old:') { + serverConfig = bootstrapInfo.xdsServers[0]; + } else { + if (authority in bootstrapInfo.authorities) { + serverConfig = bootstrapInfo.authorities[authority].xdsServers?.[0] ?? bootstrapInfo.xdsServers[0]; } else { - serverConfig = bootstrapInfo.xdsServers[0]; + throw new Error(`Authority ${authority} not found in bootstrap authorities list`); } - } else { - serverConfig = bootstrapInfo.xdsServers[0]; } - for (const entry of this.clientMap) { + for (const entry of this.clients) { if (serverConfigEqual(serverConfig, entry.serverConfig)) { return entry.client; } } - const newClient = new XdsSingleServerClient(bootstrapInfo.node, serverConfig); - this.clientMap.push({serverConfig: serverConfig, client: newClient}); - return newClient; + const client = new XdsSingleServerClient(this, bootstrapInfo.node, serverConfig); + this.clients.push({client, serverConfig}); + return client; } - addEndpointWatcher( - edsServiceName: string, - watcher: Watcher - ) { - trace('addEndpointWatcher(' + edsServiceName + ')'); - try { - const client = this.getOrCreateClientForResource(edsServiceName); - client.addEndpointWatcher(edsServiceName, watcher); - } catch (e) { - trace('addEndpointWatcher error: ' + e.message); - watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + private getClient(server: XdsServerConfig) { + for (const entry of this.clients) { + if (serverConfigEqual(server, entry.serverConfig)) { + return entry.client; + } } + return undefined; } - removeEndpointWatcher( - edsServiceName: string, - watcher: Watcher - ) { - trace('removeEndpointWatcher(' + edsServiceName + ')'); - try { - const client = this.getOrCreateClientForResource(edsServiceName); - client.removeEndpointWatcher(edsServiceName, watcher); - } catch (e) { - } + getResourceType(typeUrl: string) { + return this.typeRegistry.get(typeUrl); } - addClusterWatcher(clusterName: string, watcher: Watcher) { - trace('addClusterWatcher(' + clusterName + ')'); - try { - const client = this.getOrCreateClientForResource(clusterName); - client.addClusterWatcher(clusterName, watcher); - } catch (e) { - trace('addClusterWatcher error: ' + e.message); - watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + watchResource(type: XdsResourceType, name: string, watcher: ResourceWatcherInterface) { + trace('watchResource(type=' + type.getTypeUrl() + ', name=' + name + ')'); + if (this.typeRegistry.has(type.getTypeUrl())) { + if (this.typeRegistry.get(type.getTypeUrl()) !== type) { + throw new Error(`Resource type does not match previously used type with the same type URL: ${type.getTypeUrl()}`); + } + } else { + this.typeRegistry.set(type.getTypeUrl(), type); + this.typeRegistry.set(type.getFullTypeUrl(), type); } - } - - removeClusterWatcher(clusterName: string, watcher: Watcher) { - trace('removeClusterWatcher(' + clusterName + ')'); - try { - const client = this.getOrCreateClientForResource(clusterName); - client.removeClusterWatcher(clusterName, watcher); - } catch (e) { + const resourceName = parseXdsResourceName(name, type.getTypeUrl()); + let authorityState = this.authorityStateMap.get(resourceName.authority); + if (!authorityState) { + authorityState = { + client: this.getOrCreateClient(resourceName.authority), + resourceMap: new Map() + }; + authorityState.client.ref(); + this.authorityStateMap.set(resourceName.authority, authorityState); } - } - - addRouteWatcher(routeConfigName: string, watcher: Watcher) { - trace('addRouteWatcher(' + routeConfigName + ')'); - try { - const client = this.getOrCreateClientForResource(routeConfigName); - client.addRouteWatcher(routeConfigName, watcher); - } catch (e) { - trace('addRouteWatcher error: ' + e.message); - watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + let keyMap = authorityState.resourceMap.get(type); + if (!keyMap) { + keyMap = new Map(); + authorityState.resourceMap.set(type, keyMap); } - } - - removeRouteWatcher(routeConfigName: string, watcher: Watcher) { - trace('removeRouteWatcher(' + routeConfigName + ')'); - try { - const client = this.getOrCreateClientForResource(routeConfigName); - client.removeRouteWatcher(routeConfigName, watcher); - } catch (e) { + let entry = keyMap.get(resourceName.key); + let isNewSubscription = false; + if (!entry) { + isNewSubscription = true; + entry = { + watchers: new Set(), + cachedResource: null, + deletionIgnored: false, + meta: { + clientStatus: 'REQUESTED' + } + }; + keyMap.set(resourceName.key, entry); } - } - - addListenerWatcher(targetName: string, watcher: Watcher) { - trace('addListenerWatcher(' + targetName + ')'); - try { - const client = this.getOrCreateClientForResource(targetName); - client.addListenerWatcher(targetName, watcher); - } catch (e) { - trace('addListenerWatcher error: ' + e.message); - watcher.onTransientError({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()}); + entry.watchers.add(watcher); + if (entry.cachedResource) { + process.nextTick(() => { + if (entry?.cachedResource) { + watcher.onGenericResourceChanged(entry.cachedResource); + } + }); + } + if (isNewSubscription) { + authorityState.client.subscribe(type, resourceName); } } - removeListenerWatcher(targetName: string, watcher: Watcher) { - trace('removeListenerWatcher' + targetName); - try { - const client = this.getOrCreateClientForResource(targetName); - client.removeListenerWatcher(targetName, watcher); - } catch (e) { + cancelResourceWatch(type: XdsResourceType, name: string, watcher: ResourceWatcherInterface) { + trace('cancelResourceWatch(type=' + type.getTypeUrl() + ', name=' + name + ')'); + const resourceName = parseXdsResourceName(name, type.getTypeUrl()); + const authorityState = this.authorityStateMap.get(resourceName.authority); + if (!authorityState) { + return; + } + const entry = authorityState.resourceMap.get(type)?.get(resourceName.key); + if (entry) { + entry.watchers.delete(watcher); + if (entry.watchers.size === 0) { + authorityState.resourceMap.get(type)!.delete(resourceName.key); + authorityState.client.unsubscribe(type, resourceName); + if (authorityState.resourceMap.get(type)!.size === 0) { + authorityState.resourceMap.delete(type); + if (authorityState.resourceMap.size === 0) { + authorityState.client.unref(); + this.authorityStateMap.delete(resourceName.authority); + } + } + } } } @@ -1087,6 +1267,10 @@ export class XdsClient { return client.addClusterDropStats(clusterName, edsServiceName); } + removeClusterDropStats(lrsServer: XdsServerConfig, clusterName: string, edsServiceName: string) { + this.getClient(lrsServer)?.removeClusterDropStats(clusterName, edsServiceName); + } + addClusterLocalityStats(lrsServer: XdsServerConfig, clusterName: string, edsServiceName: string, locality: Locality__Output): XdsClusterLocalityStats { const client = this.getClient(lrsServer); if (!client) { @@ -1097,6 +1281,10 @@ export class XdsClient { } return client.addClusterLocalityStats(clusterName, edsServiceName, locality); } + + removeClusterLocalityStats(lrsServer: XdsServerConfig, clusterName: string, edsServiceName: string, locality: Locality__Output) { + this.getClient(lrsServer)?.removeClusterLocalityStats(clusterName, edsServiceName, locality); + } } let singletonXdsClient: XdsClient | null = null; @@ -1106,4 +1294,4 @@ export function getSingletonXdsClient(): XdsClient { singletonXdsClient = new XdsClient(); } return singletonXdsClient; -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/xds-client2.ts b/packages/grpc-js-xds/src/xds-client2.ts deleted file mode 100644 index 4e6bc10a0..000000000 --- a/packages/grpc-js-xds/src/xds-client2.ts +++ /dev/null @@ -1,450 +0,0 @@ -/* - * 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. - * - */ - -import { ClientDuplexStream, StatusObject, experimental, loadPackageDefinition, logVerbosity } from "@grpc/grpc-js"; -import { XdsResourceType } from "./xds-resource-type/xds-resource-type"; -import { XdsResourceName, parseXdsResourceName, xdsResourceNameToString } from "./resources"; -import { Node } from "./generated/envoy/config/core/v3/Node"; -import { BootstrapInfo, XdsServerConfig, loadBootstrapInfo, serverConfigEqual } from "./xds-bootstrap"; -import BackoffTimeout = experimental.BackoffTimeout; -import { DiscoveryRequest } from "./generated/envoy/service/discovery/v3/DiscoveryRequest"; -import { DiscoveryResponse__Output } from "./generated/envoy/service/discovery/v3/DiscoveryResponse"; -import * as adsTypes from './generated/ads'; -import * as lrsTypes from './generated/lrs'; -import * as protoLoader from '@grpc/proto-loader'; -import { EXPERIMENTAL_FEDERATION } from "./environment"; - -const TRACER_NAME = 'xds_client'; - -function trace(text: string): void { - experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); -} - -let loadedProtos: adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType | null = null; - -function loadAdsProtos(): adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType { - if (loadedProtos !== null) { - return loadedProtos; - } - return (loadPackageDefinition(protoLoader - .loadSync( - [ - 'envoy/service/discovery/v3/ads.proto', - 'envoy/service/load_stats/v3/lrs.proto', - ], - { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - json: true, - includeDirs: [ - // Paths are relative to src/build - __dirname + '/../../deps/envoy-api/', - __dirname + '/../../deps/xds/', - __dirname + '/../../deps/googleapis/', - __dirname + '/../../deps/protoc-gen-validate/', - ], - } - )) as unknown) as adsTypes.ProtoGrpcType & lrsTypes.ProtoGrpcType; -} - -const clientVersion = require('../../package.json').version; - -export interface ResourceWatcherInterface { - onGenericResourceChanged(resource: object): void; - onError(status: StatusObject): void; - onResourceDoesNotExist(): void; -} - -export interface BasicWatcher { - onResourceChanged(resource: UpdateType): void; - onError(status: StatusObject): void; - onResourceDoesNotExist(): void; -} - -export class Watcher implements ResourceWatcherInterface { - constructor(private internalWatcher: BasicWatcher) {} - onGenericResourceChanged(resource: object): void { - this.internalWatcher.onResourceChanged(resource as UpdateType); - } - onError(status: StatusObject) { - this.internalWatcher.onError(status); - } - onResourceDoesNotExist() { - this.internalWatcher.onResourceDoesNotExist(); - } -} - -const RESOURCE_TIMEOUT_MS = 15_000; - -class ResourceTimer { - private timer: NodeJS.Timer | null = null; - private resourceSeen = false; - constructor(private callState: AdsCallState, private type: XdsResourceType, private name: XdsResourceName) {} - - maybeCancelTimer() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - } - - markSeen() { - this.resourceSeen = true; - this.maybeCancelTimer(); - } - - markSubscriptionSendStarted() { - this.maybeStartTimer(); - } - - private maybeStartTimer() { - if (this.resourceSeen) { - return; - } - if (this.timer) { - return; - } - const authorityState = this.callState.client.xdsClient.authorityStateMap.get(this.name.authority); - if (!authorityState) { - return; - } - const resourceState = authorityState.resourceMap.get(this.type)?.get(this.name.key); - if (resourceState?.cachedResource) { - return; - } - this.timer = setTimeout(() => { - this.onTimer(); - }, RESOURCE_TIMEOUT_MS); - } - - private onTimer() { - const authorityState = this.callState.client.xdsClient.authorityStateMap.get(this.name.authority); - const resourceState = authorityState?.resourceMap.get(this.type)?.get(this.name.key); - if (!resourceState) { - return; - } - resourceState.meta.clientStatus = 'DOES_NOT_EXIST'; - for (const watcher of resourceState.watchers) { - watcher.onResourceDoesNotExist(); - } - } -} - -type AdsCall = ClientDuplexStream; - -interface ResourceTypeState { - nonce?: string; - /** - * authority -> key -> timer - */ - subscribedResources: Map>; -} - -class AdsCallState { - private typeStates: Map = new Map(); - constructor(public client: XdsSingleServerClient, private call: AdsCall, private node: Node) { - // Populate subscription map with existing subscriptions - for (const [authority, authorityState] of client.xdsClient.authorityStateMap) { - if (authorityState.client !== client) { - continue; - } - for (const [type, typeMap] of authorityState.resourceMap) { - let typeState = this.typeStates.get(type); - if (!typeState) { - typeState = { - nonce: '', - subscribedResources: new Map() - }; - } - const authorityMap: Map = new Map(); - for (const key of typeMap.keys()) { - const timer = new ResourceTimer(this, type, {authority, key}); - authorityMap.set(key, timer); - } - typeState.subscribedResources.set(authority, authorityMap); - } - } - } - - hasSubscribedResources(): boolean { - for (const typeState of this.typeStates.values()) { - for (const authorityMap of typeState.subscribedResources.values()) { - if (authorityMap.size > 0) { - return true; - } - } - } - return false; - } - - subscribe(type: XdsResourceType, name: XdsResourceName) { - let typeState = this.typeStates.get(type); - if (!typeState) { - typeState = { - nonce: '', - subscribedResources: new Map() - }; - } - let authorityMap = typeState.subscribedResources.get(name.authority); - if (!authorityMap) { - authorityMap = new Map(); - typeState.subscribedResources.set(name.authority, authorityMap); - } - if (!authorityMap.has(name.key)) { - const timer = new ResourceTimer(this, type, name); - authorityMap.set(name.key, timer); - } - } - - unsubscribe(type: XdsResourceType, name: XdsResourceName) { - this.typeStates.get(type)?.subscribedResources.get(name.authority)?.delete(name.key); - } - - resourceNamesForRequest(type: XdsResourceType): string[] { - const typeState = this.typeStates.get(type); - if (!typeState) { - return []; - } - const result: string[] = []; - for (const [authority, authorityMap] of typeState.subscribedResources) { - for (const [key, timer] of authorityMap) { - timer.markSubscriptionSendStarted(); - result.push(xdsResourceNameToString({authority, key}, type.getTypeUrl())); - } - } - return result; - } - - updateNames(type: XdsResourceType) { - this.call.write({ - node: this.node, - type_url: type.getTypeUrl(), - response_nonce: this.typeStates.get(type)?.nonce, - resource_names: this.resourceNamesForRequest(type) - }); - } -} - -class XdsSingleServerClient { - private adsNode: Node; - private lrsNode: Node; - private ignoreResourceDeletion: boolean; - - private adsBackoff: BackoffTimeout; - private lrsBackoff: BackoffTimeout; - - private adsCallState: AdsCallState | null = null; - - /** - * The number of authorities that are using this client. Streams should only - * be started if refcount > 0 - */ - private refcount = 0; - - /** - * Map of type to latest accepted version string for that type - */ - public resourceTypeVersionMap: Map = new Map(); - constructor(public xdsClient: XdsClient, bootstrapNode: Node, private xdsServerConfig: XdsServerConfig) { - this.adsBackoff = new BackoffTimeout(() => { - this.maybeStartAdsStream(); - }); - this.adsBackoff.unref(); - this.lrsBackoff = new BackoffTimeout(() => { - this.maybeStartLrsStream(); - }); - this.lrsBackoff.unref(); - this.ignoreResourceDeletion = xdsServerConfig.serverFeatures.includes('ignore_resource_deletion'); - const userAgentName = 'gRPC Node Pure JS'; - this.adsNode = { - ...bootstrapNode, - user_agent_name: userAgentName, - user_agent_version: clientVersion, - client_features: ['envoy.lb.does_not_support_overprovisioning'], - }; - this.lrsNode = { - ...bootstrapNode, - user_agent_name: userAgentName, - user_agent_version: clientVersion, - client_features: ['envoy.lrs.supports_send_all_clusters'], - }; - } - - private trace(text: string) { - trace(this.xdsServerConfig.serverUri + ' ' + text); - } - - subscribe(type: XdsResourceType, name: XdsResourceName) { - this.adsCallState?.subscribe(type, name); - } - - unsubscribe(type: XdsResourceType, name: XdsResourceName) { - this.adsCallState?.unsubscribe(type, name); - } - - ref() { - this.refcount += 1; - } - - unref() { - this.refcount -= 1; - } -} - -interface ClientMapEntry { - serverConfig: XdsServerConfig; - client: XdsSingleServerClient; -} - -type ClientResourceStatus = 'REQUESTED' | 'DOES_NOT_EXIST' | 'ACKED' | 'NACKED'; - -interface ResourceMetadata { - clientStatus: ClientResourceStatus; - updateTime: Date | null; - version: string | null; - failedVersion: string | null; - failedDetails: string | null; - failedUpdateTime: string | null; -} - -interface ResourceState { - watchers: Set; - cachedResource: object | null; - meta: ResourceMetadata; - deletionIgnored: boolean; -} - -interface AuthorityState { - client: XdsSingleServerClient; - /** - * type -> key -> state - */ - resourceMap: Map>; -} - -export class XdsClient { - /** - * authority -> authority state - */ - public authorityStateMap: Map = new Map(); - private clients: ClientMapEntry[] = []; - - constructor(private bootstrapInfoOverride?: BootstrapInfo) {} - - private getBootstrapInfo() { - if (this.bootstrapInfoOverride) { - return this.bootstrapInfoOverride; - } else { - return loadBootstrapInfo(); - } - } - - private getOrCreateClient(authority: string): XdsSingleServerClient { - const bootstrapInfo = this.getBootstrapInfo(); - let serverConfig: XdsServerConfig; - if (authority === ':old') { - serverConfig = bootstrapInfo.xdsServers[0]; - } else { - if (authority in bootstrapInfo.authorities) { - serverConfig = bootstrapInfo.authorities[authority].xdsServers?.[0] ?? bootstrapInfo.xdsServers[0]; - } else { - throw new Error(`Authority ${authority} not found in bootstrap authorities list`); - } - } - for (const entry of this.clients) { - if (serverConfigEqual(serverConfig, entry.serverConfig)) { - return entry.client; - } - } - const client = new XdsSingleServerClient(this, bootstrapInfo.node, serverConfig); - this.clients.push({client, serverConfig}); - return client; - } - - watchResource(type: XdsResourceType, name: string, watcher: ResourceWatcherInterface) { - const resourceName = parseXdsResourceName(name, type.getTypeUrl()); - let authorityState = this.authorityStateMap.get(resourceName.authority); - if (!authorityState) { - authorityState = { - client: this.getOrCreateClient(resourceName.authority), - resourceMap: new Map() - }; - authorityState.client.ref(); - } - let keyMap = authorityState.resourceMap.get(type); - if (!keyMap) { - keyMap = new Map(); - authorityState.resourceMap.set(type, keyMap); - } - let entry = keyMap.get(resourceName.key); - let isNewSubscription = false; - if (!entry) { - isNewSubscription = true; - entry = { - watchers: new Set(), - cachedResource: null, - deletionIgnored: false, - meta: { - clientStatus: 'REQUESTED', - updateTime: null, - version: null, - failedVersion: null, - failedUpdateTime: null, - failedDetails: null - } - }; - keyMap.set(resourceName.key, entry); - } - entry.watchers.add(watcher); - if (entry.cachedResource) { - process.nextTick(() => { - if (entry?.cachedResource) { - watcher.onGenericResourceChanged(entry.cachedResource); - } - }); - } - if (isNewSubscription) { - authorityState.client.subscribe(type, resourceName); - } - } - - cancelResourceWatch(type: XdsResourceType, name: string, watcher: ResourceWatcherInterface) { - const resourceName = parseXdsResourceName(name, type.getTypeUrl()); - const authorityState = this.authorityStateMap.get(resourceName.authority); - if (!authorityState) { - return; - } - const entry = authorityState.resourceMap.get(type)?.get(resourceName.key); - if (entry) { - entry.watchers.delete(watcher); - if (entry.watchers.size === 0) { - authorityState.resourceMap.get(type)!.delete(resourceName.key); - authorityState.client.unsubscribe(type, resourceName); - if (authorityState.resourceMap.get(type)!.size === 0) { - authorityState.resourceMap.delete(type); - if (authorityState.resourceMap.size === 0) { - authorityState.client.unref(); - this.authorityStateMap.delete(resourceName.authority); - } - } - } - } - } -} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index 5a0187261..04afae11f 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -16,17 +16,19 @@ */ import { CDS_TYPE_URL, CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from "../resources"; -import { XdsResourceType } from "./xds-resource-type"; +import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; import { experimental } from "@grpc/grpc-js"; import { XdsServerConfig } from "../xds-bootstrap"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { OutlierDetection__Output } from "../generated/envoy/config/cluster/v3/OutlierDetection"; import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; +import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; +import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; +import { Any__Output } from "../generated/google/protobuf/Any"; import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; -import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; -import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; +import { Watcher, XdsClient } from "../xds-client"; export interface OutlierDetectionUpdate { intervalMs: number | null; @@ -112,7 +114,7 @@ export class ClusterResourceType extends XdsResourceType { } getTypeUrl(): string { - return CDS_TYPE_URL; + return 'envoy.config.cluster.v3.Cluster'; } private validateNonnegativeDuration(duration: Duration__Output | null): boolean { @@ -135,7 +137,7 @@ export class ClusterResourceType extends XdsResourceType { return percentage.value >=0 && percentage.value <= 100; } - private validateResource(message: Cluster__Output): CdsUpdate | null { + private validateResource(context: XdsDecodeContext, message: Cluster__Output): CdsUpdate | null { if (message.lb_policy !== 'ROUND_ROBIN') { return null; } @@ -191,7 +193,10 @@ export class ClusterResourceType extends XdsResourceType { } } if (message.type === 'EDS') { - if (!message.eds_cluster_config?.eds_config?.ads) { + if (!message.eds_cluster_config?.eds_config?.ads && !message.eds_cluster_config?.eds_config?.self) { + return null; + } + if (message.name.startsWith('xdstp:') && message.eds_cluster_config.service_name === '') { return null; } return { @@ -200,7 +205,7 @@ export class ClusterResourceType extends XdsResourceType { aggregateChildren: [], maxConcurrentRequests: maxConcurrentRequests, edsServiceName: message.eds_cluster_config.service_name === '' ? undefined : message.eds_cluster_config.service_name, - lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, + lrsLoadReportingServer: message.lrs_server ? context.server : undefined, outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) } } else if (message.type === 'LOGICAL_DNS') { @@ -229,11 +234,44 @@ export class ClusterResourceType extends XdsResourceType { aggregateChildren: [], maxConcurrentRequests: maxConcurrentRequests, dnsHostname: `${socketAddress.address}:${socketAddress.port_value}`, - lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, + lrsLoadReportingServer: message.lrs_server ? context.server : undefined, outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) }; } } return null; } + + decode(context:XdsDecodeContext, resource: Any__Output): XdsDecodeResult { + if (resource.type_url !== CDS_TYPE_URL) { + throw new Error( + `ADS Error: Invalid resource type ${resource.type_url}, expected ${CDS_TYPE_URL}` + ); + } + const message = decodeSingleResource(CDS_TYPE_URL, resource.value); + const validatedMessage = this.validateResource(context, message); + if (validatedMessage) { + return { + name: validatedMessage.name, + value: validatedMessage + }; + } else { + return { + name: message.name, + error: 'Cluster message validation failed' + }; + } + } + + allResourcesRequiredInSotW(): boolean { + return true; + } + + static startWatch(client: XdsClient, name: string, watcher: Watcher) { + client.watchResource(ClusterResourceType.get(), name, watcher); + } + + static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { + client.cancelResourceWatch(ClusterResourceType.get(), name, watcher); + } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts index 7067f14ab..9df9c5440 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts @@ -1,12 +1,12 @@ import { experimental, logVerbosity } from "@grpc/grpc-js"; import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; -import { XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; +import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; import { Locality__Output } from "../generated/envoy/config/core/v3/Locality"; import { SocketAddress__Output } from "../generated/envoy/config/core/v3/SocketAddress"; import { isIPv4, isIPv6 } from "net"; import { Any__Output } from "../generated/google/protobuf/Any"; import { EDS_TYPE_URL, decodeSingleResource } from "../resources"; -import { Watcher, XdsClient } from "../xds-client2"; +import { Watcher, XdsClient } from "../xds-client"; const TRACER_NAME = 'xds_client'; @@ -36,7 +36,7 @@ export class EndpointResourceType extends XdsResourceType { } getTypeUrl(): string { - return EDS_TYPE_URL; + return 'envoy.config.endpoint.v3.ClusterLoadAssignment'; } private validateResource(message: ClusterLoadAssignment__Output): ClusterLoadAssignment__Output | null { @@ -94,7 +94,7 @@ export class EndpointResourceType extends XdsResourceType { return message; } - decode(resource: Any__Output): XdsDecodeResult { + decode(context: XdsDecodeContext, resource: Any__Output): XdsDecodeResult { if (resource.type_url !== EDS_TYPE_URL) { throw new Error( `ADS Error: Invalid resource type ${resource.type_url}, expected ${EDS_TYPE_URL}` @@ -110,7 +110,7 @@ export class EndpointResourceType extends XdsResourceType { } else { return { name: message.cluster_name, - error: 'Listener message validation failed' + error: 'Endpoint message validation failed' }; } } diff --git a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts index f6bf8002a..09ad3ff09 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts @@ -20,10 +20,10 @@ import { EXPERIMENTAL_FAULT_INJECTION } from "../environment"; import { Listener__Output } from "../generated/envoy/config/listener/v3/Listener"; import { Any__Output } from "../generated/google/protobuf/Any"; import { HTTP_CONNECTION_MANGER_TYPE_URL, LDS_TYPE_URL, decodeSingleResource } from "../resources"; -import { XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; +import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; import { getTopLevelFilterUrl, validateTopLevelFilter } from "../http-filter"; import { RouteConfigurationResourceType } from "./route-config-resource-type"; -import { Watcher, XdsClient } from "../xds-client2"; +import { Watcher, XdsClient } from "../xds-client"; const TRACER_NAME = 'xds_client'; @@ -43,7 +43,7 @@ export class ListenerResourceType extends XdsResourceType { return ListenerResourceType.singleton; } getTypeUrl(): string { - return LDS_TYPE_URL; + return 'envoy.config.listener.v3.Listener'; } private validateResource(message: Listener__Output): Listener__Output | null { @@ -86,7 +86,7 @@ export class ListenerResourceType extends XdsResourceType { } switch (httpConnectionManager.route_specifier) { case 'rds': - if (!httpConnectionManager.rds?.config_source?.ads) { + if (!httpConnectionManager.rds?.config_source?.ads && !httpConnectionManager.rds?.config_source?.self) { return null; } return message; @@ -99,7 +99,7 @@ export class ListenerResourceType extends XdsResourceType { return null; } - decode(resource: Any__Output): XdsDecodeResult { + decode(context: XdsDecodeContext, resource: Any__Output): XdsDecodeResult { if (resource.type_url !== LDS_TYPE_URL) { throw new Error( `ADS Error: Invalid resource type ${resource.type_url}, expected ${LDS_TYPE_URL}` diff --git a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts index b4fc274be..278208c47 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts @@ -22,8 +22,8 @@ import { Any__Output } from "../generated/google/protobuf/Any"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { validateOverrideFilter } from "../http-filter"; import { RDS_TYPE_URL, decodeSingleResource } from "../resources"; -import { Watcher, XdsClient } from "../xds-client2"; -import { XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; +import { Watcher, XdsClient } from "../xds-client"; +import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; const SUPPORTED_PATH_SPECIFIERS = ['prefix', 'path', 'safe_regex']; const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ @@ -32,7 +32,8 @@ const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ 'range_match', 'present_match', 'prefix_match', - 'suffix_match']; + 'suffix_match', + 'string_match']; const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; const UINT32_MAX = 0xFFFFFFFF; @@ -56,7 +57,7 @@ export class RouteConfigurationResourceType extends XdsResourceType { } getTypeUrl(): string { - return RDS_TYPE_URL; + return 'envoy.config.route.v3.RouteConfiguration'; } private validateRetryPolicy(policy: RetryPolicy__Output | null): boolean { @@ -161,7 +162,7 @@ export class RouteConfigurationResourceType extends XdsResourceType { return message; } - decode(resource: Any__Output): XdsDecodeResult { + decode(context: XdsDecodeContext, resource: Any__Output): XdsDecodeResult { if (resource.type_url !== RDS_TYPE_URL) { throw new Error( `ADS Error: Invalid resource type ${resource.type_url}, expected ${RDS_TYPE_URL}` @@ -177,7 +178,7 @@ export class RouteConfigurationResourceType extends XdsResourceType { } else { return { name: message.name, - error: 'Listener message validation failed' + error: 'Route configuration message validation failed' }; } } diff --git a/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts index b8daf5401..114b164df 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts @@ -16,17 +16,76 @@ */ import { Any__Output } from "../generated/google/protobuf/Any"; +import { XdsServerConfig } from "../xds-bootstrap"; + +export interface XdsDecodeContext { + server: XdsServerConfig; +} export interface XdsDecodeResult { name: string; + /** + * Mutually exclusive with error. + */ value?: object; + /** + * Mutually exclusive with value. + */ error?: string; } +type ValueType = string | number | bigint | boolean | undefined | null | symbol | {[key: string]: ValueType} | ValueType[]; + +function deepEqual(value1: ValueType, value2: ValueType): boolean { + if (value1 === value2) { + return true; + } + // Extra null check to narrow type result of typeof value === 'object' + if (value1 === null || value2 === null) { + // They are not equal per previous check + return false; + } + if (Array.isArray(value1) && Array.isArray(value2)) { + if (value1.length !== value2.length) { + return false; + } + for (const [index, entry] of value1.entries()) { + if (!deepEqual(entry, value2[index])) { + return false; + } + } + return true; + } else if (Array.isArray(value1) || Array.isArray(value2)) { + return false; + } else if (typeof value1 === 'object' && typeof value2 === 'object') { + for (const [key, entry] of Object.entries(value1)) { + if (!deepEqual(entry, value2[key])) { + return false; + } + } + return true; + } + return false; +} + export abstract class XdsResourceType { + /** + * The type URL as used in xdstp: names + */ abstract getTypeUrl(): string; - abstract decode(resource: Any__Output): XdsDecodeResult; + /** + * The type URL as used in the `DiscoveryResponse.type_url` field and the `Any.type_url` field + */ + getFullTypeUrl(): string { + return `type.googleapis.com/${this.getTypeUrl()}`; + } + + abstract decode(context: XdsDecodeContext, resource: Any__Output): XdsDecodeResult; abstract allResourcesRequiredInSotW(): boolean; + + resourcesEqual(value1: object | null, value2: object | null): boolean { + return deepEqual(value1 as ValueType, value2 as ValueType); + } } \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts deleted file mode 100644 index 3e45a8fae..000000000 --- a/packages/grpc-js-xds/src/xds-stream-state/cds-state.ts +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright 2021 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 { FailurePercentageEjectionConfig, SuccessRateEjectionConfig } from "@grpc/grpc-js/build/src/load-balancer-outlier-detection"; -import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; -import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; -import { OutlierDetection__Output } from "../generated/envoy/config/cluster/v3/OutlierDetection"; -import { Duration__Output } from "../generated/google/protobuf/Duration"; -import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; -import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from "../resources"; -import { XdsServerConfig } from "../xds-bootstrap"; -import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; - -export interface OutlierDetectionUpdate { - intervalMs: number | null; - baseEjectionTimeMs: number | null; - maxEjectionTimeMs: number | null; - maxEjectionPercent: number | null; - successRateConfig: Partial | null; - failurePercentageConfig: Partial | null; -} - -export interface CdsUpdate { - type: 'AGGREGATE' | 'EDS' | 'LOGICAL_DNS'; - name: string; - aggregateChildren: string[]; - lrsLoadReportingServer?: XdsServerConfig; - maxConcurrentRequests?: number; - edsServiceName?: string; - dnsHostname?: string; - outlierDetectionUpdate?: OutlierDetectionUpdate; -} - -function durationToMs(duration: Duration__Output): number { - return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; -} - -function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Output | null): OutlierDetectionUpdate | undefined { - if (!EXPERIMENTAL_OUTLIER_DETECTION) { - return undefined; - } - if (!outlierDetection) { - /* No-op outlier detection config, with all fields unset. */ - return { - intervalMs: null, - baseEjectionTimeMs: null, - maxEjectionTimeMs: null, - maxEjectionPercent: null, - successRateConfig: null, - failurePercentageConfig: null - }; - } - let successRateConfig: Partial | null = null; - /* Success rate ejection is enabled by default, so we only disable it if - * enforcing_success_rate is set and it has the value 0 */ - if (!outlierDetection.enforcing_success_rate || outlierDetection.enforcing_success_rate.value > 0) { - successRateConfig = { - enforcement_percentage: outlierDetection.enforcing_success_rate?.value, - minimum_hosts: outlierDetection.success_rate_minimum_hosts?.value, - request_volume: outlierDetection.success_rate_request_volume?.value, - stdev_factor: outlierDetection.success_rate_stdev_factor?.value - }; - } - let failurePercentageConfig: Partial | null = null; - /* Failure percentage ejection is disabled by default, so we only enable it - * if enforcing_failure_percentage is set and it has a value greater than 0 */ - if (outlierDetection.enforcing_failure_percentage && outlierDetection.enforcing_failure_percentage.value > 0) { - failurePercentageConfig = { - enforcement_percentage: outlierDetection.enforcing_failure_percentage.value, - minimum_hosts: outlierDetection.failure_percentage_minimum_hosts?.value, - request_volume: outlierDetection.failure_percentage_request_volume?.value, - threshold: outlierDetection.failure_percentage_threshold?.value - } - } - return { - intervalMs: outlierDetection.interval ? durationToMs(outlierDetection.interval) : null, - baseEjectionTimeMs: outlierDetection.base_ejection_time ? durationToMs(outlierDetection.base_ejection_time) : null, - maxEjectionTimeMs: outlierDetection.max_ejection_time ? durationToMs(outlierDetection.max_ejection_time) : null, - maxEjectionPercent : outlierDetection.max_ejection_percent?.value ?? null, - successRateConfig: successRateConfig, - failurePercentageConfig: failurePercentageConfig - }; -} - -export class CdsState extends BaseXdsStreamState implements XdsStreamState { - protected isStateOfTheWorld(): boolean { - return true; - } - protected getResourceName(resource: Cluster__Output): string { - return resource.name; - } - protected getProtocolName(): string { - return 'CDS'; - } - - private validateNonnegativeDuration(duration: Duration__Output | null): boolean { - if (!duration) { - return true; - } - /* The maximum values here come from the official Protobuf documentation: - * https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration - */ - return Number(duration.seconds) >= 0 && - Number(duration.seconds) <= 315_576_000_000 && - duration.nanos >= 0 && - duration.nanos <= 999_999_999; - } - - private validatePercentage(percentage: UInt32Value__Output | null): boolean { - if (!percentage) { - return true; - } - return percentage.value >=0 && percentage.value <= 100; - } - - public validateResponse(message: Cluster__Output): CdsUpdate | null { - if (message.lb_policy !== 'ROUND_ROBIN') { - return null; - } - if (message.lrs_server) { - if (!message.lrs_server.self) { - return null; - } - } - if (EXPERIMENTAL_OUTLIER_DETECTION) { - if (message.outlier_detection) { - if (!this.validateNonnegativeDuration(message.outlier_detection.interval)) { - return null; - } - if (!this.validateNonnegativeDuration(message.outlier_detection.base_ejection_time)) { - return null; - } - if (!this.validateNonnegativeDuration(message.outlier_detection.max_ejection_time)) { - return null; - } - if (!this.validatePercentage(message.outlier_detection.max_ejection_percent)) { - return null; - } - if (!this.validatePercentage(message.outlier_detection.enforcing_success_rate)) { - return null; - } - if (!this.validatePercentage(message.outlier_detection.failure_percentage_threshold)) { - return null; - } - if (!this.validatePercentage(message.outlier_detection.enforcing_failure_percentage)) { - return null; - } - } - } - if (message.cluster_discovery_type === 'cluster_type') { - if (!(message.cluster_type?.typed_config && message.cluster_type.typed_config.type_url === CLUSTER_CONFIG_TYPE_URL)) { - return null; - } - const clusterConfig = decodeSingleResource(CLUSTER_CONFIG_TYPE_URL, message.cluster_type.typed_config.value); - if (clusterConfig.clusters.length === 0) { - return null; - } - return { - type: 'AGGREGATE', - name: message.name, - aggregateChildren: clusterConfig.clusters, - outlierDetectionUpdate: convertOutlierDetectionUpdate(null) - }; - } else { - let maxConcurrentRequests: number | undefined = undefined; - for (const threshold of message.circuit_breakers?.thresholds ?? []) { - if (threshold.priority === 'DEFAULT') { - maxConcurrentRequests = threshold.max_requests?.value; - } - } - if (message.type === 'EDS') { - if (!message.eds_cluster_config?.eds_config?.ads) { - return null; - } - return { - type: 'EDS', - name: message.name, - aggregateChildren: [], - maxConcurrentRequests: maxConcurrentRequests, - edsServiceName: message.eds_cluster_config.service_name === '' ? undefined : message.eds_cluster_config.service_name, - lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, - outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) - } - } else if (message.type === 'LOGICAL_DNS') { - if (!message.load_assignment) { - return null; - } - if (message.load_assignment.endpoints.length !== 1) { - return null; - } - if (message.load_assignment.endpoints[0].lb_endpoints.length !== 1) { - return null; - } - const socketAddress = message.load_assignment.endpoints[0].lb_endpoints[0].endpoint?.address?.socket_address; - if (!socketAddress) { - return null; - } - if (socketAddress.address === '') { - return null; - } - if (socketAddress.port_specifier !== 'port_value') { - return null; - } - return { - type: 'LOGICAL_DNS', - name: message.name, - aggregateChildren: [], - maxConcurrentRequests: maxConcurrentRequests, - dnsHostname: `${socketAddress.address}:${socketAddress.port_value}`, - lrsLoadReportingServer: message.lrs_server ? this.xdsServer : undefined, - outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) - }; - } - } - return null; - } -} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts deleted file mode 100644 index 9bf8773a8..000000000 --- a/packages/grpc-js-xds/src/xds-stream-state/eds-state.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2021 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 { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js"; -import { isIPv4, isIPv6 } from "net"; -import { Locality__Output } from "../generated/envoy/config/core/v3/Locality"; -import { SocketAddress__Output } from "../generated/envoy/config/core/v3/SocketAddress"; -import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; -import { Any__Output } from "../generated/google/protobuf/Any"; -import { BaseXdsStreamState, HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state"; - -const TRACER_NAME = 'xds_client'; - -const UINT32_MAX = 0xFFFFFFFF; - -function trace(text: string): void { - experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); -} - -function localitiesEqual(a: Locality__Output, b: Locality__Output) { - return a.region === b.region && a.sub_zone === b.sub_zone && a.zone === b.zone; -} - -function addressesEqual(a: SocketAddress__Output, b: SocketAddress__Output) { - return a.address === b.address && a.port_value === b.port_value; -} - -export class EdsState extends BaseXdsStreamState implements XdsStreamState { - protected getResourceName(resource: ClusterLoadAssignment__Output): string { - return resource.cluster_name; - } - protected getProtocolName(): string { - return 'EDS'; - } - protected isStateOfTheWorld(): boolean { - return false; - } - - /** - * Validate the ClusterLoadAssignment object by these rules: - * https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md#clusterloadassignment-proto - * @param message - */ - public validateResponse(message: ClusterLoadAssignment__Output) { - const seenLocalities: {locality: Locality__Output, priority: number}[] = []; - const seenAddresses: SocketAddress__Output[] = []; - const priorityTotalWeights: Map = new Map(); - for (const endpoint of message.endpoints) { - if (!endpoint.locality) { - trace('EDS validation: endpoint locality unset'); - return null; - } - for (const {locality, priority} of seenLocalities) { - if (localitiesEqual(endpoint.locality, locality) && endpoint.priority === priority) { - trace('EDS validation: endpoint locality duplicated: ' + JSON.stringify(locality) + ', priority=' + priority); - return null; - } - } - seenLocalities.push({locality: endpoint.locality, priority: endpoint.priority}); - for (const lb of endpoint.lb_endpoints) { - const socketAddress = lb.endpoint?.address?.socket_address; - if (!socketAddress) { - trace('EDS validation: endpoint socket_address not set'); - return null; - } - if (socketAddress.port_specifier !== 'port_value') { - trace('EDS validation: socket_address.port_specifier !== "port_value"'); - return null; - } - if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) { - trace('EDS validation: address not a valid IPv4 or IPv6 address: ' + socketAddress.address); - return null; - } - for (const address of seenAddresses) { - if (addressesEqual(socketAddress, address)) { - trace('EDS validation: duplicate address seen: ' + address); - return null; - } - } - seenAddresses.push(socketAddress); - } - priorityTotalWeights.set(endpoint.priority, (priorityTotalWeights.get(endpoint.priority) ?? 0) + (endpoint.load_balancing_weight?.value ?? 0)); - } - for (const totalWeight of priorityTotalWeights.values()) { - if (totalWeight > UINT32_MAX) { - trace('EDS validation: total weight > UINT32_MAX') - return null; - } - } - for (const priority of priorityTotalWeights.keys()) { - if (priority > 0 && !priorityTotalWeights.has(priority - 1)) { - trace('EDS validation: priorities not contiguous'); - return null; - } - } - return message; - } -} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts deleted file mode 100644 index e81152e4f..000000000 --- a/packages/grpc-js-xds/src/xds-stream-state/lds-state.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2021 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 { experimental, logVerbosity } from "@grpc/grpc-js"; -import { Listener__Output } from '../generated/envoy/config/listener/v3/Listener'; -import { RdsState } from "./rds-state"; -import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; -import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from '../resources'; -import { getTopLevelFilterUrl, validateTopLevelFilter } from '../http-filter'; -import { EXPERIMENTAL_FAULT_INJECTION } from '../environment'; -import { XdsServerConfig } from "../xds-bootstrap"; - -const TRACER_NAME = 'xds_client'; - -function trace(text: string): void { - experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); -} - -const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'; - -export class LdsState extends BaseXdsStreamState implements XdsStreamState { - protected getResourceName(resource: Listener__Output): string { - return resource.name; - } - protected getProtocolName(): string { - return 'LDS'; - } - protected isStateOfTheWorld(): boolean { - return true; - } - - constructor(xdsServer: XdsServerConfig, private rdsState: RdsState, updateResourceNames: () => void) { - super(xdsServer, updateResourceNames); - } - - public validateResponse(message: Listener__Output): Listener__Output | null { - if ( - !( - message.api_listener?.api_listener && - message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL - ) - ) { - return null; - } - const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, message.api_listener!.api_listener.value); - if (EXPERIMENTAL_FAULT_INJECTION) { - const filterNames = new Set(); - for (const [index, httpFilter] of httpConnectionManager.http_filters.entries()) { - if (filterNames.has(httpFilter.name)) { - trace('LDS response validation failed: duplicate HTTP filter name ' + httpFilter.name); - return null; - } - filterNames.add(httpFilter.name); - if (!validateTopLevelFilter(httpFilter)) { - trace('LDS response validation failed: ' + httpFilter.name + ' filter validation failed'); - return null; - } - /* Validate that the last filter, and only the last filter, is the - * router filter. */ - const filterUrl = getTopLevelFilterUrl(httpFilter.typed_config!) - if (index < httpConnectionManager.http_filters.length - 1) { - if (filterUrl === ROUTER_FILTER_URL) { - trace('LDS response validation failed: router filter is before end of list'); - return null; - } - } else { - if (filterUrl !== ROUTER_FILTER_URL) { - trace('LDS response validation failed: final filter is ' + filterUrl); - return null; - } - } - } - } - switch (httpConnectionManager.route_specifier) { - case 'rds': - if (!httpConnectionManager.rds?.config_source?.ads) { - return null; - } - return message; - case 'route_config': - if (!this.rdsState.validateResponse(httpConnectionManager.route_config!)) { - return null; - } - return message; - } - return null; - } -} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts deleted file mode 100644 index 6e9d1b85e..000000000 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2021 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 { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from "../environment"; -import { RetryPolicy__Output } from "../generated/envoy/config/route/v3/RetryPolicy"; -import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; -import { Duration__Output } from "../generated/google/protobuf/Duration"; -import { validateOverrideFilter } from "../http-filter"; -import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state"; - -const SUPPORTED_PATH_SPECIFIERS = ['prefix', 'path', 'safe_regex']; -const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ - 'exact_match', - 'safe_regex_match', - 'range_match', - 'present_match', - 'prefix_match', - 'suffix_match', - 'string_match']; -const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; - -const UINT32_MAX = 0xFFFFFFFF; - -function durationToMs(duration: Duration__Output | null): number | null { - if (duration === null) { - return null; - } - return (Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000) | 0; -} - -export class RdsState extends BaseXdsStreamState implements XdsStreamState { - protected isStateOfTheWorld(): boolean { - return false; - } - protected getResourceName(resource: RouteConfiguration__Output): string { - return resource.name; - } - protected getProtocolName(): string { - return 'RDS'; - } - - private validateRetryPolicy(policy: RetryPolicy__Output | null): boolean { - if (policy === null) { - return true; - } - const numRetries = policy.num_retries?.value ?? 1 - if (numRetries < 1) { - return false; - } - if (policy.retry_back_off) { - if (!policy.retry_back_off.base_interval) { - return false; - } - const baseInterval = durationToMs(policy.retry_back_off.base_interval)!; - const maxInterval = durationToMs(policy.retry_back_off.max_interval) ?? (10 * baseInterval); - if (!(maxInterval >= baseInterval) && (baseInterval > 0)) { - return false; - } - } - return true; - } - - validateResponse(message: RouteConfiguration__Output) { - // https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation - for (const virtualHost of message.virtual_hosts) { - for (const domainPattern of virtualHost.domains) { - const starIndex = domainPattern.indexOf('*'); - const lastStarIndex = domainPattern.lastIndexOf('*'); - // A domain pattern can have at most one wildcard * - if (starIndex !== lastStarIndex) { - return null; - } - // A wildcard * can either be absent or at the beginning or end of the pattern - if (!(starIndex === -1 || starIndex === 0 || starIndex === domainPattern.length - 1)) { - return null; - } - } - if (EXPERIMENTAL_FAULT_INJECTION) { - for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) { - if (!validateOverrideFilter(filterConfig)) { - return null; - } - } - } - if (EXPERIMENTAL_RETRY) { - if (!this.validateRetryPolicy(virtualHost.retry_policy)) { - return null; - } - } - for (const route of virtualHost.routes) { - const match = route.match; - if (!match) { - return null; - } - if (SUPPORTED_PATH_SPECIFIERS.indexOf(match.path_specifier) < 0) { - return null; - } - for (const headers of match.headers) { - if (SUPPPORTED_HEADER_MATCH_SPECIFIERS.indexOf(headers.header_match_specifier) < 0) { - return null; - } - } - if (route.action !== 'route') { - return null; - } - if ((route.route === undefined) || (route.route === null) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) { - return null; - } - if (EXPERIMENTAL_FAULT_INJECTION) { - for (const [name, filterConfig] of Object.entries(route.typed_per_filter_config ?? {})) { - if (!validateOverrideFilter(filterConfig)) { - return null; - } - } - } - if (EXPERIMENTAL_RETRY) { - if (!this.validateRetryPolicy(route.route.retry_policy)) { - return null; - } - } - if (route.route!.cluster_specifier === 'weighted_clusters') { - let weightSum = 0; - for (const clusterWeight of route.route.weighted_clusters!.clusters) { - weightSum += clusterWeight.weight?.value ?? 0; - } - if (weightSum === 0 || weightSum > UINT32_MAX) { - return null; - } - if (EXPERIMENTAL_FAULT_INJECTION) { - for (const weightedCluster of route.route!.weighted_clusters!.clusters) { - for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) { - if (!validateOverrideFilter(filterConfig)) { - return null; - } - } - } - } - } - } - } - return message; - } -} \ No newline at end of file diff --git a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts b/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts deleted file mode 100644 index 69b6bb715..000000000 --- a/packages/grpc-js-xds/src/xds-stream-state/xds-stream-state.ts +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright 2021 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 { experimental, logVerbosity, Metadata, status, StatusObject } from "@grpc/grpc-js"; -import { Any__Output } from "../generated/google/protobuf/Any"; -import { ResourceCache } from "../resource-cache"; -import { XdsServerConfig } from "../xds-bootstrap"; -import { XdsResourceKey } from "../resources"; - -const TRACER_NAME = 'xds_client'; - -export interface Watcher { - /* Including the isV2 flag here is a bit of a kludge. It would probably be - * better for XdsStreamState#handleResponses to transform the protobuf - * message type into a library-specific configuration object type, to - * remove a lot of duplicate logic, including logic for handling that - * flag. */ - onValidUpdate(update: UpdateType): void; - onTransientError(error: StatusObject): void; - onResourceDoesNotExist(): void; -} - -export interface ResourcePair { - resource: ResourceType; - raw: Any__Output; -} - -export interface AcceptedResourceEntry { - name: string; - raw: Any__Output; -} - -export interface RejectedResourceEntry { - name: string; - raw: Any__Output; - error: string; -} - -export interface HandleResponseResult { - accepted: AcceptedResourceEntry[]; - rejected: RejectedResourceEntry[]; - missing: string[]; -} - -export interface XdsStreamState { - versionInfo: string; - nonce: string; - getResourceNames(): string[]; - /** - * Returns a string containing the error details if the message should be nacked, - * or null if it should be acked. - * @param responses - */ - handleResponses(responses: ResourcePair[], isV2: boolean): HandleResponseResult; - - reportStreamError(status: StatusObject): void; - reportAdsStreamStart(): void; - - addWatcher(name: string, watcher: Watcher): void; - removeWatcher(resourceName: string, watcher: Watcher): void; -} - -export interface XdsSubscriptionTracker { - getResourceNames(): string[]; - handleResourceUpdates(resourceList: ResourcePair[]): void; - - reportStreamError(status: StatusObject): void; - reportAdsStreamStart(): void; - - addWatcher(key: string, watcher: Watcher): void; - removeWatcher(key: string, watcher: Watcher): void; -} - -interface SubscriptionEntry { - watchers: Watcher[]; - cachedResponse: UpdateType | null; - resourceTimer: NodeJS.Timer; - deletionIgnored: boolean; -} - -const RESOURCE_TIMEOUT_MS = 15_000; - -export abstract class BaseXdsStreamState implements XdsStreamState { - versionInfo = ''; - nonce = ''; - - private subscriptions: Map> = new Map>(); - private isAdsStreamRunning = false; - private ignoreResourceDeletion = false; - - constructor(protected xdsServer: XdsServerConfig, private updateResourceNames: () => void) {} - - protected trace(text: string) { - experimental.trace(logVerbosity.DEBUG, TRACER_NAME, this.getProtocolName() + ' | ' + text); - } - - private startResourceTimer(subscriptionEntry: SubscriptionEntry) { - clearTimeout(subscriptionEntry.resourceTimer); - subscriptionEntry.resourceTimer = setTimeout(() => { - for (const watcher of subscriptionEntry.watchers) { - watcher.onResourceDoesNotExist(); - } - }, RESOURCE_TIMEOUT_MS); - } - - addWatcher(name: string, watcher: Watcher): void { - this.trace('Adding watcher for name ' + name); - let subscriptionEntry = this.subscriptions.get(name); - let addedName = false; - if (subscriptionEntry === undefined) { - addedName = true; - subscriptionEntry = { - watchers: [], - cachedResponse: null, - resourceTimer: setTimeout(() => {}, 0), - deletionIgnored: false - }; - if (this.isAdsStreamRunning) { - this.startResourceTimer(subscriptionEntry); - } - this.subscriptions.set(name, subscriptionEntry); - } - subscriptionEntry.watchers.push(watcher); - if (subscriptionEntry.cachedResponse !== null) { - const cachedResponse = subscriptionEntry.cachedResponse; - /* These updates normally occur asynchronously, so we ensure that - * the same happens here */ - process.nextTick(() => { - this.trace('Reporting existing update for new watcher for name ' + name); - watcher.onValidUpdate(cachedResponse); - }); - } - if (addedName) { - this.updateResourceNames(); - } - } - removeWatcher(resourceName: string, watcher: Watcher): void { - this.trace('Removing watcher for name ' + resourceName); - const subscriptionEntry = this.subscriptions.get(resourceName); - if (subscriptionEntry !== undefined) { - const entryIndex = subscriptionEntry.watchers.indexOf(watcher); - if (entryIndex >= 0) { - subscriptionEntry.watchers.splice(entryIndex, 1); - } - if (subscriptionEntry.watchers.length === 0) { - clearTimeout(subscriptionEntry.resourceTimer); - if (subscriptionEntry.deletionIgnored) { - experimental.log(logVerbosity.INFO, 'Unsubscribing from resource with previously ignored deletion: ' + resourceName); - } - this.subscriptions.delete(resourceName); - this.updateResourceNames(); - } - } - } - - getResourceNames(): string[] { - return Array.from(this.subscriptions.keys()); - } - handleResponses(responses: ResourcePair[]): HandleResponseResult { - let result: HandleResponseResult = { - accepted: [], - rejected: [], - missing: [] - } - const allResourceNames = new Set(); - for (const {resource, raw} of responses) { - const resourceName = this.getResourceName(resource); - allResourceNames.add(resourceName); - const subscriptionEntry = this.subscriptions.get(resourceName); - const update = this.validateResponse(resource); - if (update) { - result.accepted.push({ - name: resourceName, - raw: raw}); - if (subscriptionEntry) { - for (const watcher of subscriptionEntry.watchers) { - /* Use process.nextTick to prevent errors from the watcher from - * bubbling up through here. */ - process.nextTick(() => { - watcher.onValidUpdate(update); - }); - } - clearTimeout(subscriptionEntry.resourceTimer); - subscriptionEntry.cachedResponse = update; - if (subscriptionEntry.deletionIgnored) { - experimental.log(logVerbosity.INFO, `Received resource with previously ignored deletion: ${resourceName}`); - subscriptionEntry.deletionIgnored = false; - } - } - } else { - this.trace('Validation failed for message ' + JSON.stringify(resource)); - result.rejected.push({ - name: resourceName, - raw: raw, - error: `Validation failed for resource ${resourceName}` - }); - if (subscriptionEntry) { - for (const watcher of subscriptionEntry.watchers) { - /* Use process.nextTick to prevent errors from the watcher from - * bubbling up through here. */ - process.nextTick(() => { - watcher.onTransientError({ - code: status.UNAVAILABLE, - details: `Validation failed for resource ${resourceName}`, - metadata: new Metadata() - }); - }); - } - clearTimeout(subscriptionEntry.resourceTimer); - } - } - } - result.missing = this.handleMissingNames(allResourceNames); - this.trace('Received response with resource names [' + Array.from(allResourceNames) + ']'); - return result; - } - reportStreamError(status: StatusObject): void { - for (const subscriptionEntry of this.subscriptions.values()) { - for (const watcher of subscriptionEntry.watchers) { - watcher.onTransientError(status); - } - clearTimeout(subscriptionEntry.resourceTimer); - } - this.isAdsStreamRunning = false; - this.nonce = ''; - } - - reportAdsStreamStart() { - if (this.isAdsStreamRunning) { - return; - } - this.isAdsStreamRunning = true; - for (const subscriptionEntry of this.subscriptions.values()) { - if (subscriptionEntry.cachedResponse === null) { - this.startResourceTimer(subscriptionEntry); - } - } - } - - private handleMissingNames(allResponseNames: Set): string[] { - if (this.isStateOfTheWorld()) { - const missingNames: string[] = []; - for (const [resourceName, subscriptionEntry] of this.subscriptions.entries()) { - if (!allResponseNames.has(resourceName) && subscriptionEntry.cachedResponse !== null) { - if (this.ignoreResourceDeletion) { - if (!subscriptionEntry.deletionIgnored) { - experimental.log(logVerbosity.ERROR, 'Ignoring nonexistent resource ' + resourceName); - subscriptionEntry.deletionIgnored = true; - } - } else { - this.trace('Reporting resource does not exist named ' + resourceName); - missingNames.push(resourceName); - for (const watcher of subscriptionEntry.watchers) { - /* Use process.nextTick to prevent errors from the watcher from - * bubbling up through here. */ - process.nextTick(() => { - watcher.onResourceDoesNotExist(); - }); - } - subscriptionEntry.cachedResponse = null; - } - } - } - return missingNames; - } else { - return []; - } - } - - enableIgnoreResourceDeletion() { - this.ignoreResourceDeletion = true; - } - - /** - * Apply the validation rules for this resource type to this resource - * instance. - * This function is public so that the LDS validateResponse can call into - * the RDS validateResponse. - * @param resource The resource object sent by the xDS server - */ - public abstract validateResponse(resource: ResponseType): UpdateType | null; - /** - * Get the name of a resource object. The name is some field of the object, so - * getting it depends on the specific type. - * @param resource - */ - protected abstract getResourceName(resource: ResponseType): string; - protected abstract getProtocolName(): string; - protected abstract getTypeUrl(): string; - /** - * Indicates whether responses are "state of the world", i.e. that they - * contain all resources and that omitted previously-seen resources should - * be treated as removed. - */ - protected abstract isStateOfTheWorld(): boolean; -} \ No newline at end of file diff --git a/packages/grpc-js-xds/test/client.ts b/packages/grpc-js-xds/test/client.ts index bcc6f3cd8..c2420b231 100644 --- a/packages/grpc-js-xds/test/client.ts +++ b/packages/grpc-js-xds/test/client.ts @@ -44,12 +44,16 @@ export class XdsTestClient { private client: EchoTestServiceClient; private callInterval: NodeJS.Timer; - constructor(targetName: string, xdsServer: XdsServer) { - this.client = new loadedProtos.grpc.testing.EchoTestService(`xds:///${targetName}`, credentials.createInsecure(), {[BOOTSTRAP_CONFIG_KEY]: xdsServer.getBootstrapInfoString()}); + constructor(target: string, bootstrapInfo: string) { + this.client = new loadedProtos.grpc.testing.EchoTestService(target, credentials.createInsecure(), {[BOOTSTRAP_CONFIG_KEY]: bootstrapInfo}); this.callInterval = setInterval(() => {}, 0); clearInterval(this.callInterval); } + static createFromServer(targetName: string, xdsServer: XdsServer) { + return new XdsTestClient(`xds:///${targetName}`, xdsServer.getBootstrapInfoString()); + } + startCalls(interval: number) { clearInterval(this.callInterval); this.callInterval = setInterval(() => { diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index bcc3cd1a5..be0e977ea 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -64,24 +64,25 @@ export interface FakeCluster { getAllClusterConfigs(): Cluster[]; getName(): string; startAllBackends(): Promise; + haveAllBackendsReceivedTraffic(): boolean; waitForAllBackendsToReceiveTraffic(): Promise; } export class FakeEdsCluster implements FakeCluster { - constructor(private name: string, private endpoints: Endpoint[]) {} + constructor(private clusterName: string, private endpointName: string, private endpoints: Endpoint[]) {} getEndpointConfig(): ClusterLoadAssignment { return { - cluster_name: this.name, + cluster_name: this.endpointName, endpoints: this.endpoints.map(getLocalityLbEndpoints) }; } getClusterConfig(): Cluster { return { - name: this.name, + name: this.clusterName, type: 'EDS', - eds_cluster_config: {eds_config: {ads: {}}}, + eds_cluster_config: {eds_config: {ads: {}}, service_name: this.endpointName}, lb_policy: 'ROUND_ROBIN' } } @@ -91,14 +92,14 @@ export class FakeEdsCluster implements FakeCluster { } getName() { - return this.name; + return this.clusterName; } startAllBackends(): Promise { return Promise.all(this.endpoints.map(endpoint => Promise.all(endpoint.backends.map(backend => backend.startAsync())))); } - private haveAllBackendsReceivedTraffic(): boolean { + haveAllBackendsReceivedTraffic(): boolean { for (const endpoint of this.endpoints) { for (const backend of endpoint.backends) { if (backend.getCallCount() < 1) { @@ -167,6 +168,9 @@ export class FakeDnsCluster implements FakeCluster { startAllBackends(): Promise { return this.backend.startAsync(); } + haveAllBackendsReceivedTraffic(): boolean { + return this.backend.getCallCount() > 0; + } waitForAllBackendsToReceiveTraffic(): Promise { return new Promise((resolve, reject) => { this.backend.onCall(resolve); @@ -203,6 +207,14 @@ export class FakeAggregateCluster implements FakeCluster { startAllBackends(): Promise { return Promise.all(this.children.map(child => child.startAllBackends())); } + haveAllBackendsReceivedTraffic(): boolean { + for (const child of this.children) { + if (!child.haveAllBackendsReceivedTraffic()) { + return false; + } + } + return true; + } waitForAllBackendsToReceiveTraffic(): Promise { return Promise.all(this.children.map(child => child.waitForAllBackendsToReceiveTraffic())).then(() => {}); } @@ -241,11 +253,11 @@ function createRouteConfig(route: FakeRoute): Route { } export class FakeRouteGroup { - constructor(private name: string, private routes: FakeRoute[]) {} + constructor(private listenerName: string, private routeName: string, private routes: FakeRoute[]) {} getRouteConfiguration(): RouteConfiguration { return { - name: this.name, + name: this.routeName, virtual_hosts: [{ domains: ['*'], routes: this.routes.map(createRouteConfig) @@ -257,12 +269,12 @@ export class FakeRouteGroup { const httpConnectionManager: HttpConnectionManager & AnyExtension = { '@type': HTTP_CONNECTION_MANGER_TYPE_URL, rds: { - route_config_name: this.name, + route_config_name: this.routeName, config_source: {ads: {}} } } return { - name: this.name, + name: this.listenerName, api_listener: { api_listener: httpConnectionManager } @@ -281,6 +293,21 @@ export class FakeRouteGroup { })); } + haveAllBackendsReceivedTraffic(): boolean { + for (const route of this.routes) { + if (route.cluster) { + return route.cluster.haveAllBackendsReceivedTraffic(); + } else if (route.weightedClusters) { + for (const weightedCluster of route.weightedClusters) { + if (!weightedCluster.cluster.haveAllBackendsReceivedTraffic()) { + return false; + } + } + } + } + return true; + } + waitForAllBackendsToReceiveTraffic(): Promise { return Promise.all(this.routes.map(route => { if (route.cluster) { diff --git a/packages/grpc-js-xds/test/test-cluster-type.ts b/packages/grpc-js-xds/test/test-cluster-type.ts index 483180d9c..416f17727 100644 --- a/packages/grpc-js-xds/test/test-cluster-type.ts +++ b/packages/grpc-js-xds/test/test-cluster-type.ts @@ -40,7 +40,7 @@ describe('Cluster types', () => { describe('Logical DNS Clusters', () => { it('Should successfully make RPCs', done => { const cluster = new FakeDnsCluster('dnsCluster', new Backend()); - const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.addResponseListener((typeUrl, responseState) => { if (responseState.state === 'NACKED') { @@ -50,7 +50,7 @@ describe('Cluster types', () => { xdsServer.setCdsResource(cluster.getClusterConfig()); xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); xdsServer.setLdsResource(routeGroup.getListener()); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.sendOneCall(error => { done(error); }); @@ -63,10 +63,10 @@ describe('Cluster types', () => { it('Should result in prioritized clusters', () => { const backend1 = new Backend(); const backend2 = new Backend(); - const cluster1 = new FakeEdsCluster('cluster1', [{backends: [backend1], locality:{region: 'region1'}}]); - const cluster2 = new FakeEdsCluster('cluster2', [{backends: [backend2], locality:{region: 'region2'}}]); + const cluster1 = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend1], locality:{region: 'region1'}}]); + const cluster2 = new FakeEdsCluster('cluster2', 'endpoint2', [{backends: [backend2], locality:{region: 'region2'}}]); const aggregateCluster = new FakeAggregateCluster('aggregateCluster', [cluster1, cluster2]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster}]); + const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: aggregateCluster}]); return routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster1.getEndpointConfig()); xdsServer.setCdsResource(cluster1.getClusterConfig()); @@ -81,7 +81,7 @@ describe('Cluster types', () => { assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); } }); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); return cluster1.waitForAllBackendsToReceiveTraffic(); }).then(() => backend1.shutdownAsync() @@ -92,11 +92,11 @@ describe('Cluster types', () => { it('Should handle a diamond dependency', () => { const backend1 = new Backend(); const backend2 = new Backend(); - const cluster1 = new FakeEdsCluster('cluster1', [{backends: [backend1], locality:{region: 'region1'}}]); - const cluster2 = new FakeEdsCluster('cluster2', [{backends: [backend2], locality:{region: 'region2'}}]); + const cluster1 = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend1], locality:{region: 'region1'}}]); + const cluster2 = new FakeEdsCluster('cluster2', 'endpoint2', [{backends: [backend2], locality:{region: 'region2'}}]); const aggregateCluster1 = new FakeAggregateCluster('aggregateCluster1', [cluster1, cluster2]); const aggregateCluster2 = new FakeAggregateCluster('aggregateCluster2', [cluster1, aggregateCluster1]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster2}]); + const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: aggregateCluster2}]); return Promise.all([backend1.startAsync(), backend2.startAsync()]).then(() => { xdsServer.setEdsResource(cluster1.getEndpointConfig()); xdsServer.setCdsResource(cluster1.getClusterConfig()); @@ -112,7 +112,7 @@ describe('Cluster types', () => { assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); } }); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); return cluster1.waitForAllBackendsToReceiveTraffic(); }).then(() => backend1.shutdownAsync() @@ -123,10 +123,10 @@ describe('Cluster types', () => { it('Should handle EDS then DNS cluster order', () => { const backend1 = new Backend(); const backend2 = new Backend(); - const cluster1 = new FakeEdsCluster('cluster1', [{backends: [backend1], locality:{region: 'region1'}}]); + const cluster1 = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend1], locality:{region: 'region1'}}]); const cluster2 = new FakeDnsCluster('cluster2', backend2); const aggregateCluster = new FakeAggregateCluster('aggregateCluster', [cluster1, cluster2]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster}]); + const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: aggregateCluster}]); return routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster1.getEndpointConfig()); xdsServer.setCdsResource(cluster1.getClusterConfig()); @@ -140,7 +140,7 @@ describe('Cluster types', () => { assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); } }); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); return cluster1.waitForAllBackendsToReceiveTraffic(); }).then(() => backend1.shutdownAsync() @@ -152,9 +152,9 @@ describe('Cluster types', () => { const backend1 = new Backend(); const backend2 = new Backend(); const cluster1 = new FakeDnsCluster('cluster1', backend1); - const cluster2 = new FakeEdsCluster('cluster2', [{backends: [backend2], locality:{region: 'region2'}}]); + const cluster2 = new FakeEdsCluster('cluster2', 'endpoint2', [{backends: [backend2], locality:{region: 'region2'}}]); const aggregateCluster = new FakeAggregateCluster('aggregateCluster', [cluster1, cluster2]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: aggregateCluster}]); + const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: aggregateCluster}]); return routeGroup.startAllBackends().then(() => { xdsServer.setCdsResource(cluster1.getClusterConfig()); xdsServer.setEdsResource(cluster2.getEndpointConfig()); @@ -168,7 +168,7 @@ describe('Cluster types', () => { assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); } }); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); return cluster1.waitForAllBackendsToReceiveTraffic(); }).then(() => backend1.shutdownAsync() diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts index 7c28f0920..0df576e17 100644 --- a/packages/grpc-js-xds/test/test-core.ts +++ b/packages/grpc-js-xds/test/test-core.ts @@ -39,8 +39,8 @@ describe('core xDS functionality', () => { xdsServer?.shutdownServer(); }) it('should route requests to the single backend', done => { - const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality:{region: 'region1'}}]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { xdsServer.setEdsResource(cluster.getEndpointConfig()); xdsServer.setCdsResource(cluster.getClusterConfig()); @@ -52,7 +52,7 @@ describe('core xDS functionality', () => { assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`); } }) - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { client.stopCalls(); diff --git a/packages/grpc-js-xds/test/test-federation.ts b/packages/grpc-js-xds/test/test-federation.ts new file mode 100644 index 000000000..0a2a57b6f --- /dev/null +++ b/packages/grpc-js-xds/test/test-federation.ts @@ -0,0 +1,209 @@ +/* + * 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. + * + */ + +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeEdsCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; +import assert = require("assert"); + +/* Test cases in this file are derived from examples in the xDS federation proposal + * https://github.com/grpc/proposal/blob/master/A47-xds-federation.md */ +describe('Federation', () => { + let xdsServers: XdsServer[] = []; + let xdsClient: XdsTestClient; + afterEach(() => { + xdsClient?.close(); + for (const server of xdsServers) { + server.shutdownServer(); + } + xdsServers = []; + }); + describe('Bootstrap Config Contains No New Fields', () => { + let bootstrap: string; + beforeEach((done) => { + const xdsServer = new XdsServer(); + xdsServers.push(xdsServer); + xdsServer.startServer(error => { + if (error) { + done(error); + return; + } + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('server.example.com', 'route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + const bootstrapInfo = { + xds_servers: [xdsServer.getBootstrapServerConfig()], + node: { + id: 'test', + locality: {} + } + }; + bootstrap = JSON.stringify(bootstrapInfo); + done(); + }); + }); + }); + it('Should accept an old-style name', (done) => { + xdsClient = new XdsTestClient('xds:server.example.com', bootstrap); + // There is only one server, so a successful request must go to that server + xdsClient.sendOneCall(done); + }); + it('Should reject a new-style name', (done) => { + xdsClient = new XdsTestClient('xds://xds.authority.com/server.example.com', bootstrap); + xdsClient.sendOneCall(error => { + assert(error); + done(); + }); + }); + }); + describe('New-Style Names on gRPC Client', () => { + let bootstrap: string; + beforeEach((done) => { + const xdsServer = new XdsServer(); + xdsServers.push(xdsServer); + xdsServer.startServer(error => { + if (error) { + done(error); + return; + } + const cluster = new FakeEdsCluster('xdstp://xds.authority.com/envoy.config.cluster.v3.Cluster/cluster1', 'xdstp://xds.authority.com/envoy.config.endpoint.v3.ClusterLoadAssignment/endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + const routeGroup = new FakeRouteGroup('xdstp://xds.authority.com/envoy.config.listener.v3.Listener/server.example.com', 'xdstp://xds.authority.com/envoy.config.route.v3.RouteConfiguration/route1', [{cluster: cluster}]); + routeGroup.startAllBackends().then(() => { + xdsServer.setEdsResource(cluster.getEndpointConfig()); + xdsServer.setCdsResource(cluster.getClusterConfig()); + xdsServer.setRdsResource(routeGroup.getRouteConfiguration()); + xdsServer.setLdsResource(routeGroup.getListener()); + const bootstrapInfo = { + xds_servers: [xdsServer.getBootstrapServerConfig()], + node: { + id: 'test', + locality: {} + }, + "client_default_listener_resource_name_template": "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%s", + "authorities": { + "xds.authority.com": { + } + } + }; + bootstrap = JSON.stringify(bootstrapInfo); + done(); + }); + }); + }); + it('Should accept a target with no authority', (done) => { + xdsClient = new XdsTestClient('xds:server.example.com', bootstrap); + // There is only one server, so a successful request must go to that server + xdsClient.sendOneCall(done); + }); + it('Should accept a target with a listed authority', (done) => { + xdsClient = new XdsTestClient('xds://xds.authority.com/server.example.com', bootstrap); + // There is only one server, so a successful request must go to that server + xdsClient.sendOneCall(done); + }); + }); + describe('Multiple authorities', () => { + let bootstrap: string; + let defaultRouteGroup: FakeRouteGroup; + let otherRouteGroup: FakeRouteGroup; + beforeEach((done) => { + const defaultServer = new XdsServer(); + xdsServers.push(defaultServer); + const otherServer = new XdsServer(); + xdsServers.push(otherServer); + defaultServer.startServer(error => { + if (error) { + done(error); + return; + } + otherServer.startServer(error => { + if (error) { + done(error); + return; + } + const defaultCluster = new FakeEdsCluster('xdstp://xds.authority.com/envoy.config.cluster.v3.Cluster/cluster1', 'xdstp://xds.authority.com/envoy.config.endpoint.v3.ClusterLoadAssignment/endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + defaultRouteGroup = new FakeRouteGroup('xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/client/server.example.com?project_id=1234', 'xdstp://xds.authority.com/envoy.config.route.v3.RouteConfiguration/route1', [{cluster: defaultCluster}]); + const otherCluster = new FakeEdsCluster('xdstp://xds.other.com/envoy.config.cluster.v3.Cluster/cluster2', 'xdstp://xds.other.com/envoy.config.endpoint.v3.ClusterLoadAssignment/endpoint2', [{backends: [new Backend()], locality:{region: 'region2'}}]); + otherRouteGroup = new FakeRouteGroup('xdstp://xds.other.com/envoy.config.listener.v3.Listener/server.other.com', 'xdstp://xds.other.com/envoy.config.route.v3.RouteConfiguration/route2', [{cluster: otherCluster}]); + Promise.all([defaultRouteGroup.startAllBackends(), otherRouteGroup.startAllBackends()]).then(() => { + defaultServer.setEdsResource(defaultCluster.getEndpointConfig()); + defaultServer.setCdsResource(defaultCluster.getClusterConfig()); + defaultServer.setRdsResource(defaultRouteGroup.getRouteConfiguration()); + defaultServer.setLdsResource(defaultRouteGroup.getListener()); + otherServer.setEdsResource(otherCluster.getEndpointConfig()); + otherServer.setCdsResource(otherCluster.getClusterConfig()); + otherServer.setRdsResource(otherRouteGroup.getRouteConfiguration()); + otherServer.setLdsResource(otherRouteGroup.getListener()); + const bootstrapInfo = { + xds_servers: [defaultServer.getBootstrapServerConfig()], + node: { + id: 'test', + locality: {} + }, + + // Resource name template for xds: target URIs with no authority. + "client_default_listener_resource_name_template": "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/client/%s?project_id=1234", + + // Resource name template for xDS-enabled gRPC servers. + "server_listener_resource_name_template": "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/server/%s?project_id=1234", + + // Authorities map. + "authorities": { + "xds.authority.com": { + "client_listener_resource_name_template": "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/client/%s?project_id=1234" + }, + "xds.other.com": { + "xds_servers": [otherServer.getBootstrapServerConfig()] + } + } + }; + bootstrap = JSON.stringify(bootstrapInfo); + done(); + }); + }); + }); + }); + it('Should accept a name with no authority', (done) => { + xdsClient = new XdsTestClient('xds:server.example.com', bootstrap); + xdsClient.sendOneCall(error => { + assert.ifError(error); + assert(defaultRouteGroup.haveAllBackendsReceivedTraffic()); + done(); + }); + }); + it('Should accept a with an authority that has no server configured', (done) => { + xdsClient = new XdsTestClient('xds://xds.authority.com/server.example.com', bootstrap); + xdsClient.sendOneCall(error => { + assert.ifError(error); + assert(defaultRouteGroup.haveAllBackendsReceivedTraffic()); + done(); + }); + }); + it('Should accept a name with an authority that has no template configured', (done) => { + xdsClient = new XdsTestClient('xds://xds.other.com/server.other.com', bootstrap); + xdsClient.sendOneCall(error => { + assert.ifError(error); + assert(otherRouteGroup.haveAllBackendsReceivedTraffic()); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/grpc-js-xds/test/test-listener-resource-name.ts b/packages/grpc-js-xds/test/test-listener-resource-name.ts index 1359d0485..de434d28c 100644 --- a/packages/grpc-js-xds/test/test-listener-resource-name.ts +++ b/packages/grpc-js-xds/test/test-listener-resource-name.ts @@ -26,6 +26,8 @@ const testNode: Node = { locality: {} }; +/* Test cases in this file are derived from examples in the xDS federation proposal + * https://github.com/grpc/proposal/blob/master/A47-xds-federation.md */ describe('Listener resource name evaluation', () => { describe('No new bootstrap fields', () => { const bootstrap = validateBootstrapConfig({ diff --git a/packages/grpc-js-xds/test/test-nack.ts b/packages/grpc-js-xds/test/test-nack.ts index ae2a9b3e4..ce6b6f45b 100644 --- a/packages/grpc-js-xds/test/test-nack.ts +++ b/packages/grpc-js-xds/test/test-nack.ts @@ -39,14 +39,14 @@ describe('Validation errors', () => { xdsServer?.shutdownServer(); }); it('Should continue to use a valid resource after receiving an invalid EDS update', done => { - const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + 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()); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid EDS resource @@ -69,14 +69,14 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid CDS update', done => { - const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + 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()); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid CDS resource @@ -99,14 +99,14 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid RDS update', done => { - const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + 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()); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid RDS resource @@ -129,14 +129,14 @@ describe('Validation errors', () => { }, reason => done(reason)); }); it('Should continue to use a valid resource after receiving an invalid LDS update', done => { - const cluster = new FakeEdsCluster('cluster1', [{backends: [new Backend()], locality: {region: 'region1'}}]); - const routeGroup = new FakeRouteGroup('route1', [{cluster: cluster}]); + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality: {region: 'region1'}}]); + 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()); - client = new XdsTestClient('route1', xdsServer); + client = XdsTestClient.createFromServer('listener1', xdsServer); client.startCalls(100); routeGroup.waitForAllBackendsToReceiveTraffic().then(() => { // After backends receive calls, set invalid LDS resource diff --git a/packages/grpc-js-xds/test/xds-server.ts b/packages/grpc-js-xds/test/xds-server.ts index 36fd27b85..8b677ac18 100644 --- a/packages/grpc-js-xds/test/xds-server.ts +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -324,15 +324,22 @@ export class XdsServer { this.server?.forceShutdown(); } + getBootstrapServerConfig() { + if (this.port === null) { + throw new Error('Bootstrap info unavailable; server not started'); + } + return { + server_uri: `localhost:${this.port}`, + channel_creds: [{type: 'insecure'}] + }; + } + getBootstrapInfoString(): string { if (this.port === null) { throw new Error('Bootstrap info unavailable; server not started'); } const bootstrapInfo = { - xds_servers: [{ - server_uri: `localhost:${this.port}`, - channel_creds: [{type: 'insecure'}] - }], + xds_servers: [this.getBootstrapServerConfig()], node: { id: 'test', locality: {} From 61a518c30aec12834d9dbed177611a630de096eb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 15 Jun 2023 10:45:56 -0700 Subject: [PATCH 491/694] Fix stream end handling in xds client --- packages/grpc-js-xds/src/xds-client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 71b2a0966..e12c73fb0 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -401,6 +401,7 @@ class AdsCallState { watcher.onError(streamStatus); } } + this.client.handleAdsStreamEnd(); } hasSubscribedResources(): boolean { @@ -681,6 +682,7 @@ class LrsCallState { this.client.trace( 'ADS stream ended. code=' + status.code + ' details= ' + status.details ); + this.client.handleLrsStreamEnd(); } private handleResponseMessage(message: LoadStatsResponse__Output) { @@ -899,6 +901,7 @@ class XdsSingleServerClient { } handleAdsStreamEnd() { + this.adsCallState = null; /* The backoff timer would start the stream when it finishes. If it is not * running, restart the stream immediately. */ if (!this.adsBackoff.isRunning()) { @@ -922,6 +925,7 @@ class XdsSingleServerClient { } handleLrsStreamEnd() { + this.lrsCallState = null; /* The backoff timer would start the stream when it finishes. If it is not * running, restart the stream immediately. */ if (!this.lrsBackoff.isRunning()) { From 1880faf8a005a712f5c3e2e52fdbf04b87067066 Mon Sep 17 00:00:00 2001 From: Dan Rumney Date: Thu, 15 Jun 2023 12:52:45 -0500 Subject: [PATCH 492/694] fix(packages/grpc-js/test/assert2): move assert2 into its own file Moving from exporting a namespace to just putting assert2 functions into their own files Fixes #2464 --- packages/grpc-js/test/assert2.ts | 93 ++++++++++++++++++++++++++++++++ packages/grpc-js/test/common.ts | 83 ++-------------------------- 2 files changed, 96 insertions(+), 80 deletions(-) create mode 100644 packages/grpc-js/test/assert2.ts diff --git a/packages/grpc-js/test/assert2.ts b/packages/grpc-js/test/assert2.ts new file mode 100644 index 000000000..d3912a928 --- /dev/null +++ b/packages/grpc-js/test/assert2.ts @@ -0,0 +1,93 @@ +/* + * 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 * as assert from 'assert'; + +const toCall = new Map<() => void, number>(); +const afterCallsQueue: Array<() => void> = []; + +/** + * Assert that the given function doesn't throw an error, and then return + * its value. + * @param fn The function to evaluate. + */ +export function noThrowAndReturn(fn: () => T): T { + try { + return fn(); + } catch (e) { + assert.throws(() => { + throw e; + }); + throw e; // for type safety only + } +} + +/** + * Helper function that returns true when every function wrapped with + * mustCall has been called. + */ +function mustCallsSatisfied(): boolean { + let result = true; + toCall.forEach(value => { + result = result && value === 0; + }); + return result; +} + +export function clearMustCalls(): void { + afterCallsQueue.length = 0; +} + +/** + * Wraps a function to keep track of whether it was called or not. + * @param fn The function to wrap. + */ +// tslint:disable:no-any +export function mustCall(fn: (...args: any[]) => T): (...args: any[]) => T { + const existingValue = toCall.get(fn); + if (existingValue !== undefined) { + toCall.set(fn, existingValue + 1); + } else { + toCall.set(fn, 1); + } + return (...args: any[]) => { + const result = fn(...args); + const existingValue = toCall.get(fn); + if (existingValue !== undefined) { + toCall.set(fn, existingValue - 1); + } + if (mustCallsSatisfied()) { + afterCallsQueue.forEach(fn => fn()); + afterCallsQueue.length = 0; + } + return result; + }; +} + +/** + * Calls the given function when every function that was wrapped with + * mustCall has been called. + * @param fn The function to call once all mustCall-wrapped functions have + * been called. + */ +export function afterMustCallsSatisfied(fn: () => void): void { + if (!mustCallsSatisfied()) { + afterCallsQueue.push(fn); + } else { + fn(); + } +} diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index 24cb71650..16b393b55 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -16,7 +16,7 @@ */ import * as loader from '@grpc/proto-loader'; -import * as assert from 'assert'; +import * as assert2 from './assert2'; import { GrpcObject, loadPackageDefinition } from '../src/make-client'; @@ -32,86 +32,9 @@ export function mockFunction(): never { throw new Error('Not implemented'); } -export namespace assert2 { - const toCall = new Map<() => void, number>(); - const afterCallsQueue: Array<() => void> = []; - - /** - * Assert that the given function doesn't throw an error, and then return - * its value. - * @param fn The function to evaluate. - */ - export function noThrowAndReturn(fn: () => T): T { - try { - return fn(); - } catch (e) { - assert.throws(() => { - throw e; - }); - throw e; // for type safety only - } - } - - /** - * Helper function that returns true when every function wrapped with - * mustCall has been called. - */ - function mustCallsSatisfied(): boolean { - let result = true; - toCall.forEach(value => { - result = result && value === 0; - }); - return result; - } - - export function clearMustCalls(): void { - afterCallsQueue.length = 0; - } - - /** - * Wraps a function to keep track of whether it was called or not. - * @param fn The function to wrap. - */ - // tslint:disable:no-any - export function mustCall( - fn: (...args: any[]) => T - ): (...args: any[]) => T { - const existingValue = toCall.get(fn); - if (existingValue !== undefined) { - toCall.set(fn, existingValue + 1); - } else { - toCall.set(fn, 1); - } - return (...args: any[]) => { - const result = fn(...args); - const existingValue = toCall.get(fn); - if (existingValue !== undefined) { - toCall.set(fn, existingValue - 1); - } - if (mustCallsSatisfied()) { - afterCallsQueue.forEach(fn => fn()); - afterCallsQueue.length = 0; - } - return result; - }; - } - - /** - * Calls the given function when every function that was wrapped with - * mustCall has been called. - * @param fn The function to call once all mustCall-wrapped functions have - * been called. - */ - export function afterMustCallsSatisfied(fn: () => void): void { - if (!mustCallsSatisfied()) { - afterCallsQueue.push(fn); - } else { - fn(); - } - } -} - export function loadProtoFile(file: string): GrpcObject { const packageDefinition = loader.loadSync(file, protoLoaderOptions); return loadPackageDefinition(packageDefinition); } + +export { assert2 }; From e3522bb53bbd42ea81fedffa9015e4987ecb8fe0 Mon Sep 17 00:00:00 2001 From: Dan Rumney Date: Thu, 15 Jun 2023 12:54:15 -0500 Subject: [PATCH 493/694] refactor(grpc-js): convert from gts to eslint/prettier/tsconfig GTS provides config for ESLint, Prettier and TSConfig; this commit removes GTS, but brings over the configuration details Fixes #2464 --- packages/grpc-js/.eslintrc | 58 +++++++++++++++++++++++++++-- packages/grpc-js/package.json | 19 +++++++--- packages/grpc-js/prettier.config.js | 2 + packages/grpc-js/tsconfig.json | 14 ++++++- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/packages/grpc-js/.eslintrc b/packages/grpc-js/.eslintrc index 64585682c..2f6bfd62d 100644 --- a/packages/grpc-js/.eslintrc +++ b/packages/grpc-js/.eslintrc @@ -1,11 +1,61 @@ { "root": true, - "extends": "./node_modules/gts", + + "extends": [ + "eslint:recommended", + "plugin:node/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "plugins": ["node", "prettier", "@typescript-eslint"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "ignorePatterns": ["**/generated/**", "**/node_modules/**", "**/build/**"], "rules": { - "node/no-unpublished-import": ["error", { + "node/no-unpublished-import": [ + "error", + { "tryExtensions": [".ts", ".js", ".json", ".node"] - }], + } + ], "@typescript-eslint/no-unused-vars": "off", - "node/no-unpublished-require": "off" + "node/no-unpublished-require": "off", + "prettier/prettier": "error", + "block-scoped-var": "error", + "eqeqeq": "error", + "no-var": "error", + "prefer-const": "error", + "no-case-declarations": "warn", + "no-restricted-properties": [ + "error", + { + "object": "describe", + "property": "only" + }, + { + "object": "it", + "property": "only" + } + ], + + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-warning-comments": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/camelcase": "off", + "node/no-missing-import": "off", + "node/no-empty-function": "off", + "node/no-unsupported-features/es-syntax": "off", + "node/no-missing-require": "off", + "node/shebang": "off", + "no-dupe-class-members": "off", + "require-atomic-updates": "off" } } diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index dd341ef03..c068b0e95 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -22,9 +22,15 @@ "@types/ncp": "^2.0.1", "@types/pify": "^3.0.2", "@types/semver": "^7.3.9", + "@typescript-eslint/eslint-plugin": "^5.59.11", + "@typescript-eslint/parser": "^5.59.11", + "@typescript-eslint/typescript-estree": "^5.59.11", "clang-format": "^1.0.55", + "eslint": "^8.42.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^4.2.1", "execa": "^2.0.3", - "gts": "^3.1.1", "gulp": "^4.0.2", "gulp-mocha": "^6.0.0", "lodash": "^4.17.4", @@ -32,10 +38,11 @@ "mocha-jenkins-reporter": "^0.4.1", "ncp": "^2.0.0", "pify": "^4.0.1", + "prettier": "^2.8.8", "rimraf": "^3.0.2", "semver": "^7.3.5", - "ts-node": "^8.3.0", - "typescript": "^4.8.4" + "ts-node": "^10.9.1", + "typescript": "^5.1.3" }, "contributors": [ { @@ -47,11 +54,11 @@ "clean": "rimraf ./build", "compile": "tsc -p .", "format": "clang-format -i -style=\"{Language: JavaScript, BasedOnStyle: Google, ColumnLimit: 80}\" src/*.ts test/*.ts", - "lint": "npm run check", + "lint": "eslint src/*.ts test/*.ts", "prepare": "npm run generate-types && npm run compile", "test": "gulp test", - "check": "gts check src/**/*.ts", - "fix": "gts fix src/*.ts", + "check": "npm run lint", + "fix": "eslint --fix src/*.ts test/*.ts", "pretest": "npm run generate-types && npm run generate-test-types && npm run compile", "posttest": "npm run check && madge -c ./build/src", "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ --include-dirs test/fixtures/ -O src/generated/ --grpcLib ../index channelz.proto", diff --git a/packages/grpc-js/prettier.config.js b/packages/grpc-js/prettier.config.js index 92747c8cc..ecd1f4854 100644 --- a/packages/grpc-js/prettier.config.js +++ b/packages/grpc-js/prettier.config.js @@ -2,4 +2,6 @@ module.exports = { proseWrap: 'always', singleQuote: true, trailingComma: 'es5', + bracketSpacing: true, + arrowParens: 'avoid', }; diff --git a/packages/grpc-js/tsconfig.json b/packages/grpc-js/tsconfig.json index 310b633c7..d678782ce 100644 --- a/packages/grpc-js/tsconfig.json +++ b/packages/grpc-js/tsconfig.json @@ -1,6 +1,15 @@ { - "extends": "./node_modules/gts/tsconfig-google.json", "compilerOptions": { + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "declaration": true, + "forceConsistentCasingInFileNames": true, + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "pretty": true, + "sourceMap": true, + "strict": true, "lib": ["es2017"], "outDir": "build", "target": "es2017", @@ -12,5 +21,8 @@ "include": [ "src/**/*.ts", "test/**/*.ts" + ], + "exclude": [ + "node_modules" ] } From 208b79e6254540d818a8d560ef8e5760adbb421f Mon Sep 17 00:00:00 2001 From: Dan Rumney Date: Thu, 15 Jun 2023 13:01:38 -0500 Subject: [PATCH 494/694] refactor(packages/grpc-js/log.txt): remove extraneous file --- packages/grpc-js/log.txt | 971 --------------------------------------- 1 file changed, 971 deletions(-) delete mode 100644 packages/grpc-js/log.txt diff --git a/packages/grpc-js/log.txt b/packages/grpc-js/log.txt deleted file mode 100644 index 7a6bbc2c8..000000000 --- a/packages/grpc-js/log.txt +++ /dev/null @@ -1,971 +0,0 @@ -{ - O: [Getter/Setter], - outDir: [Getter/Setter], - 'out-dir': [Getter/Setter], - _: [ - 'envoy/service/discovery/v2/ads.proto', - 'envoy/api/v2/listener.proto', - 'envoy/api/v2/route.proto', - 'envoy/api/v2/cluster.proto', - 'envoy/api/v2/endpoint.proto' - ], - keepCase: true, - 'keep-case': true, - longs: [Function: String], - enums: [Function: String], - defaults: true, - oneofs: true, - json: true, - includeDirs: [ - 'deps/envoy-api/', - 'deps/udpa/', - 'node_modules/protobufjs/', - 'deps/googleapis/', - 'deps/protoc-gen-validate/' - ], - I: [ - 'deps/envoy-api/', - 'deps/udpa/', - 'node_modules/protobufjs/', - 'deps/googleapis/', - 'deps/protoc-gen-validate/' - ], - 'include-dirs': [ - 'deps/envoy-api/', - 'deps/udpa/', - 'node_modules/protobufjs/', - 'deps/googleapis/', - 'deps/protoc-gen-validate/' - ], - grpcLib: '../index', - 'grpc-lib': '../index', - '$0': 'node_modules/.bin/proto-loader-gen-types' -} -Processing envoy/service/discovery/v2/ads.proto -Writing src/generated//ads.d.ts -Writing src/generated//envoy/service/discovery/v2/AdsDummy.d.ts from file deps/envoy-api/envoy/service/discovery/v2/ads.proto -Writing src/generated//envoy/api/v2/DiscoveryRequest.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto -Writing src/generated//envoy/api/v2/DiscoveryResponse.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto -Writing src/generated//envoy/api/v2/DeltaDiscoveryRequest.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto -Writing src/generated//envoy/api/v2/DeltaDiscoveryResponse.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto -Writing src/generated//envoy/api/v2/Resource.d.ts from file deps/envoy-api/envoy/api/v2/discovery.proto -Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto -Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto -Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto -Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//google/protobuf/Any.d.ts from file null -Writing src/generated//google/protobuf/Duration.d.ts from file null -Writing src/generated//google/protobuf/Struct.d.ts from file null -Writing src/generated//google/protobuf/Value.d.ts from file null -Writing src/generated//google/protobuf/NullValue.d.ts from file null -Writing src/generated//google/protobuf/ListValue.d.ts from file null -Writing src/generated//google/protobuf/DoubleValue.d.ts from file null -Writing src/generated//google/protobuf/FloatValue.d.ts from file null -Writing src/generated//google/protobuf/Int64Value.d.ts from file null -Writing src/generated//google/protobuf/UInt64Value.d.ts from file null -Writing src/generated//google/protobuf/Int32Value.d.ts from file null -Writing src/generated//google/protobuf/UInt32Value.d.ts from file null -Writing src/generated//google/protobuf/BoolValue.d.ts from file null -Writing src/generated//google/protobuf/StringValue.d.ts from file null -Writing src/generated//google/protobuf/BytesValue.d.ts from file null -Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/Timestamp.d.ts from file null -Writing src/generated//google/rpc/Status.d.ts from file deps/googleapis/google/rpc/status.proto -Processing envoy/api/v2/listener.proto -Writing src/generated//listener.d.ts -Writing src/generated//envoy/api/v2/Listener.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto -Writing src/generated//envoy/api/v2/Listener/DrainType.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto -Writing src/generated//envoy/api/v2/Listener/DeprecatedV1.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto -Writing src/generated//envoy/api/v2/Listener/ConnectionBalanceConfig.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto -Writing src/generated//envoy/api/v2/Listener/ConnectionBalanceConfig/ExactBalance.d.ts from file deps/envoy-api/envoy/api/v2/listener.proto -Writing src/generated//envoy/api/v2/listener/Filter.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto -Writing src/generated//envoy/api/v2/listener/FilterChainMatch.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto -Writing src/generated//envoy/api/v2/listener/FilterChainMatch/ConnectionSourceType.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto -Writing src/generated//envoy/api/v2/listener/FilterChain.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto -Writing src/generated//envoy/api/v2/listener/ListenerFilterChainMatchPredicate.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto -Writing src/generated//envoy/api/v2/listener/ListenerFilterChainMatchPredicate/MatchSet.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto -Writing src/generated//envoy/api/v2/listener/ListenerFilter.d.ts from file deps/envoy-api/envoy/api/v2/listener/listener_components.proto -Writing src/generated//envoy/api/v2/listener/UdpListenerConfig.d.ts from file deps/envoy-api/envoy/api/v2/listener/udp_listener_config.proto -Writing src/generated//envoy/api/v2/listener/ActiveRawUdpListenerConfig.d.ts from file deps/envoy-api/envoy/api/v2/listener/udp_listener_config.proto -Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto -Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto -Writing src/generated//envoy/api/v2/core/ApiVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ApiConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ApiConfigSource/ApiType.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/AggregatedConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/SelfConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/RateLimitSettings.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/auth/UpstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/DownstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/CommonTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/CommonTlsContext/CombinedCertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/GenericSecret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto -Writing src/generated//envoy/api/v2/auth/SdsSecretConfig.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto -Writing src/generated//envoy/api/v2/auth/Secret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto -Writing src/generated//envoy/api/v2/auth/TlsParameters.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/TlsParameters/TlsProtocol.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/PrivateKeyProvider.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/TlsCertificate.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/TlsSessionTicketKeys.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/CertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/CertificateValidationContext/TrustChainVerification.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/route/VirtualHost.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/VirtualHost/TlsRequirementType.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/FilterAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/Route.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/WeightedCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/WeightedCluster/ClusterWeight.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteMatch/GrpcRouteMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteMatch/TlsContextMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/CorsPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/ClusterNotFoundResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/InternalRedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/RequestMirrorPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Header.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Cookie.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/ConnectionProperties.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/QueryParameter.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/FilterState.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/UpgradeConfig.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryPriority.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryHostPredicate.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryBackOff.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/HedgePolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RedirectAction/RedirectResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/DirectResponseAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/Decorator.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/Tracing.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/VirtualCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/SourceCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/DestinationCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/RequestHeaders.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/RemoteAddress.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/GenericKey.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/HeaderValueMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/HeaderMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/QueryParameterMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/config/listener/v2/ApiListener.d.ts from file deps/envoy-api/envoy/config/listener/v2/api_listener.proto -Writing src/generated//envoy/config/filter/accesslog/v2/AccessLog.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/AccessLogFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/ComparisonFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/ComparisonFilter/Op.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/StatusCodeFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/DurationFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/NotHealthCheckFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/TraceableFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/RuntimeFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/AndFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/OrFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/HeaderFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/ResponseFlagFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/GrpcStatusFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/GrpcStatusFilter/Status.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/config/filter/accesslog/v2/ExtensionFilter.d.ts from file deps/envoy-api/envoy/config/filter/accesslog/v2/accesslog.proto -Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto -Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Literal.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Environment.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Header.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Metadata.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKey.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKey/PathSegment.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Request.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Route.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Cluster.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Host.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//google/protobuf/Duration.d.ts from file null -Writing src/generated//google/protobuf/DoubleValue.d.ts from file null -Writing src/generated//google/protobuf/FloatValue.d.ts from file null -Writing src/generated//google/protobuf/Int64Value.d.ts from file null -Writing src/generated//google/protobuf/UInt64Value.d.ts from file null -Writing src/generated//google/protobuf/Int32Value.d.ts from file null -Writing src/generated//google/protobuf/UInt32Value.d.ts from file null -Writing src/generated//google/protobuf/BoolValue.d.ts from file null -Writing src/generated//google/protobuf/StringValue.d.ts from file null -Writing src/generated//google/protobuf/BytesValue.d.ts from file null -Writing src/generated//google/protobuf/Any.d.ts from file null -Writing src/generated//google/protobuf/Struct.d.ts from file null -Writing src/generated//google/protobuf/Value.d.ts from file null -Writing src/generated//google/protobuf/NullValue.d.ts from file null -Writing src/generated//google/protobuf/ListValue.d.ts from file null -Writing src/generated//google/protobuf/Timestamp.d.ts from file null -Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/Empty.d.ts from file null -Writing src/generated//google/api/Http.d.ts from file node_modules/protobufjs/google/api/http.proto -Writing src/generated//google/api/HttpRule.d.ts from file node_modules/protobufjs/google/api/http.proto -Writing src/generated//google/api/CustomHttpPattern.d.ts from file node_modules/protobufjs/google/api/http.proto -Processing envoy/api/v2/route.proto -Writing src/generated//route.d.ts -Writing src/generated//envoy/api/v2/RouteConfiguration.d.ts from file deps/envoy-api/envoy/api/v2/route.proto -Writing src/generated//envoy/api/v2/Vhds.d.ts from file deps/envoy-api/envoy/api/v2/route.proto -Writing src/generated//envoy/api/v2/core/ApiVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ApiConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ApiConfigSource/ApiType.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/AggregatedConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/SelfConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/RateLimitSettings.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto -Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto -Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/route/VirtualHost.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/VirtualHost/TlsRequirementType.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/FilterAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/Route.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/WeightedCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/WeightedCluster/ClusterWeight.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteMatch/GrpcRouteMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteMatch/TlsContextMatchOptions.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/CorsPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/ClusterNotFoundResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/InternalRedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/RequestMirrorPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Header.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/Cookie.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/ConnectionProperties.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/QueryParameter.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/HashPolicy/FilterState.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RouteAction/UpgradeConfig.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryPriority.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryHostPredicate.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RetryPolicy/RetryBackOff.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/HedgePolicy.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RedirectAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RedirectAction/RedirectResponseCode.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/DirectResponseAction.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/Decorator.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/Tracing.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/VirtualCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/SourceCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/DestinationCluster.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/RequestHeaders.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/RemoteAddress.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/GenericKey.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/RateLimit/Action/HeaderValueMatch.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/HeaderMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/api/v2/route/QueryParameterMatcher.d.ts from file deps/envoy-api/envoy/api/v2/route/route_components.proto -Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Literal.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Environment.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Header.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/tracing/v2/CustomTag/Metadata.d.ts from file deps/envoy-api/envoy/type/tracing/v2/custom_tag.proto -Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto -Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKey.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKey/PathSegment.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Request.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Route.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Cluster.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//envoy/type/metadata/v2/MetadataKind/Host.d.ts from file deps/envoy-api/envoy/type/metadata/v2/metadata.proto -Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//google/protobuf/DoubleValue.d.ts from file null -Writing src/generated//google/protobuf/FloatValue.d.ts from file null -Writing src/generated//google/protobuf/Int64Value.d.ts from file null -Writing src/generated//google/protobuf/UInt64Value.d.ts from file null -Writing src/generated//google/protobuf/Int32Value.d.ts from file null -Writing src/generated//google/protobuf/UInt32Value.d.ts from file null -Writing src/generated//google/protobuf/BoolValue.d.ts from file null -Writing src/generated//google/protobuf/StringValue.d.ts from file null -Writing src/generated//google/protobuf/BytesValue.d.ts from file null -Writing src/generated//google/protobuf/Duration.d.ts from file null -Writing src/generated//google/protobuf/Timestamp.d.ts from file null -Writing src/generated//google/protobuf/Any.d.ts from file null -Writing src/generated//google/protobuf/Struct.d.ts from file null -Writing src/generated//google/protobuf/Value.d.ts from file null -Writing src/generated//google/protobuf/NullValue.d.ts from file null -Writing src/generated//google/protobuf/ListValue.d.ts from file null -Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/Empty.d.ts from file null -Processing envoy/api/v2/cluster.proto -Writing src/generated//cluster.d.ts -Writing src/generated//envoy/api/v2/Cluster.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/DiscoveryType.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/LbPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/DnsLookupFamily.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/ClusterProtocolSelection.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/TransportSocketMatch.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/CustomClusterType.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/EdsClusterConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig/LbSubsetFallbackPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig/LbSubsetSelector.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/LbSubsetConfig/LbSubsetSelector/LbSubsetSelectorFallbackPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/LeastRequestLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/RingHashLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/RingHashLbConfig/HashFunction.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/OriginalDstLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig/ZoneAwareLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig/LocalityWeightedLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/CommonLbConfig/ConsistentHashingLbConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/Cluster/RefreshRate.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/LoadBalancingPolicy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/LoadBalancingPolicy/Policy.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/UpstreamBindConfig.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/UpstreamConnectionOptions.d.ts from file deps/envoy-api/envoy/api/v2/cluster.proto -Writing src/generated//envoy/api/v2/auth/UpstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/DownstreamTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/CommonTlsContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/CommonTlsContext/CombinedCertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/tls.proto -Writing src/generated//envoy/api/v2/auth/TlsParameters.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/TlsParameters/TlsProtocol.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/PrivateKeyProvider.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/TlsCertificate.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/TlsSessionTicketKeys.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/CertificateValidationContext.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/CertificateValidationContext/TrustChainVerification.d.ts from file deps/envoy-api/envoy/api/v2/auth/common.proto -Writing src/generated//envoy/api/v2/auth/GenericSecret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto -Writing src/generated//envoy/api/v2/auth/SdsSecretConfig.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto -Writing src/generated//envoy/api/v2/auth/Secret.d.ts from file deps/envoy-api/envoy/api/v2/auth/secret.proto -Writing src/generated//envoy/api/v2/core/ApiVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ApiConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ApiConfigSource/ApiType.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/AggregatedConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/SelfConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/RateLimitSettings.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/ConfigSource.d.ts from file deps/envoy-api/envoy/api/v2/core/config_source.proto -Writing src/generated//envoy/api/v2/core/HealthStatus.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/Payload.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/HttpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/TcpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/RedisHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/GrpcHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/CustomHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/TlsOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/TcpProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/UpstreamHttpProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/HttpProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/HttpProtocolOptions/HeadersWithUnderscoresAction.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/Http1ProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/Http1ProtocolOptions/HeaderKeyFormat.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/Http1ProtocolOptions/HeaderKeyFormat/ProperCaseWords.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/Http2ProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/Http2ProtocolOptions/SettingsParameter.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/GrpcProtocolOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/protocol.proto -Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto -Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto -Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/EventServiceConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/event_service_config.proto -Writing src/generated//envoy/api/v2/cluster/CircuitBreakers.d.ts from file deps/envoy-api/envoy/api/v2/cluster/circuit_breaker.proto -Writing src/generated//envoy/api/v2/cluster/CircuitBreakers/Thresholds.d.ts from file deps/envoy-api/envoy/api/v2/cluster/circuit_breaker.proto -Writing src/generated//envoy/api/v2/cluster/CircuitBreakers/Thresholds/RetryBudget.d.ts from file deps/envoy-api/envoy/api/v2/cluster/circuit_breaker.proto -Writing src/generated//envoy/api/v2/cluster/Filter.d.ts from file deps/envoy-api/envoy/api/v2/cluster/filter.proto -Writing src/generated//envoy/api/v2/cluster/OutlierDetection.d.ts from file deps/envoy-api/envoy/api/v2/cluster/outlier_detection.proto -Writing src/generated//envoy/api/v2/ClusterLoadAssignment.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto -Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto -Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy/DropOverload.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto -Writing src/generated//envoy/api/v2/endpoint/Endpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/api/v2/endpoint/Endpoint/HealthCheckConfig.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/api/v2/endpoint/LbEndpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/api/v2/endpoint/LocalityLbEndpoints.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/CodecClientType.d.ts from file deps/envoy-api/envoy/type/http.proto -Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto -Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//google/protobuf/Any.d.ts from file null -Writing src/generated//google/protobuf/Duration.d.ts from file null -Writing src/generated//google/protobuf/Struct.d.ts from file null -Writing src/generated//google/protobuf/Value.d.ts from file null -Writing src/generated//google/protobuf/NullValue.d.ts from file null -Writing src/generated//google/protobuf/ListValue.d.ts from file null -Writing src/generated//google/protobuf/DoubleValue.d.ts from file null -Writing src/generated//google/protobuf/FloatValue.d.ts from file null -Writing src/generated//google/protobuf/Int64Value.d.ts from file null -Writing src/generated//google/protobuf/UInt64Value.d.ts from file null -Writing src/generated//google/protobuf/Int32Value.d.ts from file null -Writing src/generated//google/protobuf/UInt32Value.d.ts from file null -Writing src/generated//google/protobuf/BoolValue.d.ts from file null -Writing src/generated//google/protobuf/StringValue.d.ts from file null -Writing src/generated//google/protobuf/BytesValue.d.ts from file null -Writing src/generated//google/protobuf/Timestamp.d.ts from file null -Writing src/generated//google/protobuf/Empty.d.ts from file null -Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/api/Http.d.ts from file node_modules/protobufjs/google/api/http.proto -Writing src/generated//google/api/HttpRule.d.ts from file node_modules/protobufjs/google/api/http.proto -Writing src/generated//google/api/CustomHttpPattern.d.ts from file node_modules/protobufjs/google/api/http.proto -Processing envoy/api/v2/endpoint.proto -Writing src/generated//endpoint.d.ts -Writing src/generated//envoy/api/v2/ClusterLoadAssignment.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto -Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto -Writing src/generated//envoy/api/v2/ClusterLoadAssignment/Policy/DropOverload.d.ts from file deps/envoy-api/envoy/api/v2/endpoint.proto -Writing src/generated//envoy/api/v2/endpoint/Endpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/api/v2/endpoint/Endpoint/HealthCheckConfig.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/api/v2/endpoint/LbEndpoint.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/api/v2/endpoint/LocalityLbEndpoints.d.ts from file deps/envoy-api/envoy/api/v2/endpoint/endpoint_components.proto -Writing src/generated//envoy/api/v2/core/RoutingPriority.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RequestMethod.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TrafficDirection.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Locality.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/BuildVersion.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Extension.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Node.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Metadata.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeUInt32.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeDouble.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFeatureFlag.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValue.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderValueOption.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/HeaderMap.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/DataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RetryPolicy.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RemoteDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/AsyncDataSource.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/TransportSocket.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/RuntimeFractionalPercent.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/ControlPlane.d.ts from file deps/envoy-api/envoy/api/v2/core/base.proto -Writing src/generated//envoy/api/v2/core/Pipe.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/SocketAddress/Protocol.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/TcpKeepalive.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/BindConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/Address.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/CidrRange.d.ts from file deps/envoy-api/envoy/api/v2/core/address.proto -Writing src/generated//envoy/api/v2/core/HealthStatus.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/Payload.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/HttpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/TcpHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/RedisHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/GrpcHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/CustomHealthCheck.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/HealthCheck/TlsOptions.d.ts from file deps/envoy-api/envoy/api/v2/core/health_check.proto -Writing src/generated//envoy/api/v2/core/BackoffStrategy.d.ts from file deps/envoy-api/envoy/api/v2/core/backoff.proto -Writing src/generated//envoy/api/v2/core/HttpUri.d.ts from file deps/envoy-api/envoy/api/v2/core/http_uri.proto -Writing src/generated//envoy/api/v2/core/SocketOption.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/SocketOption/SocketState.d.ts from file deps/envoy-api/envoy/api/v2/core/socket_option.proto -Writing src/generated//envoy/api/v2/core/EventServiceConfig.d.ts from file deps/envoy-api/envoy/api/v2/core/event_service_config.proto -Writing src/generated//envoy/api/v2/core/GrpcService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/EnvoyGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/SslCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/GoogleLocalCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/ChannelCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/ServiceAccountJWTAccessCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/GoogleIAMCredentials.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/MetadataCredentialsFromPlugin.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/api/v2/core/GrpcService/GoogleGrpc/CallCredentials/StsService.d.ts from file deps/envoy-api/envoy/api/v2/core/grpc_service.proto -Writing src/generated//envoy/type/Percent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/FractionalPercent/DenominatorType.d.ts from file deps/envoy-api/envoy/type/percent.proto -Writing src/generated//envoy/type/matcher/StringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/matcher/ListStringMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/string.proto -Writing src/generated//envoy/type/matcher/RegexMatcher.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatcher/GoogleRE2.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/matcher/RegexMatchAndSubstitute.d.ts from file deps/envoy-api/envoy/type/matcher/regex.proto -Writing src/generated//envoy/type/SemanticVersion.d.ts from file deps/envoy-api/envoy/type/semantic_version.proto -Writing src/generated//envoy/type/CodecClientType.d.ts from file deps/envoy-api/envoy/type/http.proto -Writing src/generated//envoy/type/Int64Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/Int32Range.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//envoy/type/DoubleRange.d.ts from file deps/envoy-api/envoy/type/range.proto -Writing src/generated//udpa/annotations/MigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FieldMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/FileMigrateAnnotation.d.ts from file deps/udpa/udpa/annotations/migrate.proto -Writing src/generated//udpa/annotations/PackageVersionStatus.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//udpa/annotations/StatusAnnotation.d.ts from file deps/udpa/udpa/annotations/status.proto -Writing src/generated//validate/FieldRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/FloatRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DoubleRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Int64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/UInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SInt64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/Fixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed32Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/SFixed64Rules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BoolRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/StringRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/KnownRegex.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/BytesRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/EnumRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MessageRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/RepeatedRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/MapRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/AnyRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/DurationRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//validate/TimestampRules.d.ts from file deps/protoc-gen-validate/validate/validate.proto -Writing src/generated//google/protobuf/Duration.d.ts from file null -Writing src/generated//google/protobuf/DoubleValue.d.ts from file null -Writing src/generated//google/protobuf/FloatValue.d.ts from file null -Writing src/generated//google/protobuf/Int64Value.d.ts from file null -Writing src/generated//google/protobuf/UInt64Value.d.ts from file null -Writing src/generated//google/protobuf/Int32Value.d.ts from file null -Writing src/generated//google/protobuf/UInt32Value.d.ts from file null -Writing src/generated//google/protobuf/BoolValue.d.ts from file null -Writing src/generated//google/protobuf/StringValue.d.ts from file null -Writing src/generated//google/protobuf/BytesValue.d.ts from file null -Writing src/generated//google/protobuf/Timestamp.d.ts from file null -Writing src/generated//google/protobuf/FileDescriptorSet.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ExtensionRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/DescriptorProto/ReservedRange.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Type.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldDescriptorProto/Label.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodDescriptorProto.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FileOptions/OptimizeMode.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MessageOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/CType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/FieldOptions/JSType.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/OneofOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/EnumValueOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/ServiceOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/MethodOptions.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/UninterpretedOption/NamePart.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/SourceCodeInfo/Location.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/GeneratedCodeInfo/Annotation.d.ts from file node_modules/protobufjs/google/protobuf/descriptor.proto -Writing src/generated//google/protobuf/Any.d.ts from file null -Writing src/generated//google/protobuf/Struct.d.ts from file null -Writing src/generated//google/protobuf/Value.d.ts from file null -Writing src/generated//google/protobuf/NullValue.d.ts from file null -Writing src/generated//google/protobuf/ListValue.d.ts from file null -Writing src/generated//google/protobuf/Empty.d.ts from file null -Writing src/generated//google/api/Http.d.ts from file node_modules/protobufjs/google/api/http.proto -Writing src/generated//google/api/HttpRule.d.ts from file node_modules/protobufjs/google/api/http.proto -Writing src/generated//google/api/CustomHttpPattern.d.ts from file node_modules/protobufjs/google/api/http.proto -Success From 3bf2af1d70117f9f0ced8758b25e27a30776ae14 Mon Sep 17 00:00:00 2001 From: Dan Rumney Date: Thu, 15 Jun 2023 13:03:26 -0500 Subject: [PATCH 495/694] docs(apache-notice.md): add a notice acknowledging the use of GTS config settings This might actually be unnecessary; since I've copied over configuration settings from the GTS package, I figured I'd add this notice. It's in a file, since there's no capacity for adding comments in a JSON or .rc file. It feels doubtful that configuration settings fall under the auspices of the Apache License, but I'll leave that to the maintainers to decide. --- packages/grpc-js/apache-notice.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/grpc-js/apache-notice.md diff --git a/packages/grpc-js/apache-notice.md b/packages/grpc-js/apache-notice.md new file mode 100644 index 000000000..703afce80 --- /dev/null +++ b/packages/grpc-js/apache-notice.md @@ -0,0 +1,4 @@ +The following files contain configuration settings that were derived from [the this commit](https://github.com/google/gts/commit/3b9ab6dd59691f77f5c5c632a44c6762ba4ef7c6) in the Google GTS repository: + - .eslintrc + - prettier.config.js + - tsconfig.json From cd24d6956d19015c58d661faca1155ee76428314 Mon Sep 17 00:00:00 2001 From: Dan Rumney Date: Thu, 15 Jun 2023 13:04:57 -0500 Subject: [PATCH 496/694] style: run eslint fix on codebase Fixes #2464 --- packages/grpc-js/src/admin.ts | 20 +- packages/grpc-js/src/call-credentials.ts | 6 +- packages/grpc-js/src/call-interface.ts | 22 +- packages/grpc-js/src/call-number.ts | 4 +- packages/grpc-js/src/call.ts | 25 +- packages/grpc-js/src/channel-credentials.ts | 46 +- packages/grpc-js/src/channel.ts | 17 +- packages/grpc-js/src/channelz.ts | 452 +++++++----- packages/grpc-js/src/client-interceptors.ts | 38 +- packages/grpc-js/src/client.ts | 140 ++-- .../grpc-js/src/compression-algorithms.ts | 4 +- packages/grpc-js/src/compression-filter.ts | 63 +- packages/grpc-js/src/control-plane-status.ts | 17 +- packages/grpc-js/src/deadline.ts | 8 +- packages/grpc-js/src/duration.ts | 6 +- packages/grpc-js/src/error.ts | 2 +- packages/grpc-js/src/experimental.ts | 14 +- packages/grpc-js/src/filter-stack.ts | 2 +- packages/grpc-js/src/http_proxy.ts | 24 +- packages/grpc-js/src/index.ts | 17 +- packages/grpc-js/src/internal-channel.ts | 244 +++++-- .../src/load-balancer-child-handler.ts | 7 +- .../src/load-balancer-outlier-detection.ts | 446 +++++++++--- .../grpc-js/src/load-balancer-pick-first.ts | 30 +- .../grpc-js/src/load-balancer-round-robin.ts | 25 +- packages/grpc-js/src/load-balancer.ts | 28 +- packages/grpc-js/src/load-balancing-call.ts | 301 +++++--- packages/grpc-js/src/logging.ts | 7 +- packages/grpc-js/src/make-client.ts | 2 +- .../grpc-js/src/max-message-size-filter.ts | 11 +- packages/grpc-js/src/metadata.ts | 17 +- packages/grpc-js/src/object-stream.ts | 22 +- packages/grpc-js/src/picker.ts | 4 +- packages/grpc-js/src/resolver-dns.ts | 29 +- packages/grpc-js/src/resolving-call.ts | 193 +++-- .../grpc-js/src/resolving-load-balancer.ts | 26 +- packages/grpc-js/src/retrying-call.ts | 308 ++++++-- packages/grpc-js/src/server-call.ts | 63 +- packages/grpc-js/src/server.ts | 484 ++++++++----- packages/grpc-js/src/service-config.ts | 145 +++- packages/grpc-js/src/subchannel-address.ts | 13 +- packages/grpc-js/src/subchannel-call.ts | 37 +- packages/grpc-js/src/subchannel-interface.ts | 10 +- packages/grpc-js/src/subchannel-pool.ts | 2 +- packages/grpc-js/src/subchannel.ts | 161 +++-- packages/grpc-js/src/transport.ts | 266 ++++--- .../grpc-js/test/test-call-credentials.ts | 37 +- .../grpc-js/test/test-call-propagation.ts | 244 ++++--- .../grpc-js/test/test-channel-credentials.ts | 70 +- packages/grpc-js/test/test-channelz.ts | 679 ++++++++++++------ packages/grpc-js/test/test-client.ts | 79 +- packages/grpc-js/test/test-deadline.ts | 55 +- .../test/test-global-subchannel-pool.ts | 97 ++- .../test/test-local-subchannel-pool.ts | 14 +- .../grpc-js/test/test-outlier-detection.ts | 237 +++--- .../grpc-js/test/test-prototype-pollution.ts | 8 +- packages/grpc-js/test/test-resolver.ts | 268 ++++--- packages/grpc-js/test/test-retry-config.ts | 176 +++-- packages/grpc-js/test/test-retry.ts | 192 +++-- .../grpc-js/test/test-server-deadlines.ts | 6 +- packages/grpc-js/test/test-server-errors.ts | 5 +- packages/grpc-js/test/test-server.ts | 168 +++-- packages/grpc-js/test/test-uri-parser.ts | 148 +++- 63 files changed, 4096 insertions(+), 2195 deletions(-) diff --git a/packages/grpc-js/src/admin.ts b/packages/grpc-js/src/admin.ts index 7745d07d8..4d26b89bd 100644 --- a/packages/grpc-js/src/admin.ts +++ b/packages/grpc-js/src/admin.ts @@ -15,8 +15,8 @@ * */ -import { ServiceDefinition } from "./make-client"; -import { Server, UntypedServiceImplementation } from "./server"; +import { ServiceDefinition } from './make-client'; +import { Server, UntypedServiceImplementation } from './server'; interface GetServiceDefinition { (): ServiceDefinition; @@ -26,14 +26,20 @@ interface GetHandlers { (): UntypedServiceImplementation; } -const registeredAdminServices: {getServiceDefinition: GetServiceDefinition, getHandlers: GetHandlers}[] = []; +const registeredAdminServices: { + getServiceDefinition: GetServiceDefinition; + getHandlers: GetHandlers; +}[] = []; -export function registerAdminService(getServiceDefinition: GetServiceDefinition, getHandlers: GetHandlers) { - registeredAdminServices.push({getServiceDefinition, getHandlers}); +export function registerAdminService( + getServiceDefinition: GetServiceDefinition, + getHandlers: GetHandlers +) { + registeredAdminServices.push({ getServiceDefinition, getHandlers }); } export function addAdminServicesToServer(server: Server): void { - for (const {getServiceDefinition, getHandlers} of registeredAdminServices) { + for (const { getServiceDefinition, getHandlers } of registeredAdminServices) { server.addService(getServiceDefinition(), getHandlers()); } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/call-credentials.ts b/packages/grpc-js/src/call-credentials.ts index b98624ee2..b0013eeae 100644 --- a/packages/grpc-js/src/call-credentials.ts +++ b/packages/grpc-js/src/call-credentials.ts @@ -125,14 +125,14 @@ export abstract class CallCredentials { }); } getHeaders.then( - (headers) => { + headers => { const metadata = new Metadata(); for (const key of Object.keys(headers)) { metadata.add(key, headers[key]); } callback(null, metadata); }, - (err) => { + err => { callback(err); } ); @@ -152,7 +152,7 @@ class ComposedCallCredentials extends CallCredentials { async generateMetadata(options: CallMetadataOptions): Promise { const base: Metadata = new Metadata(); const generated: Metadata[] = await Promise.all( - this.creds.map((cred) => cred.generateMetadata(options)) + this.creds.map(cred => cred.generateMetadata(options)) ); for (const gen of generated) { base.merge(gen); diff --git a/packages/grpc-js/src/call-interface.ts b/packages/grpc-js/src/call-interface.ts index 283cb9b54..15035aeac 100644 --- a/packages/grpc-js/src/call-interface.ts +++ b/packages/grpc-js/src/call-interface.ts @@ -15,11 +15,11 @@ * */ -import { CallCredentials } from "./call-credentials"; -import { Status } from "./constants"; -import { Deadline } from "./deadline"; -import { Metadata } from "./metadata"; -import { ServerSurfaceCall } from "./server-call"; +import { CallCredentials } from './call-credentials'; +import { Status } from './constants'; +import { Deadline } from './deadline'; +import { Metadata } from './metadata'; +import { ServerSurfaceCall } from './server-call'; export interface CallStreamOptions { deadline: Deadline; @@ -38,7 +38,7 @@ export interface StatusObject { export type PartialStatusObject = Pick & { metadata: Metadata | null; -} +}; export const enum WriteFlags { BufferHint = 1, @@ -118,7 +118,7 @@ export class InterceptingListenerImpl implements InterceptingListener { onReceiveMetadata(metadata: Metadata): void { this.processingMetadata = true; - this.listener.onReceiveMetadata(metadata, (metadata) => { + this.listener.onReceiveMetadata(metadata, metadata => { this.processingMetadata = false; this.nextListener.onReceiveMetadata(metadata); this.processPendingMessage(); @@ -128,9 +128,9 @@ export class InterceptingListenerImpl implements InterceptingListener { // eslint-disable-next-line @typescript-eslint/no-explicit-any onReceiveMessage(message: any): void { /* If this listener processes messages asynchronously, the last message may - * be reordered with respect to the status */ + * be reordered with respect to the status */ this.processingMessage = true; - this.listener.onReceiveMessage(message, (msg) => { + this.listener.onReceiveMessage(message, msg => { this.processingMessage = false; if (this.processingMetadata) { this.pendingMessage = msg; @@ -142,7 +142,7 @@ export class InterceptingListenerImpl implements InterceptingListener { }); } onReceiveStatus(status: StatusObject): void { - this.listener.onReceiveStatus(status, (processedStatus) => { + this.listener.onReceiveStatus(status, processedStatus => { if (this.processingMetadata || this.processingMessage) { this.pendingStatus = processedStatus; } else { @@ -170,4 +170,4 @@ export interface Call { halfClose(): void; getCallNumber(): number; setCredentials(credentials: CallCredentials): void; -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/call-number.ts b/packages/grpc-js/src/call-number.ts index 48d34fac5..8c37d3f91 100644 --- a/packages/grpc-js/src/call-number.ts +++ b/packages/grpc-js/src/call-number.ts @@ -18,5 +18,5 @@ let nextCallNumber = 0; export function getNextCallNumber() { - return nextCallNumber++; -} \ No newline at end of file + return nextCallNumber++; +} diff --git a/packages/grpc-js/src/call.ts b/packages/grpc-js/src/call.ts index bb6d74ccf..a147c98bc 100644 --- a/packages/grpc-js/src/call.ts +++ b/packages/grpc-js/src/call.ts @@ -65,10 +65,8 @@ export type ClientWritableStream = { /** * A type representing the return value of a bidirectional stream method call. */ -export type ClientDuplexStream< - RequestType, - ResponseType -> = ClientWritableStream & ClientReadableStream; +export type ClientDuplexStream = + ClientWritableStream & ClientReadableStream; /** * Construct a ServiceError from a StatusObject. This function exists primarily @@ -76,16 +74,20 @@ export type ClientDuplexStream< * error is not necessarily a problem in gRPC itself. * @param status */ -export function callErrorFromStatus(status: StatusObject, callerStack: string): ServiceError { +export function callErrorFromStatus( + status: StatusObject, + callerStack: string +): ServiceError { const message = `${status.code} ${Status[status.code]}: ${status.details}`; const error = new Error(message); const stack = `${error.stack}\nfor call at\n${callerStack}`; - return Object.assign(new Error(message), status, {stack}); + return Object.assign(new Error(message), status, { stack }); } export class ClientUnaryCallImpl extends EventEmitter - implements ClientUnaryCall { + implements ClientUnaryCall +{ public call?: InterceptingCallInterface; constructor() { super(); @@ -102,7 +104,8 @@ export class ClientUnaryCallImpl export class ClientReadableStreamImpl extends Readable - implements ClientReadableStream { + implements ClientReadableStream +{ public call?: InterceptingCallInterface; constructor(readonly deserialize: (chunk: Buffer) => ResponseType) { super({ objectMode: true }); @@ -123,7 +126,8 @@ export class ClientReadableStreamImpl export class ClientWritableStreamImpl extends Writable - implements ClientWritableStream { + implements ClientWritableStream +{ public call?: InterceptingCallInterface; constructor(readonly serialize: (value: RequestType) => Buffer) { super({ objectMode: true }); @@ -156,7 +160,8 @@ export class ClientWritableStreamImpl export class ClientDuplexStreamImpl extends Duplex - implements ClientDuplexStream { + implements ClientDuplexStream +{ public call?: InterceptingCallInterface; constructor( readonly serialize: (value: RequestType) => Buffer, diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index fd9d7b571..dc6069c84 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -15,7 +15,12 @@ * */ -import { ConnectionOptions, createSecureContext, PeerCertificate, SecureContext } from 'tls'; +import { + ConnectionOptions, + createSecureContext, + PeerCertificate, + SecureContext, +} from 'tls'; import { CallCredentials } from './call-credentials'; import { CIPHER_SUITES, getDefaultRootsData } from './tls-helpers'; @@ -137,10 +142,7 @@ export abstract class ChannelCredentials { cert: certChain ?? undefined, ciphers: CIPHER_SUITES, }); - return new SecureChannelCredentialsImpl( - secureContext, - verifyOptions ?? {} - ); + return new SecureChannelCredentialsImpl(secureContext, verifyOptions ?? {}); } /** @@ -153,11 +155,11 @@ export abstract class ChannelCredentials { * @param secureContext The return value of tls.createSecureContext() * @param verifyOptions Additional options to modify certificate verification */ - static createFromSecureContext(secureContext: SecureContext, verifyOptions?: VerifyOptions): ChannelCredentials { - return new SecureChannelCredentialsImpl( - secureContext, - verifyOptions ?? {} - ) + static createFromSecureContext( + secureContext: SecureContext, + verifyOptions?: VerifyOptions + ): ChannelCredentials { + return new SecureChannelCredentialsImpl(secureContext, verifyOptions ?? {}); } /** @@ -196,19 +198,19 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { private verifyOptions: VerifyOptions ) { super(); - this.connectionOptions = { - secureContext + this.connectionOptions = { + secureContext, }; // Node asserts that this option is a function, so we cannot pass undefined if (verifyOptions?.checkServerIdentity) { - this.connectionOptions.checkServerIdentity = verifyOptions.checkServerIdentity; + this.connectionOptions.checkServerIdentity = + verifyOptions.checkServerIdentity; } } compose(callCredentials: CallCredentials): ChannelCredentials { - const combinedCallCredentials = this.callCredentials.compose( - callCredentials - ); + const combinedCallCredentials = + this.callCredentials.compose(callCredentials); return new ComposedChannelCredentialsImpl(this, combinedCallCredentials); } @@ -225,9 +227,10 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { } if (other instanceof SecureChannelCredentialsImpl) { return ( - this.secureContext === other.secureContext && - this.verifyOptions.checkServerIdentity === other.verifyOptions.checkServerIdentity - ); + this.secureContext === other.secureContext && + this.verifyOptions.checkServerIdentity === + other.verifyOptions.checkServerIdentity + ); } else { return false; } @@ -242,9 +245,8 @@ class ComposedChannelCredentialsImpl extends ChannelCredentials { super(callCreds); } compose(callCredentials: CallCredentials) { - const combinedCallCredentials = this.callCredentials.compose( - callCredentials - ); + const combinedCallCredentials = + this.callCredentials.compose(callCredentials); return new ComposedChannelCredentialsImpl( this.channelCredentials, combinedCallCredentials diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index aaf14bb22..7ce5a15f7 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -91,7 +91,6 @@ export interface Channel { } export class ChannelImplementation implements Channel { - private internalChannel: InternalChannel; constructor( @@ -133,13 +132,17 @@ export class ChannelImplementation implements Channel { deadline: Date | number, callback: (error?: Error) => void ): void { - this.internalChannel.watchConnectivityState(currentState, deadline, callback); + this.internalChannel.watchConnectivityState( + currentState, + deadline, + callback + ); } /** * Get the channelz reference object for this channel. The returned value is * garbage if channelz is disabled for this channel. - * @returns + * @returns */ getChannelzRef() { return this.internalChannel.getChannelzRef(); @@ -160,6 +163,12 @@ export class ChannelImplementation implements Channel { 'Channel#createCall: deadline must be a number or Date' ); } - return this.internalChannel.createCall(method, deadline, host, parentCall, propagateFlags); + return this.internalChannel.createCall( + method, + deadline, + host, + parentCall, + propagateFlags + ); } } diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 5a7a54760..1e2627a97 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -15,45 +15,55 @@ * */ -import { isIPv4, isIPv6 } from "net"; -import { ConnectivityState } from "./connectivity-state"; -import { Status } from "./constants"; -import { Timestamp } from "./generated/google/protobuf/Timestamp"; -import { Channel as ChannelMessage } from "./generated/grpc/channelz/v1/Channel"; -import { ChannelConnectivityState__Output } from "./generated/grpc/channelz/v1/ChannelConnectivityState"; -import { ChannelRef as ChannelRefMessage } from "./generated/grpc/channelz/v1/ChannelRef"; -import { ChannelTrace } from "./generated/grpc/channelz/v1/ChannelTrace"; -import { GetChannelRequest__Output } from "./generated/grpc/channelz/v1/GetChannelRequest"; -import { GetChannelResponse } from "./generated/grpc/channelz/v1/GetChannelResponse"; -import { sendUnaryData, ServerUnaryCall } from "./server-call"; -import { ServerRef as ServerRefMessage } from "./generated/grpc/channelz/v1/ServerRef"; -import { SocketRef as SocketRefMessage } from "./generated/grpc/channelz/v1/SocketRef"; -import { isTcpSubchannelAddress, SubchannelAddress } from "./subchannel-address"; -import { SubchannelRef as SubchannelRefMessage } from "./generated/grpc/channelz/v1/SubchannelRef"; -import { GetServerRequest__Output } from "./generated/grpc/channelz/v1/GetServerRequest"; -import { GetServerResponse } from "./generated/grpc/channelz/v1/GetServerResponse"; -import { Server as ServerMessage } from "./generated/grpc/channelz/v1/Server"; -import { GetServersRequest__Output } from "./generated/grpc/channelz/v1/GetServersRequest"; -import { GetServersResponse } from "./generated/grpc/channelz/v1/GetServersResponse"; -import { GetTopChannelsRequest__Output } from "./generated/grpc/channelz/v1/GetTopChannelsRequest"; -import { GetTopChannelsResponse } from "./generated/grpc/channelz/v1/GetTopChannelsResponse"; -import { GetSubchannelRequest__Output } from "./generated/grpc/channelz/v1/GetSubchannelRequest"; -import { GetSubchannelResponse } from "./generated/grpc/channelz/v1/GetSubchannelResponse"; -import { Subchannel as SubchannelMessage } from "./generated/grpc/channelz/v1/Subchannel"; -import { GetSocketRequest__Output } from "./generated/grpc/channelz/v1/GetSocketRequest"; -import { GetSocketResponse } from "./generated/grpc/channelz/v1/GetSocketResponse"; -import { Socket as SocketMessage } from "./generated/grpc/channelz/v1/Socket"; -import { Address } from "./generated/grpc/channelz/v1/Address"; -import { Security } from "./generated/grpc/channelz/v1/Security"; -import { GetServerSocketsRequest__Output } from "./generated/grpc/channelz/v1/GetServerSocketsRequest"; -import { GetServerSocketsResponse } from "./generated/grpc/channelz/v1/GetServerSocketsResponse"; -import { ChannelzDefinition, ChannelzHandlers } from "./generated/grpc/channelz/v1/Channelz"; -import { ProtoGrpcType as ChannelzProtoGrpcType } from "./generated/channelz"; +import { isIPv4, isIPv6 } from 'net'; +import { ConnectivityState } from './connectivity-state'; +import { Status } from './constants'; +import { Timestamp } from './generated/google/protobuf/Timestamp'; +import { Channel as ChannelMessage } from './generated/grpc/channelz/v1/Channel'; +import { ChannelConnectivityState__Output } from './generated/grpc/channelz/v1/ChannelConnectivityState'; +import { ChannelRef as ChannelRefMessage } from './generated/grpc/channelz/v1/ChannelRef'; +import { ChannelTrace } from './generated/grpc/channelz/v1/ChannelTrace'; +import { GetChannelRequest__Output } from './generated/grpc/channelz/v1/GetChannelRequest'; +import { GetChannelResponse } from './generated/grpc/channelz/v1/GetChannelResponse'; +import { sendUnaryData, ServerUnaryCall } from './server-call'; +import { ServerRef as ServerRefMessage } from './generated/grpc/channelz/v1/ServerRef'; +import { SocketRef as SocketRefMessage } from './generated/grpc/channelz/v1/SocketRef'; +import { + isTcpSubchannelAddress, + SubchannelAddress, +} from './subchannel-address'; +import { SubchannelRef as SubchannelRefMessage } from './generated/grpc/channelz/v1/SubchannelRef'; +import { GetServerRequest__Output } from './generated/grpc/channelz/v1/GetServerRequest'; +import { GetServerResponse } from './generated/grpc/channelz/v1/GetServerResponse'; +import { Server as ServerMessage } from './generated/grpc/channelz/v1/Server'; +import { GetServersRequest__Output } from './generated/grpc/channelz/v1/GetServersRequest'; +import { GetServersResponse } from './generated/grpc/channelz/v1/GetServersResponse'; +import { GetTopChannelsRequest__Output } from './generated/grpc/channelz/v1/GetTopChannelsRequest'; +import { GetTopChannelsResponse } from './generated/grpc/channelz/v1/GetTopChannelsResponse'; +import { GetSubchannelRequest__Output } from './generated/grpc/channelz/v1/GetSubchannelRequest'; +import { GetSubchannelResponse } from './generated/grpc/channelz/v1/GetSubchannelResponse'; +import { Subchannel as SubchannelMessage } from './generated/grpc/channelz/v1/Subchannel'; +import { GetSocketRequest__Output } from './generated/grpc/channelz/v1/GetSocketRequest'; +import { GetSocketResponse } from './generated/grpc/channelz/v1/GetSocketResponse'; +import { Socket as SocketMessage } from './generated/grpc/channelz/v1/Socket'; +import { Address } from './generated/grpc/channelz/v1/Address'; +import { Security } from './generated/grpc/channelz/v1/Security'; +import { GetServerSocketsRequest__Output } from './generated/grpc/channelz/v1/GetServerSocketsRequest'; +import { GetServerSocketsResponse } from './generated/grpc/channelz/v1/GetServerSocketsResponse'; +import { + ChannelzDefinition, + ChannelzHandlers, +} from './generated/grpc/channelz/v1/Channelz'; +import { ProtoGrpcType as ChannelzProtoGrpcType } from './generated/channelz'; import type { loadSync } from '@grpc/proto-loader'; -import { registerAdminService } from "./admin"; -import { loadPackageDefinition } from "./make-client"; +import { registerAdminService } from './admin'; +import { loadPackageDefinition } from './make-client'; -export type TraceSeverity = 'CT_UNKNOWN' | 'CT_INFO' | 'CT_WARNING' | 'CT_ERROR'; +export type TraceSeverity = + | 'CT_UNKNOWN' + | 'CT_INFO' + | 'CT_WARNING' + | 'CT_ERROR'; export interface ChannelRef { kind: 'channel'; @@ -81,28 +91,28 @@ export interface SocketRef { function channelRefToMessage(ref: ChannelRef): ChannelRefMessage { return { channel_id: ref.id, - name: ref.name + name: ref.name, }; } function subchannelRefToMessage(ref: SubchannelRef): SubchannelRefMessage { return { subchannel_id: ref.id, - name: ref.name - } + name: ref.name, + }; } function serverRefToMessage(ref: ServerRef): ServerRefMessage { return { - server_id: ref.id - } + server_id: ref.id, + }; } function socketRefToMessage(ref: SocketRef): SocketRefMessage { return { socket_id: ref.id, - name: ref.name - } + name: ref.name, + }; } interface TraceEvent { @@ -124,20 +134,24 @@ const TARGET_RETAINED_TRACES = 32; export class ChannelzTrace { events: TraceEvent[] = []; creationTimestamp: Date; - eventsLogged: number = 0; + eventsLogged = 0; constructor() { this.creationTimestamp = new Date(); } - addTrace(severity: TraceSeverity, description: string, child?: ChannelRef | SubchannelRef) { + addTrace( + severity: TraceSeverity, + description: string, + child?: ChannelRef | SubchannelRef + ) { const timestamp = new Date(); this.events.push({ description: description, severity: severity, timestamp: timestamp, childChannel: child?.kind === 'channel' ? child : undefined, - childSubchannel: child?.kind === 'subchannel' ? child : undefined + childSubchannel: child?.kind === 'subchannel' ? child : undefined, }); // Whenever the trace array gets too large, discard the first half if (this.events.length >= TARGET_RETAINED_TRACES * 2) { @@ -155,35 +169,53 @@ export class ChannelzTrace { description: event.description, severity: event.severity, timestamp: dateToProtoTimestamp(event.timestamp), - channel_ref: event.childChannel ? channelRefToMessage(event.childChannel) : null, - subchannel_ref: event.childSubchannel ? subchannelRefToMessage(event.childSubchannel) : null - } - }) + channel_ref: event.childChannel + ? channelRefToMessage(event.childChannel) + : null, + subchannel_ref: event.childSubchannel + ? subchannelRefToMessage(event.childSubchannel) + : null, + }; + }), }; } } export class ChannelzChildrenTracker { - private channelChildren: Map = new Map(); - private subchannelChildren: Map = new Map(); - private socketChildren: Map = new Map(); + private channelChildren: Map = + new Map(); + private subchannelChildren: Map< + number, + { ref: SubchannelRef; count: number } + > = new Map(); + private socketChildren: Map = + new Map(); refChild(child: ChannelRef | SubchannelRef | SocketRef) { switch (child.kind) { case 'channel': { - let trackedChild = this.channelChildren.get(child.id) ?? {ref: child, count: 0}; + const trackedChild = this.channelChildren.get(child.id) ?? { + ref: child, + count: 0, + }; trackedChild.count += 1; this.channelChildren.set(child.id, trackedChild); break; } - case 'subchannel':{ - let trackedChild = this.subchannelChildren.get(child.id) ?? {ref: child, count: 0}; + case 'subchannel': { + const trackedChild = this.subchannelChildren.get(child.id) ?? { + ref: child, + count: 0, + }; trackedChild.count += 1; this.subchannelChildren.set(child.id, trackedChild); break; } - case 'socket':{ - let trackedChild = this.socketChildren.get(child.id) ?? {ref: child, count: 0}; + case 'socket': { + const trackedChild = this.socketChildren.get(child.id) ?? { + ref: child, + count: 0, + }; trackedChild.count += 1; this.socketChildren.set(child.id, trackedChild); break; @@ -194,7 +226,7 @@ export class ChannelzChildrenTracker { unrefChild(child: ChannelRef | SubchannelRef | SocketRef) { switch (child.kind) { case 'channel': { - let trackedChild = this.channelChildren.get(child.id); + const trackedChild = this.channelChildren.get(child.id); if (trackedChild !== undefined) { trackedChild.count -= 1; if (trackedChild.count === 0) { @@ -206,7 +238,7 @@ export class ChannelzChildrenTracker { break; } case 'subchannel': { - let trackedChild = this.subchannelChildren.get(child.id); + const trackedChild = this.subchannelChildren.get(child.id); if (trackedChild !== undefined) { trackedChild.count -= 1; if (trackedChild.count === 0) { @@ -218,7 +250,7 @@ export class ChannelzChildrenTracker { break; } case 'socket': { - let trackedChild = this.socketChildren.get(child.id); + const trackedChild = this.socketChildren.get(child.id); if (trackedChild !== undefined) { trackedChild.count -= 1; if (trackedChild.count === 0) { @@ -234,25 +266,25 @@ export class ChannelzChildrenTracker { getChildLists(): ChannelzChildren { const channels: ChannelRef[] = []; - for (const {ref} of this.channelChildren.values()) { + for (const { ref } of this.channelChildren.values()) { channels.push(ref); } const subchannels: SubchannelRef[] = []; - for (const {ref} of this.subchannelChildren.values()) { + for (const { ref } of this.subchannelChildren.values()) { subchannels.push(ref); } const sockets: SocketRef[] = []; - for (const {ref} of this.socketChildren.values()) { + for (const { ref } of this.socketChildren.values()) { sockets.push(ref); } - return {channels, subchannels, sockets}; + return { channels, subchannels, sockets }; } } export class ChannelzCallTracker { - callsStarted: number = 0; - callsSucceeded: number = 0; - callsFailed: number = 0; + callsStarted = 0; + callsSucceeded = 0; + callsFailed = 0; lastCallStartedTimestamp: Date | null = null; addCallStarted() { @@ -281,7 +313,7 @@ export interface ChannelInfo { children: ChannelzChildren; } -export interface SubchannelInfo extends ChannelInfo {} +export type SubchannelInfo = ChannelInfo; export interface ServerInfo { trace: ChannelzTrace; @@ -347,43 +379,60 @@ const subchannels: (SubchannelEntry | undefined)[] = []; const servers: (ServerEntry | undefined)[] = []; const sockets: (SocketEntry | undefined)[] = []; -export function registerChannelzChannel(name: string, getInfo: () => ChannelInfo, channelzEnabled: boolean): ChannelRef { +export function registerChannelzChannel( + name: string, + getInfo: () => ChannelInfo, + channelzEnabled: boolean +): ChannelRef { const id = getNextId(); - const ref: ChannelRef = {id, name, kind: 'channel'}; + const ref: ChannelRef = { id, name, kind: 'channel' }; if (channelzEnabled) { channels[id] = { ref, getInfo }; } return ref; } -export function registerChannelzSubchannel(name: string, getInfo:() => SubchannelInfo, channelzEnabled: boolean): SubchannelRef { +export function registerChannelzSubchannel( + name: string, + getInfo: () => SubchannelInfo, + channelzEnabled: boolean +): SubchannelRef { const id = getNextId(); - const ref: SubchannelRef = {id, name, kind: 'subchannel'}; + const ref: SubchannelRef = { id, name, kind: 'subchannel' }; if (channelzEnabled) { subchannels[id] = { ref, getInfo }; } return ref; } -export function registerChannelzServer(getInfo: () => ServerInfo, channelzEnabled: boolean): ServerRef { +export function registerChannelzServer( + getInfo: () => ServerInfo, + channelzEnabled: boolean +): ServerRef { const id = getNextId(); - const ref: ServerRef = {id, kind: 'server'}; + const ref: ServerRef = { id, kind: 'server' }; if (channelzEnabled) { servers[id] = { ref, getInfo }; } return ref; } -export function registerChannelzSocket(name: string, getInfo: () => SocketInfo, channelzEnabled: boolean): SocketRef { +export function registerChannelzSocket( + name: string, + getInfo: () => SocketInfo, + channelzEnabled: boolean +): SocketRef { const id = getNextId(); - const ref: SocketRef = {id, name, kind: 'socket'}; + const ref: SocketRef = { id, name, kind: 'socket' }; if (channelzEnabled) { - sockets[id] = { ref, getInfo}; + sockets[id] = { ref, getInfo }; } return ref; } -export function unregisterChannelzRef(ref: ChannelRef | SubchannelRef | ServerRef | SocketRef) { +export function unregisterChannelzRef( + ref: ChannelRef | SubchannelRef | ServerRef | SocketRef +) { switch (ref.kind) { case 'channel': delete channels[ref.id]; @@ -407,7 +456,7 @@ export function unregisterChannelzRef(ref: ChannelRef | SubchannelRef | ServerRe */ function parseIPv6Section(addressSection: string): [number, number] { const numberValue = Number.parseInt(addressSection, 16); - return [numberValue / 256 | 0, numberValue % 256]; + return [(numberValue / 256) | 0, numberValue % 256]; } /** @@ -420,7 +469,9 @@ function parseIPv6Chunk(addressChunk: string): number[] { if (addressChunk === '') { return []; } - const bytePairs = addressChunk.split(':').map(section => parseIPv6Section(section)); + const bytePairs = addressChunk + .split(':') + .map(section => parseIPv6Section(section)); const result: number[] = []; return result.concat(...bytePairs); } @@ -429,11 +480,15 @@ function parseIPv6Chunk(addressChunk: string): number[] { * Converts an IPv4 or IPv6 address from string representation to binary * representation * @param ipAddress an IP address in standard IPv4 or IPv6 text format - * @returns + * @returns */ function ipAddressStringToBuffer(ipAddress: string): Buffer | null { if (isIPv4(ipAddress)) { - return Buffer.from(Uint8Array.from(ipAddress.split('.').map(segment => Number.parseInt(segment)))); + return Buffer.from( + Uint8Array.from( + ipAddress.split('.').map(segment => Number.parseInt(segment)) + ) + ); } else if (isIPv6(ipAddress)) { let leftSection: string; let rightSection: string; @@ -447,38 +502,43 @@ function ipAddressStringToBuffer(ipAddress: string): Buffer | null { } const leftBuffer = Buffer.from(parseIPv6Chunk(leftSection)); const rightBuffer = Buffer.from(parseIPv6Chunk(rightSection)); - const middleBuffer = Buffer.alloc(16 - leftBuffer.length - rightBuffer.length, 0); + const middleBuffer = Buffer.alloc( + 16 - leftBuffer.length - rightBuffer.length, + 0 + ); return Buffer.concat([leftBuffer, middleBuffer, rightBuffer]); } else { return null; } } -function connectivityStateToMessage(state: ConnectivityState): ChannelConnectivityState__Output { +function connectivityStateToMessage( + state: ConnectivityState +): ChannelConnectivityState__Output { switch (state) { case ConnectivityState.CONNECTING: return { - state: 'CONNECTING' + state: 'CONNECTING', }; case ConnectivityState.IDLE: return { - state: 'IDLE' + state: 'IDLE', }; case ConnectivityState.READY: return { - state: 'READY' + state: 'READY', }; case ConnectivityState.SHUTDOWN: return { - state: 'SHUTDOWN' + state: 'SHUTDOWN', }; case ConnectivityState.TRANSIENT_FAILURE: return { - state: 'TRANSIENT_FAILURE' + state: 'TRANSIENT_FAILURE', }; default: return { - state: 'UNKNOWN' + state: 'UNKNOWN', }; } } @@ -490,8 +550,8 @@ function dateToProtoTimestamp(date?: Date | null): Timestamp | null { const millisSinceEpoch = date.getTime(); return { seconds: (millisSinceEpoch / 1000) | 0, - nanos: (millisSinceEpoch % 1000) * 1_000_000 - } + nanos: (millisSinceEpoch % 1000) * 1_000_000, + }; } function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { @@ -504,28 +564,40 @@ function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { calls_started: resolvedInfo.callTracker.callsStarted, calls_succeeded: resolvedInfo.callTracker.callsSucceeded, calls_failed: resolvedInfo.callTracker.callsFailed, - last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), - trace: resolvedInfo.trace.getTraceMessage() + last_call_started_timestamp: dateToProtoTimestamp( + resolvedInfo.callTracker.lastCallStartedTimestamp + ), + trace: resolvedInfo.trace.getTraceMessage(), }, - channel_ref: resolvedInfo.children.channels.map(ref => channelRefToMessage(ref)), - subchannel_ref: resolvedInfo.children.subchannels.map(ref => subchannelRefToMessage(ref)) + channel_ref: resolvedInfo.children.channels.map(ref => + channelRefToMessage(ref) + ), + subchannel_ref: resolvedInfo.children.subchannels.map(ref => + subchannelRefToMessage(ref) + ), }; } -function GetChannel(call: ServerUnaryCall, callback: sendUnaryData): void { +function GetChannel( + call: ServerUnaryCall, + callback: sendUnaryData +): void { const channelId = Number.parseInt(call.request.channel_id); const channelEntry = channels[channelId]; if (channelEntry === undefined) { callback({ - 'code': Status.NOT_FOUND, - 'details': 'No channel data found for id ' + channelId + code: Status.NOT_FOUND, + details: 'No channel data found for id ' + channelId, }); return; } - callback(null, {channel: getChannelMessage(channelEntry)}); + callback(null, { channel: getChannelMessage(channelEntry) }); } -function GetTopChannels(call: ServerUnaryCall, callback: sendUnaryData): void { +function GetTopChannels( + call: ServerUnaryCall, + callback: sendUnaryData +): void { const maxResults = Number.parseInt(call.request.max_results); const resultList: ChannelMessage[] = []; let i = Number.parseInt(call.request.start_channel_id); @@ -541,7 +613,7 @@ function GetTopChannels(call: ServerUnaryCall= servers.length + end: i >= servers.length, }); } @@ -553,27 +625,37 @@ function getServerMessage(serverEntry: ServerEntry): ServerMessage { calls_started: resolvedInfo.callTracker.callsStarted, calls_succeeded: resolvedInfo.callTracker.callsSucceeded, calls_failed: resolvedInfo.callTracker.callsFailed, - last_call_started_timestamp: dateToProtoTimestamp(resolvedInfo.callTracker.lastCallStartedTimestamp), - trace: resolvedInfo.trace.getTraceMessage() + last_call_started_timestamp: dateToProtoTimestamp( + resolvedInfo.callTracker.lastCallStartedTimestamp + ), + trace: resolvedInfo.trace.getTraceMessage(), }, - listen_socket: resolvedInfo.listenerChildren.sockets.map(ref => socketRefToMessage(ref)) + listen_socket: resolvedInfo.listenerChildren.sockets.map(ref => + socketRefToMessage(ref) + ), }; } -function GetServer(call: ServerUnaryCall, callback: sendUnaryData): void { +function GetServer( + call: ServerUnaryCall, + callback: sendUnaryData +): void { const serverId = Number.parseInt(call.request.server_id); const serverEntry = servers[serverId]; if (serverEntry === undefined) { callback({ - 'code': Status.NOT_FOUND, - 'details': 'No server data found for id ' + serverId + code: Status.NOT_FOUND, + details: 'No server data found for id ' + serverId, }); return; } - callback(null, {server: getServerMessage(serverEntry)}); + callback(null, { server: getServerMessage(serverEntry) }); } -function GetServers(call: ServerUnaryCall, callback: sendUnaryData): void { +function GetServers( + call: ServerUnaryCall, + callback: sendUnaryData +): void { const maxResults = Number.parseInt(call.request.max_results); const resultList: ServerMessage[] = []; let i = Number.parseInt(call.request.start_server_id); @@ -589,17 +671,20 @@ function GetServers(call: ServerUnaryCall= servers.length + end: i >= servers.length, }); } -function GetSubchannel(call: ServerUnaryCall, callback: sendUnaryData): void { +function GetSubchannel( + call: ServerUnaryCall, + callback: sendUnaryData +): void { const subchannelId = Number.parseInt(call.request.subchannel_id); const subchannelEntry = subchannels[subchannelId]; if (subchannelEntry === undefined) { callback({ - 'code': Status.NOT_FOUND, - 'details': 'No subchannel data found for id ' + subchannelId + code: Status.NOT_FOUND, + details: 'No subchannel data found for id ' + subchannelId, }); return; } @@ -612,58 +697,79 @@ function GetSubchannel(call: ServerUnaryCall socketRefToMessage(ref)) + socket_ref: resolvedInfo.children.sockets.map(ref => + socketRefToMessage(ref) + ), }; - callback(null, {subchannel: subchannelMessage}); + callback(null, { subchannel: subchannelMessage }); } -function subchannelAddressToAddressMessage(subchannelAddress: SubchannelAddress): Address { +function subchannelAddressToAddressMessage( + subchannelAddress: SubchannelAddress +): Address { if (isTcpSubchannelAddress(subchannelAddress)) { return { address: 'tcpip_address', tcpip_address: { - ip_address: ipAddressStringToBuffer(subchannelAddress.host) ?? undefined, - port: subchannelAddress.port - } + ip_address: + ipAddressStringToBuffer(subchannelAddress.host) ?? undefined, + port: subchannelAddress.port, + }, }; } else { return { address: 'uds_address', uds_address: { - filename: subchannelAddress.path - } + filename: subchannelAddress.path, + }, }; } } -function GetSocket(call: ServerUnaryCall, callback: sendUnaryData): void { +function GetSocket( + call: ServerUnaryCall, + callback: sendUnaryData +): void { const socketId = Number.parseInt(call.request.socket_id); const socketEntry = sockets[socketId]; if (socketEntry === undefined) { callback({ - 'code': Status.NOT_FOUND, - 'details': 'No socket data found for id ' + socketId + code: Status.NOT_FOUND, + details: 'No socket data found for id ' + socketId, }); return; } const resolvedInfo = socketEntry.getInfo(); - const securityMessage: Security | null = resolvedInfo.security ? { - model: 'tls', - tls: { - cipher_suite: resolvedInfo.security.cipherSuiteStandardName ? 'standard_name' : 'other_name', - standard_name: resolvedInfo.security.cipherSuiteStandardName ?? undefined, - other_name: resolvedInfo.security.cipherSuiteOtherName ?? undefined, - local_certificate: resolvedInfo.security.localCertificate ?? undefined, - remote_certificate: resolvedInfo.security.remoteCertificate ?? undefined - } - } : null; + const securityMessage: Security | null = resolvedInfo.security + ? { + model: 'tls', + tls: { + cipher_suite: resolvedInfo.security.cipherSuiteStandardName + ? 'standard_name' + : 'other_name', + standard_name: + resolvedInfo.security.cipherSuiteStandardName ?? undefined, + other_name: resolvedInfo.security.cipherSuiteOtherName ?? undefined, + local_certificate: + resolvedInfo.security.localCertificate ?? undefined, + remote_certificate: + resolvedInfo.security.remoteCertificate ?? undefined, + }, + } + : null; const socketMessage: SocketMessage = { ref: socketRefToMessage(socketEntry.ref), - local: resolvedInfo.localAddress ? subchannelAddressToAddressMessage(resolvedInfo.localAddress) : null, - remote: resolvedInfo.remoteAddress ? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress) : null, + local: resolvedInfo.localAddress + ? subchannelAddressToAddressMessage(resolvedInfo.localAddress) + : null, + remote: resolvedInfo.remoteAddress + ? subchannelAddressToAddressMessage(resolvedInfo.remoteAddress) + : null, remote_name: resolvedInfo.remoteName ?? undefined, security: securityMessage, data: { @@ -671,26 +777,44 @@ function GetSocket(call: ServerUnaryCall, callback: sendUnaryData): void { +function GetServerSockets( + call: ServerUnaryCall< + GetServerSocketsRequest__Output, + GetServerSocketsResponse + >, + callback: sendUnaryData +): void { const serverId = Number.parseInt(call.request.server_id); const serverEntry = servers[serverId]; if (serverEntry === undefined) { callback({ - 'code': Status.NOT_FOUND, - 'details': 'No server data found for id ' + serverId + code: Status.NOT_FOUND, + details: 'No server data found for id ' + serverId, }); return; } @@ -700,7 +824,9 @@ function GetServerSockets(call: ServerUnaryCall ref1.id - ref2.id); - const allSockets = resolvedInfo.sessionChildren.sockets.sort((ref1, ref2) => ref1.id - ref2.id); + const allSockets = resolvedInfo.sessionChildren.sockets.sort( + (ref1, ref2) => ref1.id - ref2.id + ); const resultList: SocketRefMessage[] = []; let i = 0; for (; i < allSockets.length; i++) { @@ -713,7 +839,7 @@ function GetServerSockets(call: ServerUnaryCall= allSockets.length + end: i >= allSockets.length, }); } @@ -725,7 +851,7 @@ export function getChannelzHandlers(): ChannelzHandlers { GetServers, GetSubchannel, GetSocket, - GetServerSockets + GetServerSockets, }; } @@ -737,22 +863,24 @@ export function getChannelzServiceDefinition(): ChannelzDefinition { } /* The purpose of this complexity is to avoid loading @grpc/proto-loader at * runtime for users who will not use/enable channelz. */ - const loaderLoadSync = require('@grpc/proto-loader').loadSync as typeof loadSync; + const loaderLoadSync = require('@grpc/proto-loader') + .loadSync as typeof loadSync; const loadedProto = loaderLoadSync('channelz.proto', { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, - includeDirs: [ - `${__dirname}/../../proto` - ] + includeDirs: [`${__dirname}/../../proto`], }); - const channelzGrpcObject = loadPackageDefinition(loadedProto) as unknown as ChannelzProtoGrpcType; - loadedChannelzDefinition = channelzGrpcObject.grpc.channelz.v1.Channelz.service; + const channelzGrpcObject = loadPackageDefinition( + loadedProto + ) as unknown as ChannelzProtoGrpcType; + loadedChannelzDefinition = + channelzGrpcObject.grpc.channelz.v1.Channelz.service; return loadedChannelzDefinition; } export function setup() { registerAdminService(getChannelzServiceDefinition, getChannelzHandlers); -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index d95828550..56175ed80 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -176,10 +176,10 @@ const defaultRequester: FullRequester = { sendMessage: (message, next) => { next(message); }, - halfClose: (next) => { + halfClose: next => { next(); }, - cancel: (next) => { + cancel: next => { next(); }, }; @@ -254,7 +254,10 @@ export class InterceptingCall implements InterceptingCallInterface { private processPendingMessage() { if (this.pendingMessageContext) { - this.nextCall.sendMessageWithContext(this.pendingMessageContext, this.pendingMessage); + this.nextCall.sendMessageWithContext( + this.pendingMessageContext, + this.pendingMessage + ); this.pendingMessageContext = null; this.pendingMessage = null; } @@ -273,13 +276,13 @@ export class InterceptingCall implements InterceptingCallInterface { const fullInterceptingListener: InterceptingListener = { onReceiveMetadata: interceptingListener?.onReceiveMetadata?.bind(interceptingListener) ?? - ((metadata) => {}), + (metadata => {}), onReceiveMessage: interceptingListener?.onReceiveMessage?.bind(interceptingListener) ?? - ((message) => {}), + (message => {}), onReceiveStatus: interceptingListener?.onReceiveStatus?.bind(interceptingListener) ?? - ((status) => {}), + (status => {}), }; this.processingMetadata = true; this.requester.start(metadata, fullInterceptingListener, (md, listener) => { @@ -309,7 +312,7 @@ export class InterceptingCall implements InterceptingCallInterface { // eslint-disable-next-line @typescript-eslint/no-explicit-any sendMessageWithContext(context: MessageContext, message: any): void { this.processingMessage = true; - this.requester.sendMessage(message, (finalMessage) => { + this.requester.sendMessage(message, finalMessage => { this.processingMessage = false; if (this.processingMetadata) { this.pendingMessageContext = context; @@ -391,10 +394,10 @@ class BaseInterceptingCall implements InterceptingCallInterface { ): void { let readError: StatusObject | null = null; this.call.start(metadata, { - onReceiveMetadata: (metadata) => { + onReceiveMetadata: metadata => { interceptingListener?.onReceiveMetadata?.(metadata); }, - onReceiveMessage: (message) => { + onReceiveMessage: message => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let deserialized: any; try { @@ -410,7 +413,7 @@ class BaseInterceptingCall implements InterceptingCallInterface { } interceptingListener?.onReceiveMessage?.(deserialized); }, - onReceiveStatus: (status) => { + onReceiveStatus: status => { if (readError) { interceptingListener?.onReceiveStatus?.(readError); } else { @@ -433,7 +436,8 @@ class BaseInterceptingCall implements InterceptingCallInterface { */ class BaseUnaryInterceptingCall extends BaseInterceptingCall - implements InterceptingCallInterface { + implements InterceptingCallInterface +{ // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(call: Call, methodDefinition: ClientMethodDefinition) { super(call, methodDefinition); @@ -442,7 +446,7 @@ class BaseUnaryInterceptingCall let receivedMessage = false; const wrapperListener: InterceptingListener = { onReceiveMetadata: - listener?.onReceiveMetadata?.bind(listener) ?? ((metadata) => {}), + listener?.onReceiveMetadata?.bind(listener) ?? (metadata => {}), // eslint-disable-next-line @typescript-eslint/no-explicit-any onReceiveMessage: (message: any) => { receivedMessage = true; @@ -536,21 +540,21 @@ export function getInterceptingCall( interceptors = ([] as Interceptor[]) .concat( interceptorArgs.callInterceptors, - interceptorArgs.callInterceptorProviders.map((provider) => + interceptorArgs.callInterceptorProviders.map(provider => provider(methodDefinition) ) ) - .filter((interceptor) => interceptor); + .filter(interceptor => interceptor); // Filter out falsy values when providers return nothing } else { interceptors = ([] as Interceptor[]) .concat( interceptorArgs.clientInterceptors, - interceptorArgs.clientInterceptorProviders.map((provider) => + interceptorArgs.clientInterceptorProviders.map(provider => provider(methodDefinition) ) ) - .filter((interceptor) => interceptor); + .filter(interceptor => interceptor); // Filter out falsy values when providers return nothing } const interceptorOptions = Object.assign({}, options, { @@ -565,7 +569,7 @@ export function getInterceptingCall( * channel. */ const getCall: NextCall = interceptors.reduceRight( (nextCall: NextCall, nextInterceptor: Interceptor) => { - return (currentOptions) => nextInterceptor(currentOptions, nextCall); + return currentOptions => nextInterceptor(currentOptions, nextCall); }, (finalOptions: InterceptorOptions) => getBottomInterceptingCall(channel, finalOptions, methodDefinition) diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index f96f8fdf0..61f986661 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -273,21 +273,20 @@ export class Client { options?: CallOptions | UnaryCallback, callback?: UnaryCallback ): ClientUnaryCall { - const checkedArguments = this.checkOptionalUnaryResponseArguments( - metadata, - options, - callback - ); - const methodDefinition: ClientMethodDefinition< - RequestType, - ResponseType - > = { - path: method, - requestStream: false, - responseStream: false, - requestSerialize: serialize, - responseDeserialize: deserialize, - }; + const checkedArguments = + this.checkOptionalUnaryResponseArguments( + metadata, + options, + callback + ); + const methodDefinition: ClientMethodDefinition = + { + path: method, + requestStream: false, + responseStream: false, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; let callProperties: CallProperties = { argument: argument, metadata: checkedArguments.metadata, @@ -325,7 +324,7 @@ export class Client { let receivedStatus = false; const callerStackError = new Error(); call.start(callProperties.metadata, { - onReceiveMetadata: (metadata) => { + onReceiveMetadata: metadata => { emitter.emit('metadata', metadata); }, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -343,11 +342,16 @@ export class Client { if (status.code === Status.OK) { if (responseMessage === null) { const callerStack = getErrorStackString(callerStackError); - callProperties.callback!(callErrorFromStatus({ - code: Status.INTERNAL, - details: 'No message received', - metadata: status.metadata - }, callerStack)); + callProperties.callback!( + callErrorFromStatus( + { + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata, + }, + callerStack + ) + ); } else { callProperties.callback!(null, responseMessage); } @@ -399,21 +403,20 @@ export class Client { options?: CallOptions | UnaryCallback, callback?: UnaryCallback ): ClientWritableStream { - const checkedArguments = this.checkOptionalUnaryResponseArguments( - metadata, - options, - callback - ); - const methodDefinition: ClientMethodDefinition< - RequestType, - ResponseType - > = { - path: method, - requestStream: true, - responseStream: false, - requestSerialize: serialize, - responseDeserialize: deserialize, - }; + const checkedArguments = + this.checkOptionalUnaryResponseArguments( + metadata, + options, + callback + ); + const methodDefinition: ClientMethodDefinition = + { + path: method, + requestStream: true, + responseStream: false, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; let callProperties: CallProperties = { metadata: checkedArguments.metadata, call: new ClientWritableStreamImpl(serialize), @@ -427,7 +430,8 @@ export class Client { callProperties ) as CallProperties; } - const emitter: ClientWritableStream = callProperties.call as ClientWritableStream; + const emitter: ClientWritableStream = + callProperties.call as ClientWritableStream; const interceptorArgs: InterceptorArguments = { clientInterceptors: this[INTERCEPTOR_SYMBOL], clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], @@ -450,7 +454,7 @@ export class Client { let receivedStatus = false; const callerStackError = new Error(); call.start(callProperties.metadata, { - onReceiveMetadata: (metadata) => { + onReceiveMetadata: metadata => { emitter.emit('metadata', metadata); }, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -468,11 +472,16 @@ export class Client { if (status.code === Status.OK) { if (responseMessage === null) { const callerStack = getErrorStackString(callerStackError); - callProperties.callback!(callErrorFromStatus({ - code: Status.INTERNAL, - details: 'No message received', - metadata: status.metadata - }, callerStack)); + callProperties.callback!( + callErrorFromStatus( + { + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata, + }, + callerStack + ) + ); } else { callProperties.callback!(null, responseMessage); } @@ -534,16 +543,14 @@ export class Client { options?: CallOptions ): ClientReadableStream { const checkedArguments = this.checkMetadataAndOptions(metadata, options); - const methodDefinition: ClientMethodDefinition< - RequestType, - ResponseType - > = { - path: method, - requestStream: false, - responseStream: true, - requestSerialize: serialize, - responseDeserialize: deserialize, - }; + const methodDefinition: ClientMethodDefinition = + { + path: method, + requestStream: false, + responseStream: true, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; let callProperties: CallProperties = { argument: argument, metadata: checkedArguments.metadata, @@ -557,7 +564,8 @@ export class Client { callProperties ) as CallProperties; } - const stream: ClientReadableStream = callProperties.call as ClientReadableStream; + const stream: ClientReadableStream = + callProperties.call as ClientReadableStream; const interceptorArgs: InterceptorArguments = { clientInterceptors: this[INTERCEPTOR_SYMBOL], clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], @@ -625,16 +633,14 @@ export class Client { options?: CallOptions ): ClientDuplexStream { const checkedArguments = this.checkMetadataAndOptions(metadata, options); - const methodDefinition: ClientMethodDefinition< - RequestType, - ResponseType - > = { - path: method, - requestStream: true, - responseStream: true, - requestSerialize: serialize, - responseDeserialize: deserialize, - }; + const methodDefinition: ClientMethodDefinition = + { + path: method, + requestStream: true, + responseStream: true, + requestSerialize: serialize, + responseDeserialize: deserialize, + }; let callProperties: CallProperties = { metadata: checkedArguments.metadata, call: new ClientDuplexStreamImpl( @@ -650,10 +656,8 @@ export class Client { callProperties ) as CallProperties; } - const stream: ClientDuplexStream< - RequestType, - ResponseType - > = callProperties.call as ClientDuplexStream; + const stream: ClientDuplexStream = + callProperties.call as ClientDuplexStream; const interceptorArgs: InterceptorArguments = { clientInterceptors: this[INTERCEPTOR_SYMBOL], clientInterceptorProviders: this[INTERCEPTOR_PROVIDER_SYMBOL], diff --git a/packages/grpc-js/src/compression-algorithms.ts b/packages/grpc-js/src/compression-algorithms.ts index ca2c7a624..67fdcf14c 100644 --- a/packages/grpc-js/src/compression-algorithms.ts +++ b/packages/grpc-js/src/compression-algorithms.ts @@ -18,5 +18,5 @@ export enum CompressionAlgorithms { identity = 0, deflate = 1, - gzip = 2 -}; + gzip = 2, +} diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index f87614114..3fd5604cd 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -26,9 +26,13 @@ import { BaseFilter, Filter, FilterFactory } from './filter'; import * as logging from './logging'; import { Metadata, MetadataValue } from './metadata'; -const isCompressionAlgorithmKey = (key: number): key is CompressionAlgorithms => { - return typeof key === 'number' && typeof CompressionAlgorithms[key] === 'string'; -} +const isCompressionAlgorithmKey = ( + key: number +): key is CompressionAlgorithms => { + return ( + typeof key === 'number' && typeof CompressionAlgorithms[key] === 'string' + ); +}; type CompressionAlgorithm = keyof typeof CompressionAlgorithms; @@ -183,14 +187,21 @@ export class CompressionFilter extends BaseFilter implements Filter { private receiveCompression: CompressionHandler = new IdentityHandler(); private currentCompressionAlgorithm: CompressionAlgorithm = 'identity'; - constructor(channelOptions: ChannelOptions, private sharedFilterConfig: SharedCompressionFilterConfig) { + constructor( + channelOptions: ChannelOptions, + private sharedFilterConfig: SharedCompressionFilterConfig + ) { super(); - const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']; + const compressionAlgorithmKey = + channelOptions['grpc.default_compression_algorithm']; if (compressionAlgorithmKey !== undefined) { if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { - const clientSelectedEncoding = CompressionAlgorithms[compressionAlgorithmKey] as CompressionAlgorithm; - const serverSupportedEncodings = sharedFilterConfig.serverSupportedEncodingHeader?.split(','); + const clientSelectedEncoding = CompressionAlgorithms[ + compressionAlgorithmKey + ] as CompressionAlgorithm; + const serverSupportedEncodings = + sharedFilterConfig.serverSupportedEncodingHeader?.split(','); /** * There are two possible situations here: * 1) We don't have any info yet from the server about what compression it supports @@ -198,12 +209,20 @@ export class CompressionFilter extends BaseFilter implements Filter { * 2) We've previously received a response from the server including a grpc-accept-encoding header * In that case we only want to use the encoding chosen by the client if the server supports it */ - if (!serverSupportedEncodings || serverSupportedEncodings.includes(clientSelectedEncoding)) { + if ( + !serverSupportedEncodings || + serverSupportedEncodings.includes(clientSelectedEncoding) + ) { this.currentCompressionAlgorithm = clientSelectedEncoding; - this.sendCompression = getCompressionHandler(this.currentCompressionAlgorithm); + this.sendCompression = getCompressionHandler( + this.currentCompressionAlgorithm + ); } } else { - logging.log(LogVerbosity.ERROR, `Invalid value provided for grpc.default_compression_algorithm option: ${compressionAlgorithmKey}`); + logging.log( + LogVerbosity.ERROR, + `Invalid value provided for grpc.default_compression_algorithm option: ${compressionAlgorithmKey}` + ); } } } @@ -235,12 +254,18 @@ export class CompressionFilter extends BaseFilter implements Filter { /* Check to see if the compression we're using to send messages is supported by the server * If not, reset the sendCompression filter and have it use the default IdentityHandler */ - const serverSupportedEncodingsHeader = metadata.get('grpc-accept-encoding')[0] as string | undefined; + const serverSupportedEncodingsHeader = metadata.get( + 'grpc-accept-encoding' + )[0] as string | undefined; if (serverSupportedEncodingsHeader) { - this.sharedFilterConfig.serverSupportedEncodingHeader = serverSupportedEncodingsHeader; - const serverSupportedEncodings = serverSupportedEncodingsHeader.split(','); + this.sharedFilterConfig.serverSupportedEncodingHeader = + serverSupportedEncodingsHeader; + const serverSupportedEncodings = + serverSupportedEncodingsHeader.split(','); - if (!serverSupportedEncodings.includes(this.currentCompressionAlgorithm)) { + if ( + !serverSupportedEncodings.includes(this.currentCompressionAlgorithm) + ) { this.sendCompression = new IdentityHandler(); this.currentCompressionAlgorithm = 'identity'; } @@ -280,9 +305,13 @@ export class CompressionFilter extends BaseFilter implements Filter { } export class CompressionFilterFactory - implements FilterFactory { - private sharedFilterConfig: SharedCompressionFilterConfig = {}; - constructor(private readonly channel: Channel, private readonly options: ChannelOptions) {} + implements FilterFactory +{ + private sharedFilterConfig: SharedCompressionFilterConfig = {}; + constructor( + private readonly channel: Channel, + private readonly options: ChannelOptions + ) {} createFilter(): CompressionFilter { return new CompressionFilter(this.options, this.sharedFilterConfig); } diff --git a/packages/grpc-js/src/control-plane-status.ts b/packages/grpc-js/src/control-plane-status.ts index 808d695b2..1d10cb3d9 100644 --- a/packages/grpc-js/src/control-plane-status.ts +++ b/packages/grpc-js/src/control-plane-status.ts @@ -25,16 +25,19 @@ const INAPPROPRIATE_CONTROL_PLANE_CODES: Status[] = [ Status.FAILED_PRECONDITION, Status.ABORTED, Status.OUT_OF_RANGE, - Status.DATA_LOSS -] + Status.DATA_LOSS, +]; -export function restrictControlPlaneStatusCode(code: Status, details: string): {code: Status, details: string} { +export function restrictControlPlaneStatusCode( + code: Status, + details: string +): { code: Status; details: string } { if (INAPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) { return { code: Status.INTERNAL, - details: `Invalid status from control plane: ${code} ${Status[code]} ${details}` - } + details: `Invalid status from control plane: ${code} ${Status[code]} ${details}`, + }; } else { - return {code, details}; + return { code, details }; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/deadline.ts b/packages/grpc-js/src/deadline.ts index be1f3c3b0..8f8fe67b7 100644 --- a/packages/grpc-js/src/deadline.ts +++ b/packages/grpc-js/src/deadline.ts @@ -48,7 +48,7 @@ export function getDeadlineTimeoutString(deadline: Deadline) { return String(Math.ceil(amount)) + unit; } } - throw new Error('Deadline is too far in the future') + throw new Error('Deadline is too far in the future'); } /** @@ -65,7 +65,7 @@ const MAX_TIMEOUT_TIME = 2147483647; * immediately, represented by a value of 0. For any deadline more than * MAX_TIMEOUT_TIME milliseconds in the future, a timer cannot be set that will * end at that time, so it is treated as infinitely far in the future. - * @param deadline + * @param deadline * @returns */ export function getRelativeTimeout(deadline: Deadline) { @@ -75,7 +75,7 @@ export function getRelativeTimeout(deadline: Deadline) { if (timeout < 0) { return 0; } else if (timeout > MAX_TIMEOUT_TIME) { - return Infinity + return Infinity; } else { return timeout; } @@ -92,4 +92,4 @@ export function deadlineToString(deadline: Deadline): string { return dateDeadline.toISOString(); } } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/duration.ts b/packages/grpc-js/src/duration.ts index 278c9ae54..ff77dba25 100644 --- a/packages/grpc-js/src/duration.ts +++ b/packages/grpc-js/src/duration.ts @@ -23,7 +23,7 @@ export interface Duration { export function msToDuration(millis: number): Duration { return { seconds: (millis / 1000) | 0, - nanos: (millis % 1000) * 1_000_000 | 0 + nanos: ((millis % 1000) * 1_000_000) | 0, }; } @@ -32,5 +32,5 @@ export function durationToMs(duration: Duration): number { } export function isDuration(value: any): value is Duration { - return (typeof value.seconds === 'number') && (typeof value.nanos === 'number'); -} \ No newline at end of file + return typeof value.seconds === 'number' && typeof value.nanos === 'number'; +} diff --git a/packages/grpc-js/src/error.ts b/packages/grpc-js/src/error.ts index b973128a7..105a3eef3 100644 --- a/packages/grpc-js/src/error.ts +++ b/packages/grpc-js/src/error.ts @@ -34,4 +34,4 @@ export function getErrorCode(error: unknown): number | null { } else { return null; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 26c9596ed..9e4bbf45b 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -4,7 +4,7 @@ export { ResolverListener, registerResolver, ConfigSelector, - createResolver + createResolver, } from './resolver'; export { GrpcUri, uriToString } from './uri-parser'; export { Duration, durationToMs } from './duration'; @@ -36,5 +36,13 @@ export { Call as CallStream } from './call-interface'; export { Filter, BaseFilter, FilterFactory } from './filter'; export { FilterStackFactory } from './filter-stack'; export { registerAdminService } from './admin'; -export { SubchannelInterface, BaseSubchannelWrapper, ConnectivityStateListener } from './subchannel-interface'; -export { OutlierDetectionLoadBalancingConfig, SuccessRateEjectionConfig, FailurePercentageEjectionConfig } from './load-balancer-outlier-detection'; +export { + SubchannelInterface, + BaseSubchannelWrapper, + ConnectivityStateListener, +} from './subchannel-interface'; +export { + OutlierDetectionLoadBalancingConfig, + SuccessRateEjectionConfig, + FailurePercentageEjectionConfig, +} from './load-balancer-outlier-detection'; diff --git a/packages/grpc-js/src/filter-stack.ts b/packages/grpc-js/src/filter-stack.ts index 4733c8218..910f5aa36 100644 --- a/packages/grpc-js/src/filter-stack.ts +++ b/packages/grpc-js/src/filter-stack.ts @@ -94,7 +94,7 @@ export class FilterStackFactory implements FilterFactory { createFilter(): FilterStack { return new FilterStack( - this.factories.map((factory) => factory.createFilter()) + this.factories.map(factory => factory.createFilter()) ); } } diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index 6faa1976d..3aed28c85 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -206,11 +206,11 @@ export function getProxiedConnection( if ('grpc.http_connect_creds' in channelOptions) { headers['Proxy-Authorization'] = 'Basic ' + - Buffer.from( - channelOptions['grpc.http_connect_creds'] as string - ).toString('base64'); + Buffer.from(channelOptions['grpc.http_connect_creds'] as string).toString( + 'base64' + ); } - options.headers = headers + options.headers = headers; const proxyAddressString = subchannelAddressToString(address); trace('Using proxy ' + proxyAddressString + ' to connect to ' + options.path); return new Promise((resolve, reject) => { @@ -252,12 +252,14 @@ export function getProxiedConnection( } ); cts.on('error', (error: Error) => { - trace('Failed to establish a TLS connection to ' + - options.path + - ' through proxy ' + - proxyAddressString + - ' with error ' + - error.message); + trace( + 'Failed to establish a TLS connection to ' + + options.path + + ' through proxy ' + + proxyAddressString + + ' with error ' + + error.message + ); reject(); }); } else { @@ -285,7 +287,7 @@ export function getProxiedConnection( reject(); } }); - request.once('error', (err) => { + request.once('error', err => { request.removeAllListeners(); log( LogVerbosity.ERROR, diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 51f394785..9363a37b7 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -128,7 +128,7 @@ export { Status as status, ConnectivityState as connectivityState, Propagate as propagate, - CompressionAlgorithms as compressionAlgorithms + CompressionAlgorithms as compressionAlgorithms, // TODO: Other constants as well }; @@ -248,21 +248,18 @@ export { InterceptorProvider, InterceptingCall, InterceptorConfigurationError, - NextCall + NextCall, } from './client-interceptors'; export { GrpcObject, ServiceClientConstructor, - ProtobufTypeDefinition + ProtobufTypeDefinition, } from './make-client'; export { ChannelOptions } from './channel-options'; -export { - getChannelzServiceDefinition, - getChannelzHandlers -} from './channelz'; +export { getChannelzServiceDefinition, getChannelzHandlers } from './channelz'; export { addAdminServicesToServer } from './admin'; @@ -281,7 +278,11 @@ import { Deadline } from './deadline'; const clientVersion = require('../../package.json').version; (() => { - logging.trace(LogVerbosity.DEBUG, 'index', 'Loading @grpc/grpc-js version ' + clientVersion); + logging.trace( + LogVerbosity.DEBUG, + 'index', + 'Loading @grpc/grpc-js version ' + clientVersion + ); resolver_dns.setup(); resolver_uds.setup(); resolver_ip.setup(); diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 14038bd3f..3f3ca5d7f 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -40,18 +40,45 @@ import { ServerSurfaceCall } from './server-call'; import { Filter } from './filter'; import { ConnectivityState } from './connectivity-state'; -import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; +import { + ChannelInfo, + ChannelRef, + ChannelzCallTracker, + ChannelzChildrenTracker, + ChannelzTrace, + registerChannelzChannel, + SubchannelRef, + unregisterChannelzRef, +} from './channelz'; import { Subchannel } from './subchannel'; import { LoadBalancingCall } from './load-balancing-call'; import { CallCredentials } from './call-credentials'; -import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from './call-interface'; +import { + Call, + CallStreamOptions, + InterceptingListener, + MessageContext, + StatusObject, +} from './call-interface'; import { SubchannelCall } from './subchannel-call'; -import { Deadline, deadlineToString, getDeadlineTimeoutString } from './deadline'; +import { + Deadline, + deadlineToString, + getDeadlineTimeoutString, +} from './deadline'; import { ResolvingCall } from './resolving-call'; import { getNextCallNumber } from './call-number'; import { restrictControlPlaneStatusCode } from './control-plane-status'; -import { MessageBufferTracker, RetryingCall, RetryThrottler } from './retrying-call'; -import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from './subchannel-interface'; +import { + MessageBufferTracker, + RetryingCall, + RetryThrottler, +} from './retrying-call'; +import { + BaseSubchannelWrapper, + ConnectivityStateListener, + SubchannelInterface, +} from './subchannel-interface'; /** * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args @@ -78,19 +105,33 @@ interface ErrorConfigResult { error: StatusObject; } -type GetConfigResult = NoneConfigResult | SuccessConfigResult | ErrorConfigResult; +type GetConfigResult = + | NoneConfigResult + | SuccessConfigResult + | ErrorConfigResult; const RETRY_THROTTLER_MAP: Map = new Map(); -const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1<<24; // 16 MB -const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1<<20; // 1 MB +const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1 << 24; // 16 MB +const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1 << 20; // 1 MB -class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { +class ChannelSubchannelWrapper + extends BaseSubchannelWrapper + implements SubchannelInterface +{ private refCount = 0; private subchannelStateListener: ConnectivityStateListener; - constructor(childSubchannel: SubchannelInterface, private channel: InternalChannel) { + constructor( + childSubchannel: SubchannelInterface, + private channel: InternalChannel + ) { super(childSubchannel); - this.subchannelStateListener = (subchannel, previousState, newState, keepaliveTime) => { + this.subchannelStateListener = ( + subchannel, + previousState, + newState, + keepaliveTime + ) => { channel.throttleKeepalive(keepaliveTime); }; childSubchannel.addConnectivityStateListener(this.subchannelStateListener); @@ -112,7 +153,6 @@ class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements Subchann } export class InternalChannel { - private resolvingLoadBalancer: ResolvingLoadBalancer; private subchannelPool: SubchannelPool; private connectivityState: ConnectivityState = ConnectivityState.IDLE; @@ -196,7 +236,11 @@ export class InternalChannel { } this.channelzTrace = new ChannelzTrace(); - this.channelzRef = registerChannelzChannel(target, () => this.getChannelzInfo(), this.channelzEnabled); + this.channelzRef = registerChannelzChannel( + target, + () => this.getChannelzInfo(), + this.channelzEnabled + ); if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Channel created'); } @@ -217,7 +261,8 @@ export class InternalChannel { ); this.retryBufferTracker = new MessageBufferTracker( options['grpc.retry_buffer_size'] ?? DEFAULT_RETRY_BUFFER_SIZE_BYTES, - options['grpc.per_rpc_retry_buffer_size'] ?? DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES + options['grpc.per_rpc_retry_buffer_size'] ?? + DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES ); this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; const channelControlHelper: ChannelControlHelper = { @@ -233,9 +278,16 @@ export class InternalChannel { ); subchannel.throttleKeepalive(this.keepaliveTime); if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef()); + this.channelzTrace.addTrace( + 'CT_INFO', + 'Created subchannel or used existing subchannel', + subchannel.getChannelzRef() + ); } - const wrappedSubchannel = new ChannelSubchannelWrapper(subchannel, this); + const wrappedSubchannel = new ChannelSubchannelWrapper( + subchannel, + this + ); this.wrappedSubchannels.add(wrappedSubchannel); return wrappedSubchannel; }, @@ -264,7 +316,7 @@ export class InternalChannel { if (this.channelzEnabled) { this.childrenTracker.unrefChild(child); } - } + }, }; this.resolvingLoadBalancer = new ResolvingLoadBalancer( this.target, @@ -272,12 +324,22 @@ export class InternalChannel { options, (serviceConfig, configSelector) => { if (serviceConfig.retryThrottling) { - RETRY_THROTTLER_MAP.set(this.getTarget(), new RetryThrottler(serviceConfig.retryThrottling.maxTokens, serviceConfig.retryThrottling.tokenRatio, RETRY_THROTTLER_MAP.get(this.getTarget()))); + RETRY_THROTTLER_MAP.set( + this.getTarget(), + new RetryThrottler( + serviceConfig.retryThrottling.maxTokens, + serviceConfig.retryThrottling.tokenRatio, + RETRY_THROTTLER_MAP.get(this.getTarget()) + ) + ); } else { RETRY_THROTTLER_MAP.delete(this.getTarget()); } if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded'); + this.channelzTrace.addTrace( + 'CT_INFO', + 'Address resolution succeeded' + ); } this.configSelector = configSelector; this.currentResolutionError = null; @@ -292,17 +354,28 @@ export class InternalChannel { } this.configSelectionQueue = []; }); - }, - (status) => { + status => { if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"'); + this.channelzTrace.addTrace( + 'CT_WARNING', + 'Address resolution failed with code ' + + status.code + + ' and details "' + + status.details + + '"' + ); } if (this.configSelectionQueue.length > 0) { - this.trace('Name resolution failed with calls queued for config selection'); + this.trace( + 'Name resolution failed with calls queued for config selection' + ); } if (this.configSelector === null) { - this.currentResolutionError = {...restrictControlPlaneStatusCode(status.code, status.details), metadata: status.metadata}; + this.currentResolutionError = { + ...restrictControlPlaneStatusCode(status.code, status.details), + metadata: status.metadata, + }; } const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; @@ -316,9 +389,20 @@ export class InternalChannel { new MaxMessageSizeFilterFactory(this.options), new CompressionFilterFactory(this, this.options), ]); - this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2)); + this.trace( + 'Channel constructed with options ' + + JSON.stringify(options, undefined, 2) + ); const error = new Error(); - trace(LogVerbosity.DEBUG, 'channel_stacktrace', '(' + this.channelzRef.id + ') ' + 'Channel constructed \n' + error.stack?.substring(error.stack.indexOf('\n')+1)); + trace( + LogVerbosity.DEBUG, + 'channel_stacktrace', + '(' + + this.channelzRef.id + + ') ' + + 'Channel constructed \n' + + error.stack?.substring(error.stack.indexOf('\n') + 1) + ); } private getChannelzInfo(): ChannelInfo { @@ -327,12 +411,16 @@ export class InternalChannel { state: this.connectivityState, trace: this.channelzTrace, callTracker: this.callTracker, - children: this.childrenTracker.getChildLists() + children: this.childrenTracker.getChildLists(), }; } private trace(text: string, verbosityOverride?: LogVerbosity) { - trace(verbosityOverride ?? LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + uriToString(this.target) + ' ' + text); + trace( + verbosityOverride ?? LogVerbosity.DEBUG, + 'channel', + '(' + this.channelzRef.id + ') ' + uriToString(this.target) + ' ' + text + ); } private callRefTimerRef() { @@ -365,7 +453,7 @@ export class InternalChannel { watcherObject: ConnectivityStateWatcher ) { const watcherIndex = this.connectivityStateWatchers.findIndex( - (value) => value === watcherObject + value => value === watcherObject ); if (watcherIndex >= 0) { this.connectivityStateWatchers.splice(watcherIndex, 1); @@ -376,7 +464,9 @@ export class InternalChannel { trace( LogVerbosity.DEBUG, 'connectivity_state', - '(' + this.channelzRef.id + ') ' + + '(' + + this.channelzRef.id + + ') ' + uriToString(this.target) + ' ' + ConnectivityState[this.connectivityState] + @@ -384,7 +474,12 @@ export class InternalChannel { ConnectivityState[newState] ); if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + this.channelzTrace.addTrace( + 'CT_INFO', + ConnectivityState[this.connectivityState] + + ' -> ' + + ConnectivityState[newState] + ); } this.connectivityState = newState; const watchersCopy = this.connectivityStateWatchers.slice(); @@ -415,8 +510,11 @@ export class InternalChannel { this.wrappedSubchannels.delete(wrappedSubchannel); } - doPick(metadata: Metadata, extraPickInfo: {[key: string]: string}) { - return this.currentPicker.pick({metadata: metadata, extraPickInfo: extraPickInfo}); + doPick(metadata: Metadata, extraPickInfo: { [key: string]: string }) { + return this.currentPicker.pick({ + metadata: metadata, + extraPickInfo: extraPickInfo, + }); } queueCallForPick(call: LoadBalancingCall) { @@ -429,18 +527,18 @@ export class InternalChannel { if (this.configSelector) { return { type: 'SUCCESS', - config: this.configSelector(method, metadata) + config: this.configSelector(method, metadata), }; } else { if (this.currentResolutionError) { return { type: 'ERROR', - error: this.currentResolutionError - } + error: this.currentResolutionError, + }; } else { return { - type: 'NONE' - } + type: 'NONE', + }; } } } @@ -459,13 +557,17 @@ export class InternalChannel { ): LoadBalancingCall { const callNumber = getNextCallNumber(); this.trace( - 'createLoadBalancingCall [' + - callNumber + - '] method="' + - method + - '"' + 'createLoadBalancingCall [' + callNumber + '] method="' + method + '"' + ); + return new LoadBalancingCall( + this, + callConfig, + method, + host, + credentials, + deadline, + callNumber ); - return new LoadBalancingCall(this, callConfig, method, host, credentials, deadline, callNumber); } createRetryingCall( @@ -477,13 +579,19 @@ export class InternalChannel { ): RetryingCall { const callNumber = getNextCallNumber(); this.trace( - 'createRetryingCall [' + - callNumber + - '] method="' + - method + - '"' + 'createRetryingCall [' + callNumber + '] method="' + method + '"' + ); + return new RetryingCall( + this, + callConfig, + method, + host, + credentials, + deadline, + callNumber, + this.retryBufferTracker, + RETRY_THROTTLER_MAP.get(this.getTarget()) ); - return new RetryingCall(this, callConfig, method, host, credentials, deadline, callNumber, this.retryBufferTracker, RETRY_THROTTLER_MAP.get(this.getTarget())) } createInnerCall( @@ -495,9 +603,21 @@ export class InternalChannel { ): Call { // Create a RetryingCall if retries are enabled if (this.options['grpc.enable_retries'] === 0) { - return this.createLoadBalancingCall(callConfig, method, host, credentials, deadline); + return this.createLoadBalancingCall( + callConfig, + method, + host, + credentials, + deadline + ); } else { - return this.createRetryingCall(callConfig, method, host, credentials, deadline); + return this.createRetryingCall( + callConfig, + method, + host, + credentials, + deadline + ); } } @@ -524,7 +644,14 @@ export class InternalChannel { parentCall: parentCall, }; - const call = new ResolvingCall(this, method, finalOptions, this.filterStackFactory.clone(), this.credentials._getCallCredentials(), callNumber); + const call = new ResolvingCall( + this, + method, + finalOptions, + this.filterStackFactory.clone(), + this.credentials._getCallCredentials(), + callNumber + ); if (this.channelzEnabled) { this.callTracker.addCallStarted(); @@ -537,7 +664,6 @@ export class InternalChannel { }); } return call; - } close() { @@ -601,7 +727,7 @@ export class InternalChannel { /** * Get the channelz reference object for this channel. The returned value is * garbage if channelz is disabled for this channel. - * @returns + * @returns */ getChannelzRef() { return this.channelzRef; @@ -625,6 +751,12 @@ export class InternalChannel { if (this.connectivityState === ConnectivityState.SHUTDOWN) { throw new Error('Channel has been shut down'); } - return this.createResolvingCall(method, deadline, host, parentCall, propagateFlags); + return this.createResolvingCall( + method, + deadline, + host, + parentCall, + propagateFlags + ); } } diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 595d411a0..b556db0c6 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -75,7 +75,7 @@ export class ChildLoadBalancerHandler implements LoadBalancer { removeChannelzChild(child: ChannelRef | SubchannelRef) { this.parent.channelControlHelper.removeChannelzChild(child); } - + private calledByPendingChild(): boolean { return this.child === this.parent.pendingChild; } @@ -86,7 +86,10 @@ export class ChildLoadBalancerHandler implements LoadBalancer { constructor(private readonly channelControlHelper: ChannelControlHelper) {} - protected configUpdateRequiresNewPolicyInstance(oldConfig: LoadBalancingConfig, newConfig: LoadBalancingConfig): boolean { + protected configUpdateRequiresNewPolicyInstance( + oldConfig: LoadBalancingConfig, + newConfig: LoadBalancingConfig + ): boolean { return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName(); } diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 2f72a9625..0c61065ec 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -15,18 +15,41 @@ * */ -import { ChannelOptions } from "./channel-options"; -import { ConnectivityState } from "./connectivity-state"; -import { LogVerbosity, Status } from "./constants"; -import { durationToMs, isDuration, msToDuration } from "./duration"; -import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental"; -import { BaseFilter, Filter, FilterFactory } from "./filter"; -import { getFirstUsableConfig, LoadBalancer, LoadBalancingConfig, validateLoadBalancingConfig } from "./load-balancer"; -import { ChildLoadBalancerHandler } from "./load-balancer-child-handler"; -import { PickArgs, Picker, PickResult, PickResultType, QueuePicker, UnavailablePicker } from "./picker"; -import { Subchannel } from "./subchannel"; -import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address"; -import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface"; +import { ChannelOptions } from './channel-options'; +import { ConnectivityState } from './connectivity-state'; +import { LogVerbosity, Status } from './constants'; +import { durationToMs, isDuration, msToDuration } from './duration'; +import { + ChannelControlHelper, + createChildChannelControlHelper, + registerLoadBalancerType, +} from './experimental'; +import { BaseFilter, Filter, FilterFactory } from './filter'; +import { + getFirstUsableConfig, + LoadBalancer, + LoadBalancingConfig, + validateLoadBalancingConfig, +} from './load-balancer'; +import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; +import { + PickArgs, + Picker, + PickResult, + PickResultType, + QueuePicker, + UnavailablePicker, +} from './picker'; +import { Subchannel } from './subchannel'; +import { + SubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; +import { + BaseSubchannelWrapper, + ConnectivityStateListener, + SubchannelInterface, +} from './subchannel-interface'; import * as logging from './logging'; const TRACER_NAME = 'outlier_detection'; @@ -37,7 +60,8 @@ function trace(text: string): void { const TYPE_NAME = 'outlier_detection'; -const OUTLIER_DETECTION_ENABLED = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; +const OUTLIER_DETECTION_ENABLED = + (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; export interface SuccessRateEjectionConfig { readonly stdev_factor: number; @@ -57,33 +81,66 @@ const defaultSuccessRateEjectionConfig: SuccessRateEjectionConfig = { stdev_factor: 1900, enforcement_percentage: 100, minimum_hosts: 5, - request_volume: 100 + request_volume: 100, }; -const defaultFailurePercentageEjectionConfig: FailurePercentageEjectionConfig = { - threshold: 85, - enforcement_percentage: 100, - minimum_hosts: 5, - request_volume: 50 -} - -type TypeofValues = 'object' | 'boolean' | 'function' | 'number' | 'string' | 'undefined'; - -function validateFieldType(obj: any, fieldName: string, expectedType: TypeofValues, objectName?: string) { +const defaultFailurePercentageEjectionConfig: FailurePercentageEjectionConfig = + { + threshold: 85, + enforcement_percentage: 100, + minimum_hosts: 5, + request_volume: 50, + }; + +type TypeofValues = + | 'object' + | 'boolean' + | 'function' + | 'number' + | 'string' + | 'undefined'; + +function validateFieldType( + obj: any, + fieldName: string, + expectedType: TypeofValues, + objectName?: string +) { if (fieldName in obj && typeof obj[fieldName] !== expectedType) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; - throw new Error(`outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[fieldName]}`); + throw new Error( + `outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[ + fieldName + ]}` + ); } } -function validatePositiveDuration(obj: any, fieldName: string, objectName?: string) { +function validatePositiveDuration( + obj: any, + fieldName: string, + objectName?: string +) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; if (fieldName in obj) { if (!isDuration(obj[fieldName])) { - throw new Error(`outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[fieldName]}`); - } - if (!(obj[fieldName].seconds >= 0 && obj[fieldName].seconds <= 315_576_000_000 && obj[fieldName].nanos >= 0 && obj[fieldName].nanos <= 999_999_999)) { - throw new Error(`outlier detection config ${fullFieldName} parse error: values out of range for non-negative Duaration`); + throw new Error( + `outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[ + fieldName + ]}` + ); + } + if ( + !( + obj[fieldName].seconds >= 0 && + obj[fieldName].seconds <= 315_576_000_000 && + obj[fieldName].nanos >= 0 && + obj[fieldName].nanos <= 999_999_999 + ) + ) { + throw new Error( + `outlier detection config ${fullFieldName} parse error: values out of range for non-negative Duaration` + ); } } } @@ -92,11 +149,15 @@ function validatePercentage(obj: any, fieldName: string, objectName?: string) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; validateFieldType(obj, fieldName, 'number', objectName); if (fieldName in obj && !(obj[fieldName] >= 0 && obj[fieldName] <= 100)) { - throw new Error(`outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)`); + throw new Error( + `outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)` + ); } } -export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig { +export class OutlierDetectionLoadBalancingConfig + implements LoadBalancingConfig +{ private readonly intervalMs: number; private readonly baseEjectionTimeMs: number; private readonly maxEjectionTimeMs: number; @@ -117,8 +178,15 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig this.baseEjectionTimeMs = baseEjectionTimeMs ?? 30_000; this.maxEjectionTimeMs = maxEjectionTimeMs ?? 300_000; this.maxEjectionPercent = maxEjectionPercent ?? 10; - this.successRateEjection = successRateEjection ? {...defaultSuccessRateEjectionConfig, ...successRateEjection} : null; - this.failurePercentageEjection = failurePercentageEjection ? {...defaultFailurePercentageEjectionConfig, ...failurePercentageEjection}: null; + this.successRateEjection = successRateEjection + ? { ...defaultSuccessRateEjectionConfig, ...successRateEjection } + : null; + this.failurePercentageEjection = failurePercentageEjection + ? { + ...defaultFailurePercentageEjectionConfig, + ...failurePercentageEjection, + } + : null; } getLoadBalancerName(): string { return TYPE_NAME; @@ -131,7 +199,7 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig max_ejection_percent: this.maxEjectionPercent, success_rate_ejection: this.successRateEjection, failure_percentage_ejection: this.failurePercentageEjection, - child_policy: this.childPolicy.map(policy => policy.toJsonObject()) + child_policy: this.childPolicy.map(policy => policy.toJsonObject()), }; } @@ -157,8 +225,18 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig return this.childPolicy; } - copyWithChildPolicy(childPolicy: LoadBalancingConfig[]): OutlierDetectionLoadBalancingConfig { - return new OutlierDetectionLoadBalancingConfig(this.intervalMs, this.baseEjectionTimeMs, this.maxEjectionTimeMs, this.maxEjectionPercent, this.successRateEjection, this.failurePercentageEjection, childPolicy); + copyWithChildPolicy( + childPolicy: LoadBalancingConfig[] + ): OutlierDetectionLoadBalancingConfig { + return new OutlierDetectionLoadBalancingConfig( + this.intervalMs, + this.baseEjectionTimeMs, + this.maxEjectionTimeMs, + this.maxEjectionPercent, + this.successRateEjection, + this.failurePercentageEjection, + childPolicy + ); } static createFromJson(obj: any): OutlierDetectionLoadBalancingConfig { @@ -168,21 +246,62 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig validatePercentage(obj, 'max_ejection_percent'); if ('success_rate_ejection' in obj) { if (typeof obj.success_rate_ejection !== 'object') { - throw new Error('outlier detection config success_rate_ejection must be an object'); + throw new Error( + 'outlier detection config success_rate_ejection must be an object' + ); } - validateFieldType(obj.success_rate_ejection, 'stdev_factor', 'number', 'success_rate_ejection'); - validatePercentage(obj.success_rate_ejection, 'enforcement_percentage', 'success_rate_ejection'); - validateFieldType(obj.success_rate_ejection, 'minimum_hosts', 'number', 'success_rate_ejection'); - validateFieldType(obj.success_rate_ejection, 'request_volume', 'number', 'success_rate_ejection'); + validateFieldType( + obj.success_rate_ejection, + 'stdev_factor', + 'number', + 'success_rate_ejection' + ); + validatePercentage( + obj.success_rate_ejection, + 'enforcement_percentage', + 'success_rate_ejection' + ); + validateFieldType( + obj.success_rate_ejection, + 'minimum_hosts', + 'number', + 'success_rate_ejection' + ); + validateFieldType( + obj.success_rate_ejection, + 'request_volume', + 'number', + 'success_rate_ejection' + ); } if ('failure_percentage_ejection' in obj) { if (typeof obj.failure_percentage_ejection !== 'object') { - throw new Error('outlier detection config failure_percentage_ejection must be an object'); + throw new Error( + 'outlier detection config failure_percentage_ejection must be an object' + ); } - validatePercentage(obj.failure_percentage_ejection, 'threshold', 'failure_percentage_ejection'); - validatePercentage(obj.failure_percentage_ejection, 'enforcement_percentage', 'failure_percentage_ejection'); - validateFieldType(obj.failure_percentage_ejection, 'minimum_hosts', 'number', 'failure_percentage_ejection'); - validateFieldType(obj.failure_percentage_ejection, 'request_volume', 'number', 'failure_percentage_ejection'); + validatePercentage( + obj.failure_percentage_ejection, + 'threshold', + 'failure_percentage_ejection' + ); + validatePercentage( + obj.failure_percentage_ejection, + 'enforcement_percentage', + 'failure_percentage_ejection' + ); + validateFieldType( + obj.failure_percentage_ejection, + 'minimum_hosts', + 'number', + 'failure_percentage_ejection' + ); + validateFieldType( + obj.failure_percentage_ejection, + 'request_volume', + 'number', + 'failure_percentage_ejection' + ); } return new OutlierDetectionLoadBalancingConfig( @@ -197,22 +316,30 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig } } -class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { +class OutlierDetectionSubchannelWrapper + extends BaseSubchannelWrapper + implements SubchannelInterface +{ private childSubchannelState: ConnectivityState; private stateListeners: ConnectivityStateListener[] = []; - private ejected: boolean = false; - private refCount: number = 0; - constructor(childSubchannel: SubchannelInterface, private mapEntry?: MapEntry) { + private ejected = false; + private refCount = 0; + constructor( + childSubchannel: SubchannelInterface, + private mapEntry?: MapEntry + ) { super(childSubchannel); this.childSubchannelState = childSubchannel.getConnectivityState(); - childSubchannel.addConnectivityStateListener((subchannel, previousState, newState, keepaliveTime) => { - this.childSubchannelState = newState; - if (!this.ejected) { - for (const listener of this.stateListeners) { - listener(this, previousState, newState, keepaliveTime); + childSubchannel.addConnectivityStateListener( + (subchannel, previousState, newState, keepaliveTime) => { + this.childSubchannelState = newState; + if (!this.ejected) { + for (const listener of this.stateListeners) { + listener(this, previousState, newState, keepaliveTime); + } } } - }); + ); } getConnectivityState(): ConnectivityState { @@ -265,14 +392,24 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements eject() { this.ejected = true; for (const listener of this.stateListeners) { - listener(this, this.childSubchannelState, ConnectivityState.TRANSIENT_FAILURE, -1); + listener( + this, + this.childSubchannelState, + ConnectivityState.TRANSIENT_FAILURE, + -1 + ); } } uneject() { this.ejected = false; for (const listener of this.stateListeners) { - listener(this, ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState, -1); + listener( + this, + ConnectivityState.TRANSIENT_FAILURE, + this.childSubchannelState, + -1 + ); } } @@ -293,8 +430,8 @@ interface CallCountBucket { function createEmptyBucket(): CallCountBucket { return { success: 0, - failure: 0 - } + failure: 0, + }; } class CallCounter { @@ -330,7 +467,8 @@ class OutlierDetectionPicker implements Picker { pick(pickArgs: PickArgs): PickResult { const wrappedPick = this.wrappedPicker.pick(pickArgs); if (wrappedPick.pickResultType === PickResultType.COMPLETE) { - const subchannelWrapper = wrappedPick.subchannel as OutlierDetectionSubchannelWrapper; + const subchannelWrapper = + wrappedPick.subchannel as OutlierDetectionSubchannelWrapper; const mapEntry = subchannelWrapper.getMapEntry(); if (mapEntry) { let onCallEnded = wrappedPick.onCallEnded; @@ -347,19 +485,18 @@ class OutlierDetectionPicker implements Picker { return { ...wrappedPick, subchannel: subchannelWrapper.getWrappedSubchannel(), - onCallEnded: onCallEnded + onCallEnded: onCallEnded, }; } else { return { ...wrappedPick, - subchannel: subchannelWrapper.getWrappedSubchannel() - } + subchannel: subchannelWrapper.getWrappedSubchannel(), + }; } } else { return wrappedPick; } } - } export class OutlierDetectionLoadBalancer implements LoadBalancer { @@ -370,34 +507,52 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private timerStartTime: Date | null = null; constructor(channelControlHelper: ChannelControlHelper) { - this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { - createSubchannel: (subchannelAddress: SubchannelAddress, subchannelArgs: ChannelOptions) => { - const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); - const mapEntry = this.addressMap.get(subchannelAddressToString(subchannelAddress)); - const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry); - if (mapEntry?.currentEjectionTimestamp !== null) { - // If the address is ejected, propagate that to the new subchannel wrapper - subchannelWrapper.eject(); - } - mapEntry?.subchannelWrappers.push(subchannelWrapper); - return subchannelWrapper; - }, - updateState: (connectivityState: ConnectivityState, picker: Picker) => { - if (connectivityState === ConnectivityState.READY) { - channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled())); - } else { - channelControlHelper.updateState(connectivityState, picker); - } - } - })); + this.childBalancer = new ChildLoadBalancerHandler( + createChildChannelControlHelper(channelControlHelper, { + createSubchannel: ( + subchannelAddress: SubchannelAddress, + subchannelArgs: ChannelOptions + ) => { + const originalSubchannel = channelControlHelper.createSubchannel( + subchannelAddress, + subchannelArgs + ); + const mapEntry = this.addressMap.get( + subchannelAddressToString(subchannelAddress) + ); + const subchannelWrapper = new OutlierDetectionSubchannelWrapper( + originalSubchannel, + mapEntry + ); + if (mapEntry?.currentEjectionTimestamp !== null) { + // If the address is ejected, propagate that to the new subchannel wrapper + subchannelWrapper.eject(); + } + mapEntry?.subchannelWrappers.push(subchannelWrapper); + return subchannelWrapper; + }, + updateState: (connectivityState: ConnectivityState, picker: Picker) => { + if (connectivityState === ConnectivityState.READY) { + channelControlHelper.updateState( + connectivityState, + new OutlierDetectionPicker(picker, this.isCountingEnabled()) + ); + } else { + channelControlHelper.updateState(connectivityState, picker); + } + }, + }) + ); this.ejectionTimer = setInterval(() => {}, 0); clearInterval(this.ejectionTimer); } private isCountingEnabled(): boolean { - return this.latestConfig !== null && - (this.latestConfig.getSuccessRateEjectionConfig() !== null || - this.latestConfig.getFailurePercentageEjectionConfig() !== null); + return ( + this.latestConfig !== null && + (this.latestConfig.getSuccessRateEjectionConfig() !== null || + this.latestConfig.getFailurePercentageEjectionConfig() !== null) + ); } private getCurrentEjectionPercent() { @@ -422,23 +577,41 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { // Step 1 const targetRequestVolume = successRateConfig.request_volume; let addresesWithTargetVolume = 0; - const successRates: number[] = [] + const successRates: number[] = []; for (const [address, mapEntry] of this.addressMap) { const successes = mapEntry.counter.getLastSuccesses(); const failures = mapEntry.counter.getLastFailures(); - trace('Stats for ' + address + ': successes=' + successes + ' failures=' + failures + ' targetRequestVolume=' + targetRequestVolume); + trace( + 'Stats for ' + + address + + ': successes=' + + successes + + ' failures=' + + failures + + ' targetRequestVolume=' + + targetRequestVolume + ); if (successes + failures >= targetRequestVolume) { addresesWithTargetVolume += 1; - successRates.push(successes/(successes + failures)); + successRates.push(successes / (successes + failures)); } } - trace('Found ' + addresesWithTargetVolume + ' success rate candidates; currentEjectionPercent=' + this.getCurrentEjectionPercent() + ' successRates=[' + successRates + ']'); + trace( + 'Found ' + + addresesWithTargetVolume + + ' success rate candidates; currentEjectionPercent=' + + this.getCurrentEjectionPercent() + + ' successRates=[' + + successRates + + ']' + ); if (addresesWithTargetVolume < successRateConfig.minimum_hosts) { return; } // Step 2 - const successRateMean = successRates.reduce((a, b) => a + b) / successRates.length; + const successRateMean = + successRates.reduce((a, b) => a + b) / successRates.length; let successRateDeviationSum = 0; for (const rate of successRates) { const deviation = rate - successRateMean; @@ -446,13 +619,20 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } const successRateVariance = successRateDeviationSum / successRates.length; const successRateStdev = Math.sqrt(successRateVariance); - const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000); - trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold); + const ejectionThreshold = + successRateMean - + successRateStdev * (successRateConfig.stdev_factor / 1000); + trace( + 'stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold + ); // Step 3 for (const [address, mapEntry] of this.addressMap.entries()) { // Step 3.i - if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) { + if ( + this.getCurrentEjectionPercent() >= + this.latestConfig.getMaxEjectionPercent() + ) { break; } // Step 3.ii @@ -466,7 +646,14 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { trace('Checking candidate ' + address + ' successRate=' + successRate); if (successRate < ejectionThreshold) { const randomNumber = Math.random() * 100; - trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + successRateConfig.enforcement_percentage); + trace( + 'Candidate ' + + address + + ' randomNumber=' + + randomNumber + + ' enforcement_percentage=' + + successRateConfig.enforcement_percentage + ); if (randomNumber < successRateConfig.enforcement_percentage) { trace('Ejecting candidate ' + address); this.eject(mapEntry, ejectionTimestamp); @@ -479,11 +666,17 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (!this.latestConfig) { return; } - const failurePercentageConfig = this.latestConfig.getFailurePercentageEjectionConfig() + const failurePercentageConfig = + this.latestConfig.getFailurePercentageEjectionConfig(); if (!failurePercentageConfig) { return; } - trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume); + trace( + 'Running failure percentage check. threshold=' + + failurePercentageConfig.threshold + + ' request volume threshold=' + + failurePercentageConfig.request_volume + ); // Step 1 let addressesWithTargetVolume = 0; for (const mapEntry of this.addressMap.values()) { @@ -496,11 +689,14 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (addressesWithTargetVolume < failurePercentageConfig.minimum_hosts) { return; } - + // Step 2 for (const [address, mapEntry] of this.addressMap.entries()) { // Step 2.i - if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) { + if ( + this.getCurrentEjectionPercent() >= + this.latestConfig.getMaxEjectionPercent() + ) { break; } // Step 2.ii @@ -514,7 +710,14 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const failurePercentage = (failures * 100) / (failures + successes); if (failurePercentage > failurePercentageConfig.threshold) { const randomNumber = Math.random() * 100; - trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + failurePercentageConfig.enforcement_percentage); + trace( + 'Candidate ' + + address + + ' randomNumber=' + + randomNumber + + ' enforcement_percentage=' + + failurePercentageConfig.enforcement_percentage + ); if (randomNumber < failurePercentageConfig.enforcement_percentage) { trace('Ejecting candidate ' + address); this.eject(mapEntry, ejectionTimestamp); @@ -572,8 +775,16 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } else { const baseEjectionTimeMs = this.latestConfig.getBaseEjectionTimeMs(); const maxEjectionTimeMs = this.latestConfig.getMaxEjectionTimeMs(); - const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime()); - returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs))); + const returnTime = new Date( + mapEntry.currentEjectionTimestamp.getTime() + ); + returnTime.setMilliseconds( + returnTime.getMilliseconds() + + Math.min( + baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, + Math.max(baseEjectionTimeMs, maxEjectionTimeMs) + ) + ); if (returnTime < new Date()) { trace('Unejecting ' + address); this.uneject(mapEntry); @@ -582,7 +793,11 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } } - updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) { return; } @@ -597,7 +812,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { counter: new CallCounter(), currentEjectionTimestamp: null, ejectionTimeMultiplier: 0, - subchannelWrappers: [] + subchannelWrappers: [], }); } } @@ -613,11 +828,16 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { ); this.childBalancer.updateAddressList(addressList, childPolicy, attributes); - if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) { + if ( + lbConfig.getSuccessRateEjectionConfig() || + lbConfig.getFailurePercentageEjectionConfig() + ) { if (this.timerStartTime) { trace('Previous timer existed. Replacing timer'); clearTimeout(this.ejectionTimer); - const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime()); + const remainingDelay = + lbConfig.getIntervalMs() - + (new Date().getTime() - this.timerStartTime.getTime()); this.startTimer(remainingDelay); } else { trace('Starting new timer'); @@ -654,6 +874,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { export function setup() { if (OUTLIER_DETECTION_ENABLED) { - registerLoadBalancerType(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig); + registerLoadBalancerType( + TYPE_NAME, + OutlierDetectionLoadBalancer, + OutlierDetectionLoadBalancingConfig + ); } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 41d21a2ea..e91bb3c5a 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -38,7 +38,10 @@ import { } from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { SubchannelInterface, ConnectivityStateListener } from './subchannel-interface'; +import { + SubchannelInterface, + ConnectivityStateListener, +} from './subchannel-interface'; const TRACER_NAME = 'pick_first'; @@ -86,7 +89,7 @@ class PickFirstPicker implements Picker { subchannel: this.subchannel, status: null, onCallStarted: null, - onCallEnded: null + onCallEnded: null, }; } } @@ -169,7 +172,8 @@ export class PickFirstLoadBalancer implements LoadBalancer { * connecting to the next one instead of waiting for the connection * delay timer. */ if ( - subchannel.getRealSubchannel() === this.subchannels[this.currentSubchannelIndex].getRealSubchannel() && + subchannel.getRealSubchannel() === + this.subchannels[this.currentSubchannelIndex].getRealSubchannel() && newState === ConnectivityState.TRANSIENT_FAILURE ) { this.startNextSubchannelConnecting(); @@ -232,7 +236,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { subchannel.removeConnectivityStateListener( this.pickedSubchannelStateListener ); - this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); + this.channelControlHelper.removeChannelzChild( + subchannel.getChannelzRef() + ); if (this.subchannels.length > 0) { if (this.triedAllSubchannels) { let newLBState: ConnectivityState; @@ -344,7 +350,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { for (const subchannel of this.subchannels) { subchannel.removeConnectivityStateListener(this.subchannelStateListener); subchannel.unref(); - this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); + this.channelControlHelper.removeChannelzChild( + subchannel.getChannelzRef() + ); } this.currentSubchannelIndex = 0; this.subchannelStateCounts = { @@ -368,11 +376,11 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.resetSubchannelList(); trace( 'Connect to address list ' + - this.latestAddressList.map((address) => + this.latestAddressList.map(address => subchannelAddressToString(address) ) ); - this.subchannels = this.latestAddressList.map((address) => + this.subchannels = this.latestAddressList.map(address => this.channelControlHelper.createSubchannel(address, {}) ); for (const subchannel of this.subchannels) { @@ -422,7 +430,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.subchannels.length === 0 || this.latestAddressList.length !== addressList.length || !this.latestAddressList.every( - (value, index) => addressList[index] && subchannelAddressEqual(addressList[index], value) + (value, index) => + addressList[index] && + subchannelAddressEqual(addressList[index], value) ) ) { this.latestAddressList = addressList; @@ -463,7 +473,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { currentPick.removeConnectivityStateListener( this.pickedSubchannelStateListener ); - this.channelControlHelper.removeChannelzChild(currentPick.getChannelzRef()); + this.channelControlHelper.removeChannelzChild( + currentPick.getChannelzRef() + ); } } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 9e91038ff..f389fefc0 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -36,7 +36,10 @@ import { } from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { ConnectivityStateListener, SubchannelInterface } from './subchannel-interface'; +import { + ConnectivityStateListener, + SubchannelInterface, +} from './subchannel-interface'; const TRACER_NAME = 'round_robin'; @@ -79,7 +82,7 @@ class RoundRobinPicker implements Picker { subchannel: pickedSubchannel, status: null, onCallStarted: null, - onCallEnded: null + onCallEnded: null, }; } @@ -121,13 +124,15 @@ export class RoundRobinLoadBalancer implements LoadBalancer { } private countSubchannelsWithState(state: ConnectivityState) { - return this.subchannels.filter(subchannel => subchannel.getConnectivityState() === state).length; + return this.subchannels.filter( + subchannel => subchannel.getConnectivityState() === state + ).length; } private calculateAndUpdateState() { if (this.countSubchannelsWithState(ConnectivityState.READY) > 0) { const readySubchannels = this.subchannels.filter( - (subchannel) => + subchannel => subchannel.getConnectivityState() === ConnectivityState.READY ); let index = 0; @@ -143,7 +148,9 @@ export class RoundRobinLoadBalancer implements LoadBalancer { ConnectivityState.READY, new RoundRobinPicker(readySubchannels, index) ); - } else if (this.countSubchannelsWithState(ConnectivityState.CONNECTING) > 0) { + } else if ( + this.countSubchannelsWithState(ConnectivityState.CONNECTING) > 0 + ) { this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); } else if ( this.countSubchannelsWithState(ConnectivityState.TRANSIENT_FAILURE) > 0 @@ -176,7 +183,9 @@ export class RoundRobinLoadBalancer implements LoadBalancer { for (const subchannel of this.subchannels) { subchannel.removeConnectivityStateListener(this.subchannelStateListener); subchannel.unref(); - this.channelControlHelper.removeChannelzChild(subchannel.getChannelzRef()); + this.channelControlHelper.removeChannelzChild( + subchannel.getChannelzRef() + ); } this.subchannels = []; } @@ -188,9 +197,9 @@ export class RoundRobinLoadBalancer implements LoadBalancer { this.resetSubchannelList(); trace( 'Connect to address list ' + - addressList.map((address) => subchannelAddressToString(address)) + addressList.map(address => subchannelAddressToString(address)) ); - this.subchannels = addressList.map((address) => + this.subchannels = addressList.map(address => this.channelControlHelper.createSubchannel(address, {}) ); for (const subchannel of this.subchannels) { diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index 48930c7db..8e859495e 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -58,16 +58,28 @@ export interface ChannelControlHelper { * parent while letting others pass through to the parent unmodified. This * allows other code to create these children without needing to know about * all of the methods to be passed through. - * @param parent - * @param overrides + * @param parent + * @param overrides */ -export function createChildChannelControlHelper(parent: ChannelControlHelper, overrides: Partial): ChannelControlHelper { +export function createChildChannelControlHelper( + parent: ChannelControlHelper, + overrides: Partial +): ChannelControlHelper { return { - createSubchannel: overrides.createSubchannel?.bind(overrides) ?? parent.createSubchannel.bind(parent), - updateState: overrides.updateState?.bind(overrides) ?? parent.updateState.bind(parent), - requestReresolution: overrides.requestReresolution?.bind(overrides) ?? parent.requestReresolution.bind(parent), - addChannelzChild: overrides.addChannelzChild?.bind(overrides) ?? parent.addChannelzChild.bind(parent), - removeChannelzChild: overrides.removeChannelzChild?.bind(overrides) ?? parent.removeChannelzChild.bind(parent) + createSubchannel: + overrides.createSubchannel?.bind(overrides) ?? + parent.createSubchannel.bind(parent), + updateState: + overrides.updateState?.bind(overrides) ?? parent.updateState.bind(parent), + requestReresolution: + overrides.requestReresolution?.bind(overrides) ?? + parent.requestReresolution.bind(parent), + addChannelzChild: + overrides.addChannelzChild?.bind(overrides) ?? + parent.addChannelzChild.bind(parent), + removeChannelzChild: + overrides.removeChannelzChild?.bind(overrides) ?? + parent.removeChannelzChild.bind(parent), }; } diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index f74933983..1a97c89fc 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -15,20 +15,25 @@ * */ -import { CallCredentials } from "./call-credentials"; -import { Call, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; -import { SubchannelCall } from "./subchannel-call"; -import { ConnectivityState } from "./connectivity-state"; -import { LogVerbosity, Status } from "./constants"; -import { Deadline, getDeadlineTimeoutString } from "./deadline"; -import { FilterStack, FilterStackFactory } from "./filter-stack"; -import { InternalChannel } from "./internal-channel"; -import { Metadata } from "./metadata"; -import { PickResultType } from "./picker"; -import { CallConfig } from "./resolver"; -import { splitHostPort } from "./uri-parser"; +import { CallCredentials } from './call-credentials'; +import { + Call, + InterceptingListener, + MessageContext, + StatusObject, +} from './call-interface'; +import { SubchannelCall } from './subchannel-call'; +import { ConnectivityState } from './connectivity-state'; +import { LogVerbosity, Status } from './constants'; +import { Deadline, getDeadlineTimeoutString } from './deadline'; +import { FilterStack, FilterStackFactory } from './filter-stack'; +import { InternalChannel } from './internal-channel'; +import { Metadata } from './metadata'; +import { PickResultType } from './picker'; +import { CallConfig } from './resolver'; +import { splitHostPort } from './uri-parser'; import * as logging from './logging'; -import { restrictControlPlaneStatusCode } from "./control-plane-status"; +import { restrictControlPlaneStatusCode } from './control-plane-status'; import * as http2 from 'http2'; const TRACER_NAME = 'load_balancing_call'; @@ -39,14 +44,16 @@ export interface StatusObjectWithProgress extends StatusObject { progress: RpcProgress; } -export interface LoadBalancingCallInterceptingListener extends InterceptingListener { +export interface LoadBalancingCallInterceptingListener + extends InterceptingListener { onReceiveStatus(status: StatusObjectWithProgress): void; } export class LoadBalancingCall implements Call { private child: SubchannelCall | null = null; private readPending = false; - private pendingMessage: {context: MessageContext, message: Buffer} | null = null; + private pendingMessage: { context: MessageContext; message: Buffer } | null = + null; private pendingHalfClose = false; private pendingChildStatus: StatusObject | null = null; private ended = false; @@ -58,7 +65,7 @@ export class LoadBalancingCall implements Call { private readonly channel: InternalChannel, private readonly callConfig: CallConfig, private readonly methodName: string, - private readonly host : string, + private readonly host: string, private readonly credentials: CallCredentials, private readonly deadline: Deadline, private readonly callNumber: number @@ -88,8 +95,14 @@ export class LoadBalancingCall implements Call { private outputStatus(status: StatusObject, progress: RpcProgress) { if (!this.ended) { this.ended = true; - this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"'); - const finalStatus = {...status, progress}; + this.trace( + 'ended with status: code=' + + status.code + + ' details="' + + status.details + + '"' + ); + const finalStatus = { ...status, progress }; this.listener?.onReceiveStatus(finalStatus); this.onCallEnded?.(finalStatus.code); } @@ -102,11 +115,17 @@ export class LoadBalancingCall implements Call { if (!this.metadata) { throw new Error('doPick called before start'); } - this.trace('Pick called') - const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation); - const subchannelString = pickResult.subchannel ? - '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : - '' + pickResult.subchannel; + this.trace('Pick called'); + const pickResult = this.channel.doPick( + this.metadata, + this.callConfig.pickInformation + ); + const subchannelString = pickResult.subchannel + ? '(' + + pickResult.subchannel.getChannelzRef().id + + ') ' + + pickResult.subchannel.getAddress() + : '' + pickResult.subchannel; this.trace( 'Pick result: ' + PickResultType[pickResult.pickResultType] + @@ -119,111 +138,147 @@ export class LoadBalancingCall implements Call { ); switch (pickResult.pickResultType) { case PickResultType.COMPLETE: - this.credentials.generateMetadata({service_url: this.serviceUrl}).then( - (credsMetadata) => { - const finalMetadata = this.metadata!.clone(); - finalMetadata.merge(credsMetadata); - if (finalMetadata.get('authorization').length > 1) { - this.outputStatus( - { - code: Status.INTERNAL, - details: '"authorization" metadata cannot have multiple values', - metadata: new Metadata() - }, - 'PROCESSED' - ); - } - if (pickResult.subchannel!.getConnectivityState() !== ConnectivityState.READY) { - this.trace( - 'Picked subchannel ' + - subchannelString + - ' has state ' + - ConnectivityState[pickResult.subchannel!.getConnectivityState()] + - ' after getting credentials metadata. Retrying pick' - ); - this.doPick(); - return; - } + this.credentials + .generateMetadata({ service_url: this.serviceUrl }) + .then( + credsMetadata => { + const finalMetadata = this.metadata!.clone(); + finalMetadata.merge(credsMetadata); + if (finalMetadata.get('authorization').length > 1) { + this.outputStatus( + { + code: Status.INTERNAL, + details: + '"authorization" metadata cannot have multiple values', + metadata: new Metadata(), + }, + 'PROCESSED' + ); + } + if ( + pickResult.subchannel!.getConnectivityState() !== + ConnectivityState.READY + ) { + this.trace( + 'Picked subchannel ' + + subchannelString + + ' has state ' + + ConnectivityState[ + pickResult.subchannel!.getConnectivityState() + ] + + ' after getting credentials metadata. Retrying pick' + ); + this.doPick(); + return; + } - if (this.deadline !== Infinity) { - finalMetadata.set('grpc-timeout', getDeadlineTimeoutString(this.deadline)); - } - try { - this.child = pickResult.subchannel!.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, { - onReceiveMetadata: metadata => { - this.trace('Received metadata'); - this.listener!.onReceiveMetadata(metadata); - }, - onReceiveMessage: message => { - this.trace('Received message'); - this.listener!.onReceiveMessage(message); - }, - onReceiveStatus: status => { - this.trace('Received status'); - if (status.rstCode === http2.constants.NGHTTP2_REFUSED_STREAM) { - this.outputStatus(status, 'REFUSED'); - } else { - this.outputStatus(status, 'PROCESSED'); - } - } - }); - } catch (error) { + if (this.deadline !== Infinity) { + finalMetadata.set( + 'grpc-timeout', + getDeadlineTimeoutString(this.deadline) + ); + } + try { + this.child = pickResult + .subchannel!.getRealSubchannel() + .createCall(finalMetadata, this.host, this.methodName, { + onReceiveMetadata: metadata => { + this.trace('Received metadata'); + this.listener!.onReceiveMetadata(metadata); + }, + onReceiveMessage: message => { + this.trace('Received message'); + this.listener!.onReceiveMessage(message); + }, + onReceiveStatus: status => { + this.trace('Received status'); + if ( + status.rstCode === + http2.constants.NGHTTP2_REFUSED_STREAM + ) { + this.outputStatus(status, 'REFUSED'); + } else { + this.outputStatus(status, 'PROCESSED'); + } + }, + }); + } catch (error) { + this.trace( + 'Failed to start call on picked subchannel ' + + subchannelString + + ' with error ' + + (error as Error).message + ); + this.outputStatus( + { + code: Status.INTERNAL, + details: + 'Failed to start HTTP/2 stream with error ' + + (error as Error).message, + metadata: new Metadata(), + }, + 'NOT_STARTED' + ); + return; + } + this.callConfig.onCommitted?.(); + pickResult.onCallStarted?.(); + this.onCallEnded = pickResult.onCallEnded; this.trace( - 'Failed to start call on picked subchannel ' + - subchannelString + - ' with error ' + - (error as Error).message + 'Created child call [' + this.child.getCallNumber() + ']' + ); + if (this.readPending) { + this.child.startRead(); + } + if (this.pendingMessage) { + this.child.sendMessageWithContext( + this.pendingMessage.context, + this.pendingMessage.message + ); + } + if (this.pendingHalfClose) { + this.child.halfClose(); + } + }, + (error: Error & { code: number }) => { + // We assume the error code isn't 0 (Status.OK) + const { code, details } = restrictControlPlaneStatusCode( + typeof error.code === 'number' ? error.code : Status.UNKNOWN, + `Getting metadata from plugin failed with error: ${error.message}` ); this.outputStatus( { - code: Status.INTERNAL, - details: 'Failed to start HTTP/2 stream with error ' + (error as Error).message, - metadata: new Metadata() + code: code, + details: details, + metadata: new Metadata(), }, - 'NOT_STARTED' + 'PROCESSED' ); - return; - } - this.callConfig.onCommitted?.(); - pickResult.onCallStarted?.(); - this.onCallEnded = pickResult.onCallEnded; - this.trace('Created child call [' + this.child.getCallNumber() + ']'); - if (this.readPending) { - this.child.startRead(); } - if (this.pendingMessage) { - this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message); - } - if (this.pendingHalfClose) { - this.child.halfClose(); - } - }, (error: Error & { code: number }) => { - // We assume the error code isn't 0 (Status.OK) - const {code, details} = restrictControlPlaneStatusCode( - typeof error.code === 'number' ? error.code : Status.UNKNOWN, - `Getting metadata from plugin failed with error: ${error.message}` - ) - this.outputStatus( - { - code: code, - details: details, - metadata: new Metadata() - }, - 'PROCESSED' - ); - } - ); + ); break; case PickResultType.DROP: - const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); - this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'DROP'); + const { code, details } = restrictControlPlaneStatusCode( + pickResult.status!.code, + pickResult.status!.details + ); + this.outputStatus( + { code, details, metadata: pickResult.status!.metadata }, + 'DROP' + ); break; case PickResultType.TRANSIENT_FAILURE: if (this.metadata.getOptions().waitForReady) { this.channel.queueCallForPick(this); } else { - const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); - this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'PROCESSED'); + const { code, details } = restrictControlPlaneStatusCode( + pickResult.status!.code, + pickResult.status!.details + ); + this.outputStatus( + { code, details, metadata: pickResult.status!.metadata }, + 'PROCESSED' + ); } break; case PickResultType.QUEUE: @@ -232,14 +287,22 @@ export class LoadBalancingCall implements Call { } cancelWithStatus(status: Status, details: string): void { - this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.trace( + 'cancelWithStatus code: ' + status + ' details: "' + details + '"' + ); this.child?.cancelWithStatus(status, details); - this.outputStatus({code: status, details: details, metadata: new Metadata()}, 'PROCESSED'); + this.outputStatus( + { code: status, details: details, metadata: new Metadata() }, + 'PROCESSED' + ); } getPeer(): string { return this.child?.getPeer() ?? this.channel.getTarget(); } - start(metadata: Metadata, listener: LoadBalancingCallInterceptingListener): void { + start( + metadata: Metadata, + listener: LoadBalancingCallInterceptingListener + ): void { this.trace('start called'); this.listener = listener; this.metadata = metadata; @@ -250,7 +313,7 @@ export class LoadBalancingCall implements Call { if (this.child) { this.child.sendMessageWithContext(context, message); } else { - this.pendingMessage = {context, message}; + this.pendingMessage = { context, message }; } } startRead(): void { @@ -270,10 +333,10 @@ export class LoadBalancingCall implements Call { } } setCredentials(credentials: CallCredentials): void { - throw new Error("Method not implemented."); + throw new Error('Method not implemented.'); } getCallNumber(): number { return this.callNumber; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts index ec845c1a5..83438ef73 100644 --- a/packages/grpc-js/src/logging.ts +++ b/packages/grpc-js/src/logging.ts @@ -27,7 +27,7 @@ const DEFAULT_LOGGER: Partial = { debug: (message?: any, ...optionalParams: any[]) => { console.error('D ' + message, ...optionalParams); }, -} +}; let _logger: Partial = DEFAULT_LOGGER; let _logVerbosity: LogVerbosity = LogVerbosity.ERROR; @@ -114,6 +114,7 @@ export function trace( } export function isTracerEnabled(tracer: string): boolean { - return !disabledTracers.has(tracer) && - (allEnabled || enabledTracers.has(tracer)); + return ( + !disabledTracers.has(tracer) && (allEnabled || enabledTracers.has(tracer)) + ); } diff --git a/packages/grpc-js/src/make-client.ts b/packages/grpc-js/src/make-client.ts index 7d08fee20..10d1e959c 100644 --- a/packages/grpc-js/src/make-client.ts +++ b/packages/grpc-js/src/make-client.ts @@ -132,7 +132,7 @@ export function makeClientConstructor( [methodName: string]: Function; } - Object.keys(methods).forEach((name) => { + Object.keys(methods).forEach(name => { if (isPrototypePolluted(name)) { return; } diff --git a/packages/grpc-js/src/max-message-size-filter.ts b/packages/grpc-js/src/max-message-size-filter.ts index 62d01077c..d0f791624 100644 --- a/packages/grpc-js/src/max-message-size-filter.ts +++ b/packages/grpc-js/src/max-message-size-filter.ts @@ -28,9 +28,7 @@ import { Metadata } from './metadata'; export class MaxMessageSizeFilter extends BaseFilter implements Filter { private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; - constructor( - private readonly options: ChannelOptions - ) { + constructor(private readonly options: ChannelOptions) { super(); if ('grpc.max_send_message_length' in options) { this.maxSendMessageSize = options['grpc.max_send_message_length']!; @@ -51,7 +49,7 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { throw { code: Status.RESOURCE_EXHAUSTED, details: `Sent message larger than max (${concreteMessage.message.length} vs. ${this.maxSendMessageSize})`, - metadata: new Metadata() + metadata: new Metadata(), }; } else { return concreteMessage; @@ -70,7 +68,7 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { throw { code: Status.RESOURCE_EXHAUSTED, details: `Received message larger than max (${concreteMessage.length} vs. ${this.maxReceiveMessageSize})`, - metadata: new Metadata() + metadata: new Metadata(), }; } else { return concreteMessage; @@ -80,7 +78,8 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { } export class MaxMessageSizeFilterFactory - implements FilterFactory { + implements FilterFactory +{ constructor(private readonly options: ChannelOptions) {} createFilter(): MaxMessageSizeFilter { diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts index 0dddd9465..f5ab6bd3f 100644 --- a/packages/grpc-js/src/metadata.ts +++ b/packages/grpc-js/src/metadata.ts @@ -118,7 +118,8 @@ export class Metadata { key = normalizeKey(key); validate(key, value); - const existingValue: MetadataValue[] | undefined = this.internalRepr.get(key); + const existingValue: MetadataValue[] | undefined = + this.internalRepr.get(key); if (existingValue === undefined) { this.internalRepr.set(key, [value]); @@ -174,7 +175,7 @@ export class Metadata { const newInternalRepr = newMetadata.internalRepr; for (const [key, value] of this.internalRepr) { - const clonedValue: MetadataValue[] = value.map((v) => { + const clonedValue: MetadataValue[] = value.map(v => { if (Buffer.isBuffer(v)) { return Buffer.from(v); } else { @@ -264,12 +265,12 @@ export class Metadata { try { if (isBinaryKey(key)) { if (Array.isArray(values)) { - values.forEach((value) => { + values.forEach(value => { result.add(key, Buffer.from(value, 'base64')); }); } else if (values !== undefined) { if (isCustomMetadata(key)) { - values.split(',').forEach((v) => { + values.split(',').forEach(v => { result.add(key, Buffer.from(v.trim(), 'base64')); }); } else { @@ -278,7 +279,7 @@ export class Metadata { } } else { if (Array.isArray(values)) { - values.forEach((value) => { + values.forEach(value => { result.add(key, value); }); } else if (values !== undefined) { @@ -286,7 +287,9 @@ export class Metadata { } } } catch (error) { - const message = `Failed to add metadata entry ${key}: ${values}. ${getErrorMessage(error)}. For more information see https://github.com/grpc/grpc-node/issues/1173`; + const message = `Failed to add metadata entry ${key}: ${values}. ${getErrorMessage( + error + )}. For more information see https://github.com/grpc/grpc-node/issues/1173`; log(LogVerbosity.ERROR, message); } } @@ -296,5 +299,5 @@ export class Metadata { } const bufToString = (val: string | Buffer): string => { - return Buffer.isBuffer(val) ? val.toString('base64') : val + return Buffer.isBuffer(val) ? val.toString('base64') : val; }; diff --git a/packages/grpc-js/src/object-stream.ts b/packages/grpc-js/src/object-stream.ts index 22ab8a41f..946d4afed 100644 --- a/packages/grpc-js/src/object-stream.ts +++ b/packages/grpc-js/src/object-stream.ts @@ -37,8 +37,15 @@ export interface IntermediateObjectWritable extends Writable { write(chunk: any & T, encoding?: any, cb?: WriteCallback): boolean; setDefaultEncoding(encoding: string): this; end(): ReturnType extends Writable ? this : void; - end(chunk: any & T, cb?: Function): ReturnType extends Writable ? this : void; - end(chunk: any & T, encoding?: any, cb?: Function): ReturnType extends Writable ? this : void; + end( + chunk: any & T, + cb?: Function + ): ReturnType extends Writable ? this : void; + end( + chunk: any & T, + encoding?: any, + cb?: Function + ): ReturnType extends Writable ? this : void; } export interface ObjectWritable extends IntermediateObjectWritable { @@ -47,6 +54,13 @@ export interface ObjectWritable extends IntermediateObjectWritable { write(chunk: T, encoding?: any, cb?: Function): boolean; setDefaultEncoding(encoding: string): this; end(): ReturnType extends Writable ? this : void; - end(chunk: T, cb?: Function): ReturnType extends Writable ? this : void; - end(chunk: T, encoding?: any, cb?: Function): ReturnType extends Writable ? this : void; + end( + chunk: T, + cb?: Function + ): ReturnType extends Writable ? this : void; + end( + chunk: T, + encoding?: any, + cb?: Function + ): ReturnType extends Writable ? this : void; } diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index 162596ef5..d95eca21b 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -114,7 +114,7 @@ export class UnavailablePicker implements Picker { subchannel: null, status: this.status, onCallStarted: null, - onCallEnded: null + onCallEnded: null, }; } } @@ -143,7 +143,7 @@ export class QueuePicker { subchannel: null, status: null, onCallStarted: null, - onCallEnded: null + onCallEnded: null, }; } } diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 355ce2dfd..f5ea6ad4b 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -61,7 +61,7 @@ function mergeArrays(...arrays: T[][]): T[] { i < Math.max.apply( null, - arrays.map((array) => array.length) + arrays.map(array => array.length) ); i++ ) { @@ -137,7 +137,7 @@ class DnsResolver implements Resolver { details: `Name resolution failed for target ${uriToString(this.target)}`, metadata: new Metadata(), }; - + const backoffOptions: BackoffOptions = { initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'], maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'], @@ -150,7 +150,9 @@ class DnsResolver implements Resolver { }, backoffOptions); this.backoff.unref(); - this.minTimeBetweenResolutionsMs = channelOptions['grpc.dns_min_time_between_resolutions_ms'] ?? DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS; + this.minTimeBetweenResolutionsMs = + channelOptions['grpc.dns_min_time_between_resolutions_ms'] ?? + DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS; this.nextResolutionTimer = setTimeout(() => {}, 0); clearTimeout(this.nextResolutionTimer); } @@ -204,24 +206,23 @@ class DnsResolver implements Resolver { * error is indistinguishable from other kinds of errors */ this.pendingLookupPromise = dnsLookupPromise(hostname, { all: true }); this.pendingLookupPromise.then( - (addressList) => { + addressList => { this.pendingLookupPromise = null; this.backoff.reset(); this.backoff.stop(); const ip4Addresses: dns.LookupAddress[] = addressList.filter( - (addr) => addr.family === 4 + addr => addr.family === 4 ); const ip6Addresses: dns.LookupAddress[] = addressList.filter( - (addr) => addr.family === 6 + addr => addr.family === 6 + ); + this.latestLookupResult = mergeArrays(ip6Addresses, ip4Addresses).map( + addr => ({ host: addr.address, port: +this.port! }) ); - this.latestLookupResult = mergeArrays( - ip6Addresses, - ip4Addresses - ).map((addr) => ({ host: addr.address, port: +this.port! })); const allAddressesString: string = '[' + this.latestLookupResult - .map((addr) => addr.host + ':' + addr.port) + .map(addr => addr.host + ':' + addr.port) .join(',') + ']'; trace( @@ -246,7 +247,7 @@ class DnsResolver implements Resolver { {} ); }, - (err) => { + err => { trace( 'Resolution error for target ' + uriToString(this.target) + @@ -266,7 +267,7 @@ class DnsResolver implements Resolver { * lookup fails */ this.pendingTxtPromise = resolveTxtPromise(hostname); this.pendingTxtPromise.then( - (txtRecord) => { + txtRecord => { this.pendingTxtPromise = null; try { this.latestServiceConfig = extractAndSelectServiceConfig( @@ -294,7 +295,7 @@ class DnsResolver implements Resolver { ); } }, - (err) => { + err => { /* If TXT lookup fails we should do nothing, which means that we * continue to use the result of the most recent successful lookup, * or the default null config object if there has never been a diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index f29fb7fd7..9c4b18913 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -15,22 +15,35 @@ * */ -import { CallCredentials } from "./call-credentials"; -import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; -import { LogVerbosity, Propagate, Status } from "./constants"; -import { Deadline, deadlineToString, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; -import { FilterStack, FilterStackFactory } from "./filter-stack"; -import { InternalChannel } from "./internal-channel"; -import { Metadata } from "./metadata"; +import { CallCredentials } from './call-credentials'; +import { + Call, + CallStreamOptions, + InterceptingListener, + MessageContext, + StatusObject, +} from './call-interface'; +import { LogVerbosity, Propagate, Status } from './constants'; +import { + Deadline, + deadlineToString, + getDeadlineTimeoutString, + getRelativeTimeout, + minDeadline, +} from './deadline'; +import { FilterStack, FilterStackFactory } from './filter-stack'; +import { InternalChannel } from './internal-channel'; +import { Metadata } from './metadata'; import * as logging from './logging'; -import { restrictControlPlaneStatusCode } from "./control-plane-status"; +import { restrictControlPlaneStatusCode } from './control-plane-status'; const TRACER_NAME = 'resolving_call'; export class ResolvingCall implements Call { private child: Call | null = null; private readPending = false; - private pendingMessage: {context: MessageContext, message: Buffer} | null = null; + private pendingMessage: { context: MessageContext; message: Buffer } | null = + null; private pendingHalfClose = false; private ended = false; private readFilterPending = false; @@ -61,8 +74,14 @@ export class ResolvingCall implements Call { }); } if (options.flags & Propagate.DEADLINE) { - this.trace('Propagating deadline from parent: ' + options.parentCall.getDeadline()); - this.deadline = minDeadline(this.deadline, options.parentCall.getDeadline()); + this.trace( + 'Propagating deadline from parent: ' + + options.parentCall.getDeadline() + ); + this.deadline = minDeadline( + this.deadline, + options.parentCall.getDeadline() + ); } } this.trace('Created'); @@ -84,11 +103,8 @@ export class ResolvingCall implements Call { if (timeout !== Infinity) { this.trace('Deadline will be reached in ' + timeout + 'ms'); const handleDeadline = () => { - this.cancelWithStatus( - Status.DEADLINE_EXCEEDED, - 'Deadline exceeded' - ); - } + this.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); + }; if (timeout <= 0) { process.nextTick(handleDeadline); } else { @@ -105,7 +121,13 @@ export class ResolvingCall implements Call { } clearTimeout(this.deadlineTimer); const filteredStatus = this.filterStack.receiveTrailers(status); - this.trace('ended with status: code=' + filteredStatus.code + ' details="' + filteredStatus.details + '"'); + this.trace( + 'ended with status: code=' + + filteredStatus.code + + ' details="' + + filteredStatus.details + + '"' + ); this.statusWatchers.forEach(watcher => watcher(filteredStatus)); process.nextTick(() => { this.listener?.onReceiveStatus(filteredStatus); @@ -119,15 +141,20 @@ export class ResolvingCall implements Call { } const child = this.child; this.writeFilterPending = true; - this.filterStack!.sendMessage(Promise.resolve({message: message, flags: context.flags})).then((filteredMessage) => { - this.writeFilterPending = false; - child.sendMessageWithContext(context, filteredMessage.message); - if (this.pendingHalfClose) { - child.halfClose(); + this.filterStack!.sendMessage( + Promise.resolve({ message: message, flags: context.flags }) + ).then( + filteredMessage => { + this.writeFilterPending = false; + child.sendMessageWithContext(context, filteredMessage.message); + if (this.pendingHalfClose) { + child.halfClose(); + } + }, + (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); } - }, (status: StatusObject) => { - this.cancelWithStatus(status.code, status.details); - }); + ); } getConfig(): void { @@ -152,11 +179,14 @@ export class ResolvingCall implements Call { // configResult.type === 'SUCCESS' const config = configResult.config; if (config.status !== Status.OK) { - const {code, details} = restrictControlPlaneStatusCode(config.status, 'Failed to route call to method ' + this.method); + const { code, details } = restrictControlPlaneStatusCode( + config.status, + 'Failed to route call to method ' + this.method + ); this.outputStatus({ code: code, details: details, - metadata: new Metadata() + metadata: new Metadata(), }); return; } @@ -176,48 +206,65 @@ export class ResolvingCall implements Call { this.filterStackFactory.push(config.dynamicFilterFactories); this.filterStack = this.filterStackFactory.createFilter(); - this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then(filteredMetadata => { - this.child = this.channel.createInnerCall(config, this.method, this.host, this.credentials, this.deadline); - this.trace('Created child [' + this.child.getCallNumber() + ']') - this.child.start(filteredMetadata, { - onReceiveMetadata: metadata => { - this.trace('Received metadata') - this.listener!.onReceiveMetadata(this.filterStack!.receiveMetadata(metadata)); - }, - onReceiveMessage: message => { - this.trace('Received message'); - this.readFilterPending = true; - this.filterStack!.receiveMessage(message).then(filteredMesssage => { - this.trace('Finished filtering received message'); - this.readFilterPending = false; - this.listener!.onReceiveMessage(filteredMesssage); - if (this.pendingChildStatus) { - this.outputStatus(this.pendingChildStatus); + this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then( + filteredMetadata => { + this.child = this.channel.createInnerCall( + config, + this.method, + this.host, + this.credentials, + this.deadline + ); + this.trace('Created child [' + this.child.getCallNumber() + ']'); + this.child.start(filteredMetadata, { + onReceiveMetadata: metadata => { + this.trace('Received metadata'); + this.listener!.onReceiveMetadata( + this.filterStack!.receiveMetadata(metadata) + ); + }, + onReceiveMessage: message => { + this.trace('Received message'); + this.readFilterPending = true; + this.filterStack!.receiveMessage(message).then( + filteredMesssage => { + this.trace('Finished filtering received message'); + this.readFilterPending = false; + this.listener!.onReceiveMessage(filteredMesssage); + if (this.pendingChildStatus) { + this.outputStatus(this.pendingChildStatus); + } + }, + (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + } + ); + }, + onReceiveStatus: status => { + this.trace('Received status'); + if (this.readFilterPending) { + this.pendingChildStatus = status; + } else { + this.outputStatus(status); } - }, (status: StatusObject) => { - this.cancelWithStatus(status.code, status.details); - }); - }, - onReceiveStatus: status => { - this.trace('Received status'); - if (this.readFilterPending) { - this.pendingChildStatus = status; - } else { - this.outputStatus(status); - } + }, + }); + if (this.readPending) { + this.child.startRead(); } - }); - if (this.readPending) { - this.child.startRead(); - } - if (this.pendingMessage) { - this.sendMessageOnChild(this.pendingMessage.context, this.pendingMessage.message); - } else if (this.pendingHalfClose) { - this.child.halfClose(); + if (this.pendingMessage) { + this.sendMessageOnChild( + this.pendingMessage.context, + this.pendingMessage.message + ); + } else if (this.pendingHalfClose) { + this.child.halfClose(); + } + }, + (status: StatusObject) => { + this.outputStatus(status); } - }, (status: StatusObject) => { - this.outputStatus(status); - }) + ); } reportResolverError(status: StatusObject) { @@ -228,9 +275,15 @@ export class ResolvingCall implements Call { } } cancelWithStatus(status: Status, details: string): void { - this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); + this.trace( + 'cancelWithStatus code: ' + status + ' details: "' + details + '"' + ); this.child?.cancelWithStatus(status, details); - this.outputStatus({code: status, details: details, metadata: new Metadata()}); + this.outputStatus({ + code: status, + details: details, + metadata: new Metadata(), + }); } getPeer(): string { return this.child?.getPeer() ?? this.channel.getTarget(); @@ -246,7 +299,7 @@ export class ResolvingCall implements Call { if (this.child) { this.sendMessageOnChild(context, message); } else { - this.pendingMessage = {context, message}; + this.pendingMessage = { context, message }; } } startRead(): void { @@ -276,4 +329,4 @@ export class ResolvingCall implements Call { getCallNumber(): number { return this.callNumber; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index a39606f2c..064053bc9 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -53,7 +53,7 @@ function getDefaultConfigSelector( methodName: string, metadata: Metadata ) { - const splitName = methodName.split('/').filter((x) => x.length > 0); + const splitName = methodName.split('/').filter(x => x.length > 0); const service = splitName[0] ?? ''; const method = splitName[1] ?? ''; if (serviceConfig && serviceConfig.methodConfig) { @@ -67,7 +67,7 @@ function getDefaultConfigSelector( methodConfig: methodConfig, pickInformation: {}, status: Status.OK, - dynamicFilterFactories: [] + dynamicFilterFactories: [], }; } } @@ -77,7 +77,7 @@ function getDefaultConfigSelector( methodConfig: { name: [] }, pickInformation: {}, status: Status.OK, - dynamicFilterFactories: [] + dynamicFilterFactories: [], }; }; } @@ -153,9 +153,8 @@ export class ResolvingLoadBalancer implements LoadBalancer { } this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); this.childLoadBalancer = new ChildLoadBalancerHandler({ - createSubchannel: channelControlHelper.createSubchannel.bind( - channelControlHelper - ), + createSubchannel: + channelControlHelper.createSubchannel.bind(channelControlHelper), requestReresolution: () => { /* If the backoffTimeout is running, we're still backing off from * making resolve requests, so we shouldn't make another one here. @@ -172,12 +171,10 @@ export class ResolvingLoadBalancer implements LoadBalancer { this.latestChildPicker = picker; this.updateState(newState, picker); }, - addChannelzChild: channelControlHelper.addChannelzChild.bind( - channelControlHelper - ), - removeChannelzChild: channelControlHelper.removeChannelzChild.bind( - channelControlHelper - ) + addChannelzChild: + channelControlHelper.addChannelzChild.bind(channelControlHelper), + removeChannelzChild: + channelControlHelper.removeChannelzChild.bind(channelControlHelper), }); this.innerResolver = createResolver( target, @@ -299,7 +296,10 @@ export class ResolvingLoadBalancer implements LoadBalancer { } exitIdle() { - if (this.currentState === ConnectivityState.IDLE || this.currentState === ConnectivityState.TRANSIENT_FAILURE) { + if ( + this.currentState === ConnectivityState.IDLE || + this.currentState === ConnectivityState.TRANSIENT_FAILURE + ) { if (this.backoffTimeout.isRunning()) { this.continueResolving = true; } else { diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index 5ae585b9e..c329161c3 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -15,25 +15,41 @@ * */ -import { CallCredentials } from "./call-credentials"; -import { LogVerbosity, Status } from "./constants"; -import { Deadline } from "./deadline"; -import { Metadata } from "./metadata"; -import { CallConfig } from "./resolver"; +import { CallCredentials } from './call-credentials'; +import { LogVerbosity, Status } from './constants'; +import { Deadline } from './deadline'; +import { Metadata } from './metadata'; +import { CallConfig } from './resolver'; import * as logging from './logging'; -import { Call, InterceptingListener, MessageContext, StatusObject, WriteCallback, WriteObject } from "./call-interface"; -import { LoadBalancingCall, StatusObjectWithProgress } from "./load-balancing-call"; -import { InternalChannel } from "./internal-channel"; +import { + Call, + InterceptingListener, + MessageContext, + StatusObject, + WriteCallback, + WriteObject, +} from './call-interface'; +import { + LoadBalancingCall, + StatusObjectWithProgress, +} from './load-balancing-call'; +import { InternalChannel } from './internal-channel'; const TRACER_NAME = 'retrying_call'; export class RetryThrottler { private tokens: number; - constructor(private readonly maxTokens: number, private readonly tokenRatio: number, previousRetryThrottler?: RetryThrottler) { + constructor( + private readonly maxTokens: number, + private readonly tokenRatio: number, + previousRetryThrottler?: RetryThrottler + ) { if (previousRetryThrottler) { /* When carrying over tokens from a previous config, rescale them to the * new max value */ - this.tokens = previousRetryThrottler.tokens * (maxTokens / previousRetryThrottler.maxTokens); + this.tokens = + previousRetryThrottler.tokens * + (maxTokens / previousRetryThrottler.maxTokens); } else { this.tokens = maxTokens; } @@ -53,14 +69,17 @@ export class RetryThrottler { } export class MessageBufferTracker { - private totalAllocated: number = 0; + private totalAllocated = 0; private allocatedPerCall: Map = new Map(); constructor(private totalLimit: number, private limitPerCall: number) {} allocate(size: number, callId: number): boolean { const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; - if (this.limitPerCall - currentPerCall < size || this.totalLimit - this.totalAllocated < size) { + if ( + this.limitPerCall - currentPerCall < size || + this.totalLimit - this.totalAllocated < size + ) { return false; } this.allocatedPerCall.set(callId, currentPerCall + size); @@ -70,12 +89,16 @@ export class MessageBufferTracker { free(size: number, callId: number) { if (this.totalAllocated < size) { - throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > total allocated ${this.totalAllocated}`); + throw new Error( + `Invalid buffer allocation state: call ${callId} freed ${size} > total allocated ${this.totalAllocated}` + ); } this.totalAllocated -= size; const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; if (currentPerCall < size) { - throw new Error(`Invalid buffer allocation state: call ${callId} freed ${size} > allocated for call ${currentPerCall}`); + throw new Error( + `Invalid buffer allocation state: call ${callId} freed ${size} > allocated for call ${currentPerCall}` + ); } this.allocatedPerCall.set(callId, currentPerCall - size); } @@ -83,7 +106,9 @@ export class MessageBufferTracker { freeAll(callId: number) { const currentPerCall = this.allocatedPerCall.get(callId) ?? 0; if (this.totalAllocated < currentPerCall) { - throw new Error(`Invalid buffer allocation state: call ${callId} allocated ${currentPerCall} > total allocated ${this.totalAllocated}`); + throw new Error( + `Invalid buffer allocation state: call ${callId} allocated ${currentPerCall} > total allocated ${this.totalAllocated}` + ); } this.totalAllocated -= currentPerCall; this.allocatedPerCall.delete(callId); @@ -164,11 +189,11 @@ export class RetryingCall implements Call { * be no new child calls. */ private readStarted = false; - private transparentRetryUsed: boolean = false; + private transparentRetryUsed = false; /** * Number of attempts so far */ - private attempts: number = 0; + private attempts = 0; private hedgingTimer: NodeJS.Timer | null = null; private committedCallIndex: number | null = null; private initialRetryBackoffSec = 0; @@ -187,7 +212,12 @@ export class RetryingCall implements Call { if (callConfig.methodConfig.retryPolicy) { this.state = 'RETRY'; const retryPolicy = callConfig.methodConfig.retryPolicy; - this.nextRetryBackoffSec = this.initialRetryBackoffSec = Number(retryPolicy.initialBackoff.substring(0, retryPolicy.initialBackoff.length - 1)); + this.nextRetryBackoffSec = this.initialRetryBackoffSec = Number( + retryPolicy.initialBackoff.substring( + 0, + retryPolicy.initialBackoff.length - 1 + ) + ); } else if (callConfig.methodConfig.hedgingPolicy) { this.state = 'HEDGING'; } else { @@ -207,7 +237,13 @@ export class RetryingCall implements Call { } private reportStatus(statusObject: StatusObject) { - this.trace('ended with status: code=' + statusObject.code + ' details="' + statusObject.details + '"'); + this.trace( + 'ended with status: code=' + + statusObject.code + + ' details="' + + statusObject.details + + '"' + ); this.bufferTracker.freeAll(this.callNumber); this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length; this.writeBuffer = []; @@ -216,15 +252,17 @@ export class RetryingCall implements Call { this.listener?.onReceiveStatus({ code: statusObject.code, details: statusObject.details, - metadata: statusObject.metadata + metadata: statusObject.metadata, }); }); } cancelWithStatus(status: Status, details: string): void { - this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); - this.reportStatus({code: status, details, metadata: new Metadata()}); - for (const {call} of this.underlyingCalls) { + this.trace( + 'cancelWithStatus code: ' + status + ' details: "' + details + '"' + ); + this.reportStatus({ code: status, details, metadata: new Metadata() }); + for (const { call } of this.underlyingCalls) { call.cancelWithStatus(status, details); } } @@ -237,7 +275,12 @@ export class RetryingCall implements Call { } private getBufferEntry(messageIndex: number): WriteBufferEntry { - return this.writeBuffer[messageIndex - this.writeBufferOffset] ?? {entryType: 'FREED', allocated: false}; + return ( + this.writeBuffer[messageIndex - this.writeBufferOffset] ?? { + entryType: 'FREED', + allocated: false, + } + ); } private getNextBufferIndex() { @@ -248,14 +291,24 @@ export class RetryingCall implements Call { if (this.state !== 'COMMITTED') { return; } - const earliestNeededMessageIndex = this.underlyingCalls[this.committedCallIndex!].nextMessageToSend; - for (let messageIndex = this.writeBufferOffset; messageIndex < earliestNeededMessageIndex; messageIndex++) { + const earliestNeededMessageIndex = + this.underlyingCalls[this.committedCallIndex!].nextMessageToSend; + for ( + let messageIndex = this.writeBufferOffset; + messageIndex < earliestNeededMessageIndex; + messageIndex++ + ) { const bufferEntry = this.getBufferEntry(messageIndex); if (bufferEntry.allocated) { - this.bufferTracker.free(bufferEntry.message!.message.length, this.callNumber); + this.bufferTracker.free( + bufferEntry.message!.message.length, + this.callNumber + ); } } - this.writeBuffer = this.writeBuffer.slice(earliestNeededMessageIndex - this.writeBufferOffset); + this.writeBuffer = this.writeBuffer.slice( + earliestNeededMessageIndex - this.writeBufferOffset + ); this.writeBufferOffset = earliestNeededMessageIndex; } @@ -266,7 +319,12 @@ export class RetryingCall implements Call { if (this.underlyingCalls[index].state === 'COMPLETED') { return; } - this.trace('Committing call [' + this.underlyingCalls[index].call.getCallNumber() + '] at index ' + index); + this.trace( + 'Committing call [' + + this.underlyingCalls[index].call.getCallNumber() + + '] at index ' + + index + ); this.state = 'COMMITTED'; this.committedCallIndex = index; for (let i = 0; i < this.underlyingCalls.length; i++) { @@ -277,7 +335,10 @@ export class RetryingCall implements Call { continue; } this.underlyingCalls[i].state = 'COMPLETED'; - this.underlyingCalls[i].call.cancelWithStatus(Status.CANCELLED, 'Discarded in favor of other hedged attempt'); + this.underlyingCalls[i].call.cancelWithStatus( + Status.CANCELLED, + 'Discarded in favor of other hedged attempt' + ); } this.clearSentMessages(); } @@ -289,7 +350,10 @@ export class RetryingCall implements Call { let mostMessages = -1; let callWithMostMessages = -1; for (const [index, childCall] of this.underlyingCalls.entries()) { - if (childCall.state === 'ACTIVE' && childCall.nextMessageToSend > mostMessages) { + if ( + childCall.state === 'ACTIVE' && + childCall.nextMessageToSend > mostMessages + ) { mostMessages = childCall.nextMessageToSend; callWithMostMessages = index; } @@ -304,7 +368,11 @@ export class RetryingCall implements Call { } private isStatusCodeInList(list: (Status | string)[], code: Status) { - return list.some((value => value === code || value.toString().toLowerCase() === Status[code].toLowerCase())); + return list.some( + value => + value === code || + value.toString().toLowerCase() === Status[code].toLowerCase() + ); } private getNextRetryBackoffMs() { @@ -313,12 +381,20 @@ export class RetryingCall implements Call { return 0; } const nextBackoffMs = Math.random() * this.nextRetryBackoffSec * 1000; - const maxBackoffSec = Number(retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1)); - this.nextRetryBackoffSec = Math.min(this.nextRetryBackoffSec * retryPolicy.backoffMultiplier, maxBackoffSec); - return nextBackoffMs + const maxBackoffSec = Number( + retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1) + ); + this.nextRetryBackoffSec = Math.min( + this.nextRetryBackoffSec * retryPolicy.backoffMultiplier, + maxBackoffSec + ); + return nextBackoffMs; } - private maybeRetryCall(pushback: number | null, callback: (retried: boolean) => void) { + private maybeRetryCall( + pushback: number | null, + callback: (retried: boolean) => void + ) { if (this.state !== 'RETRY') { callback(false); return; @@ -362,7 +438,11 @@ export class RetryingCall implements Call { return count; } - private handleProcessedStatus(status: StatusObject, callIndex: number, pushback: number | null) { + private handleProcessedStatus( + status: StatusObject, + callIndex: number, + pushback: number | null + ) { switch (this.state) { case 'COMMITTED': case 'TRANSPARENT_ONLY': @@ -370,7 +450,13 @@ export class RetryingCall implements Call { this.reportStatus(status); break; case 'HEDGING': - if (this.isStatusCodeInList(this.callConfig!.methodConfig.hedgingPolicy!.nonFatalStatusCodes ?? [], status.code)) { + if ( + this.isStatusCodeInList( + this.callConfig!.methodConfig.hedgingPolicy!.nonFatalStatusCodes ?? + [], + status.code + ) + ) { this.retryThrottler?.addCallFailed(); let delayMs: number; if (pushback === null) { @@ -397,9 +483,14 @@ export class RetryingCall implements Call { } break; case 'RETRY': - if (this.isStatusCodeInList(this.callConfig!.methodConfig.retryPolicy!.retryableStatusCodes, status.code)) { + if ( + this.isStatusCodeInList( + this.callConfig!.methodConfig.retryPolicy!.retryableStatusCodes, + status.code + ) + ) { this.retryThrottler?.addCallFailed(); - this.maybeRetryCall(pushback, (retried) => { + this.maybeRetryCall(pushback, retried => { if (!retried) { this.commitCall(callIndex); this.reportStatus(status); @@ -425,11 +516,23 @@ export class RetryingCall implements Call { } } - private handleChildStatus(status: StatusObjectWithProgress, callIndex: number) { + private handleChildStatus( + status: StatusObjectWithProgress, + callIndex: number + ) { if (this.underlyingCalls[callIndex].state === 'COMPLETED') { return; } - this.trace('state=' + this.state + ' handling status with progress ' + status.progress + ' from child [' + this.underlyingCalls[callIndex].call.getCallNumber() + '] in state ' + this.underlyingCalls[callIndex].state); + this.trace( + 'state=' + + this.state + + ' handling status with progress ' + + status.progress + + ' from child [' + + this.underlyingCalls[callIndex].call.getCallNumber() + + '] in state ' + + this.underlyingCalls[callIndex].state + ); this.underlyingCalls[callIndex].state = 'COMPLETED'; if (status.code === Status.OK) { this.retryThrottler?.addCallSucceeded(); @@ -454,7 +557,7 @@ export class RetryingCall implements Call { } else { this.transparentRetryUsed = true; this.startNewAttempt(); - }; + } break; case 'DROP': this.commitCall(callIndex); @@ -497,7 +600,9 @@ export class RetryingCall implements Call { return; } const hedgingDelayString = hedgingPolicy.hedgingDelay ?? '0s'; - const hedgingDelaySec = Number(hedgingDelayString.substring(0, hedgingDelayString.length - 1)); + const hedgingDelaySec = Number( + hedgingDelayString.substring(0, hedgingDelayString.length - 1) + ); this.hedgingTimer = setTimeout(() => { this.maybeStartHedgingAttempt(); }, hedgingDelaySec * 1000); @@ -505,42 +610,72 @@ export class RetryingCall implements Call { } private startNewAttempt() { - const child = this.channel.createLoadBalancingCall(this.callConfig, this.methodName, this.host, this.credentials, this.deadline); - this.trace('Created child call [' + child.getCallNumber() + '] for attempt ' + this.attempts); + const child = this.channel.createLoadBalancingCall( + this.callConfig, + this.methodName, + this.host, + this.credentials, + this.deadline + ); + this.trace( + 'Created child call [' + + child.getCallNumber() + + '] for attempt ' + + this.attempts + ); const index = this.underlyingCalls.length; - this.underlyingCalls.push({state: 'ACTIVE', call: child, nextMessageToSend: 0}); + this.underlyingCalls.push({ + state: 'ACTIVE', + call: child, + nextMessageToSend: 0, + }); const previousAttempts = this.attempts - 1; const initialMetadata = this.initialMetadata!.clone(); if (previousAttempts > 0) { - initialMetadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + initialMetadata.set( + PREVIONS_RPC_ATTEMPTS_METADATA_KEY, + `${previousAttempts}` + ); } let receivedMetadata = false; child.start(initialMetadata, { onReceiveMetadata: metadata => { - this.trace('Received metadata from child [' + child.getCallNumber() + ']'); + this.trace( + 'Received metadata from child [' + child.getCallNumber() + ']' + ); this.commitCall(index); receivedMetadata = true; if (previousAttempts > 0) { - metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + metadata.set( + PREVIONS_RPC_ATTEMPTS_METADATA_KEY, + `${previousAttempts}` + ); } if (this.underlyingCalls[index].state === 'ACTIVE') { this.listener!.onReceiveMetadata(metadata); } }, onReceiveMessage: message => { - this.trace('Received message from child [' + child.getCallNumber() + ']'); + this.trace( + 'Received message from child [' + child.getCallNumber() + ']' + ); this.commitCall(index); if (this.underlyingCalls[index].state === 'ACTIVE') { this.listener!.onReceiveMessage(message); } }, onReceiveStatus: status => { - this.trace('Received status from child [' + child.getCallNumber() + ']'); + this.trace( + 'Received status from child [' + child.getCallNumber() + ']' + ); if (!receivedMetadata && previousAttempts > 0) { - status.metadata.set(PREVIONS_RPC_ATTEMPTS_METADATA_KEY, `${previousAttempts}`); + status.metadata.set( + PREVIONS_RPC_ATTEMPTS_METADATA_KEY, + `${previousAttempts}` + ); } this.handleChildStatus(status, index); - } + }, }); this.sendNextChildMessage(index); if (this.readStarted) { @@ -575,12 +710,15 @@ export class RetryingCall implements Call { const bufferEntry = this.getBufferEntry(childCall.nextMessageToSend); switch (bufferEntry.entryType) { case 'MESSAGE': - childCall.call.sendMessageWithContext({ - callback: (error) => { - // Ignore error - this.handleChildWriteCompleted(childIndex); - } - }, bufferEntry.message!.message); + childCall.call.sendMessageWithContext( + { + callback: error => { + // Ignore error + this.handleChildWriteCompleted(childIndex); + }, + }, + bufferEntry.message!.message + ); break; case 'HALF_CLOSE': childCall.nextMessageToSend += 1; @@ -603,19 +741,25 @@ export class RetryingCall implements Call { const bufferEntry: WriteBufferEntry = { entryType: 'MESSAGE', message: writeObj, - allocated: this.bufferTracker.allocate(message.length, this.callNumber) + allocated: this.bufferTracker.allocate(message.length, this.callNumber), }; this.writeBuffer.push(bufferEntry); if (bufferEntry.allocated) { context.callback?.(); for (const [callIndex, call] of this.underlyingCalls.entries()) { - if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { - call.call.sendMessageWithContext({ - callback: (error) => { - // Ignore error - this.handleChildWriteCompleted(callIndex); - } - }, message); + if ( + call.state === 'ACTIVE' && + call.nextMessageToSend === messageIndex + ) { + call.call.sendMessageWithContext( + { + callback: error => { + // Ignore error + this.handleChildWriteCompleted(callIndex); + }, + }, + message + ); } } } else { @@ -625,14 +769,17 @@ export class RetryingCall implements Call { return; } const call = this.underlyingCalls[this.committedCallIndex]; - bufferEntry.callback = context.callback; + bufferEntry.callback = context.callback; if (call.state === 'ACTIVE' && call.nextMessageToSend === messageIndex) { - call.call.sendMessageWithContext({ - callback: (error) => { - // Ignore error - this.handleChildWriteCompleted(this.committedCallIndex!); - } - }, message); + call.call.sendMessageWithContext( + { + callback: error => { + // Ignore error + this.handleChildWriteCompleted(this.committedCallIndex!); + }, + }, + message + ); } } } @@ -650,17 +797,20 @@ export class RetryingCall implements Call { const halfCloseIndex = this.getNextBufferIndex(); this.writeBuffer.push({ entryType: 'HALF_CLOSE', - allocated: false + allocated: false, }); for (const call of this.underlyingCalls) { - if (call?.state === 'ACTIVE' && call.nextMessageToSend === halfCloseIndex) { + if ( + call?.state === 'ACTIVE' && + call.nextMessageToSend === halfCloseIndex + ) { call.nextMessageToSend += 1; call.call.halfClose(); } } } setCredentials(newCredentials: CallCredentials): void { - throw new Error("Method not implemented."); + throw new Error('Method not implemented.'); } getMethod(): string { return this.methodName; @@ -668,4 +818,4 @@ export class RetryingCall implements Call { getHost(): string { return this.host; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 48186bc29..c03258308 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -68,7 +68,7 @@ const defaultCompressionHeaders = { // once compression is integrated. [GRPC_ACCEPT_ENCODING_HEADER]: 'identity,deflate,gzip', [GRPC_ENCODING_HEADER]: 'identity', -} +}; const defaultResponseHeaders = { [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', @@ -199,7 +199,7 @@ export class ServerWritableStreamImpl this.trailingMetadata = new Metadata(); this.call.setupSurfaceCall(this); - this.on('error', (err) => { + this.on('error', err => { this.call.sendError(err); this.end(); }); @@ -237,7 +237,7 @@ export class ServerWritableStreamImpl } catch (err) { this.emit('error', { details: getErrorMessage(err), - code: Status.INTERNAL + code: Status.INTERNAL, }); } @@ -283,7 +283,7 @@ export class ServerDuplexStreamImpl this.call.setupSurfaceCall(this); this.call.setupReadable(this, encoding); - this.on('error', (err) => { + this.on('error', err => { this.call.sendError(err); this.end(); }); @@ -502,7 +502,11 @@ export class Http2ServerCallStream< this.metadataSent = true; const custom = customMetadata ? customMetadata.toHttp2Headers() : null; // TODO(cjihrig): Include compression headers. - const headers = { ...defaultResponseHeaders, ...defaultCompressionHeaders, ...custom }; + const headers = { + ...defaultResponseHeaders, + ...defaultCompressionHeaders, + ...custom, + }; this.stream.respond(headers, defaultResponseOptions); } @@ -559,6 +563,8 @@ export class Http2ServerCallStream< const { stream } = this; let receivedLength = 0; + + // eslint-disable-next-line @typescript-eslint/no-this-alias const call = this; const body: Buffer[] = []; const limit = this.maxReceiveMessageSize; @@ -595,7 +601,10 @@ export class Http2ServerCallStream< } if (receivedLength === 0) { - next({ code: Status.INTERNAL, details: 'received empty unary message' }) + next({ + code: Status.INTERNAL, + details: 'received empty unary message', + }); return; } @@ -615,29 +624,33 @@ export class Http2ServerCallStream< } decompressedMessage.then( - (decompressed) => call.safeDeserializeMessage(decompressed, next), - (err: any) => next( - err.code - ? err - : { - code: Status.INTERNAL, - details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, - } - ) - ) + decompressed => call.safeDeserializeMessage(decompressed, next), + (err: any) => + next( + err.code + ? err + : { + code: Status.INTERNAL, + details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, + } + ) + ); } } private safeDeserializeMessage( buffer: Buffer, - next: (err: Partial | null, request?: RequestType) => void + next: ( + err: Partial | null, + request?: RequestType + ) => void ) { try { next(null, this.deserializeMessage(buffer)); } catch (err) { next({ details: getErrorMessage(err), - code: Status.INTERNAL + code: Status.INTERNAL, }); } } @@ -688,7 +701,7 @@ export class Http2ServerCallStream< } catch (err) { this.sendError({ details: getErrorMessage(err), - code: Status.INTERNAL + code: Status.INTERNAL, }); } } @@ -720,7 +733,7 @@ export class Http2ServerCallStream< [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), ...statusObj.metadata?.toHttp2Headers(), }; - + this.stream.sendTrailers(trailersToSend); this.statusSent = true; }); @@ -734,7 +747,7 @@ export class Http2ServerCallStream< ...defaultResponseHeaders, ...statusObj.metadata?.toHttp2Headers(), }; - this.stream.respond(trailersToSend, {endStream: true}); + this.stream.respond(trailersToSend, { endStream: true }); this.statusSent = true; } } @@ -790,12 +803,12 @@ export class Http2ServerCallStream< } setupSurfaceCall(call: ServerSurfaceCall) { - this.once('cancelled', (reason) => { + this.once('cancelled', reason => { call.cancelled = true; call.emit('cancelled', reason); }); - this.once('callEnd', (status) => call.emit('callEnd', status)); + this.once('callEnd', status => call.emit('callEnd', status)); } setupReadable( @@ -931,12 +944,12 @@ export class Http2ServerCallStream< this.bufferedMessages.length = 0; let code = getErrorCode(error); if (code === null || code < Status.OK || code > Status.UNAUTHENTICATED) { - code = Status.INTERNAL + code = Status.INTERNAL; } readable.emit('error', { details: getErrorMessage(error), - code: code + code: code, }); } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index d19186a75..b9ad8096d 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -59,17 +59,27 @@ import { stringToSubchannelAddress, } from './subchannel-address'; import { parseUri } from './uri-parser'; -import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; +import { + ChannelzCallTracker, + ChannelzChildrenTracker, + ChannelzTrace, + registerChannelzServer, + registerChannelzSocket, + ServerInfo, + ServerRef, + SocketInfo, + SocketRef, + TlsInfo, + unregisterChannelzRef, +} from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; import { getErrorCode, getErrorMessage } from './error'; -const UNLIMITED_CONNECTION_AGE_MS = ~(1<<31); -const KEEPALIVE_MAX_TIME_MS = ~(1<<31); +const UNLIMITED_CONNECTION_AGE_MS = ~(1 << 31); +const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); const KEEPALIVE_TIMEOUT_MS = 20000; -const { - HTTP2_HEADER_PATH -} = http2.constants +const { HTTP2_HEADER_PATH } = http2.constants; const TRACER_NAME = 'server'; @@ -101,9 +111,8 @@ export interface UntypedServiceImplementation { } function getDefaultHandler(handlerType: HandlerType, methodName: string) { - const unimplementedStatusResponse = getUnimplementedStatusResponse( - methodName - ); + const unimplementedStatusResponse = + getUnimplementedStatusResponse(methodName); switch (handlerType) { case 'unary': return ( @@ -146,7 +155,10 @@ interface ChannelzListenerInfo { } export class Server { - private http2ServerList: { server: (http2.Http2Server | http2.Http2SecureServer), channelzRef: SocketRef }[] = []; + private http2ServerList: { + server: http2.Http2Server | http2.Http2SecureServer; + channelzRef: SocketRef; + }[] = []; private handlers: Map = new Map< string, @@ -155,7 +167,7 @@ export class Server { private sessions = new Map(); private started = false; private options: ChannelOptions; - private serverAddressString: string = 'null' + private serverAddressString = 'null'; // Channelz Info private readonly channelzEnabled: boolean = true; @@ -176,14 +188,22 @@ export class Server { if (this.options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; } - this.channelzRef = registerChannelzServer(() => this.getChannelzInfo(), this.channelzEnabled); + this.channelzRef = registerChannelzServer( + () => this.getChannelzInfo(), + this.channelzEnabled + ); if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Server created'); } - this.maxConnectionAgeMs = this.options['grpc.max_connection_age_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; - this.maxConnectionAgeGraceMs = this.options['grpc.max_connection_age_grace_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; - this.keepaliveTimeMs = this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS; - this.keepaliveTimeoutMs = this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; + this.maxConnectionAgeMs = + this.options['grpc.max_connection_age_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; + this.maxConnectionAgeGraceMs = + this.options['grpc.max_connection_age_grace_ms'] ?? + UNLIMITED_CONNECTION_AGE_MS; + this.keepaliveTimeMs = + this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS; + this.keepaliveTimeoutMs = + this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; this.trace('Server constructed'); } @@ -192,27 +212,46 @@ export class Server { trace: this.channelzTrace, callTracker: this.callTracker, listenerChildren: this.listenerChildrenTracker.getChildLists(), - sessionChildren: this.sessionChildrenTracker.getChildLists() + sessionChildren: this.sessionChildrenTracker.getChildLists(), }; } - private getChannelzSessionInfoGetter(session: http2.ServerHttp2Session): () => SocketInfo { + private getChannelzSessionInfoGetter( + session: http2.ServerHttp2Session + ): () => SocketInfo { return () => { const sessionInfo = this.sessions.get(session)!; const sessionSocket = session.socket; - const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; - const localAddress = sessionSocket.localAddress ? stringToSubchannelAddress(sessionSocket.localAddress!, sessionSocket.localPort) : null; + const remoteAddress = sessionSocket.remoteAddress + ? stringToSubchannelAddress( + sessionSocket.remoteAddress, + sessionSocket.remotePort + ) + : null; + const localAddress = sessionSocket.localAddress + ? stringToSubchannelAddress( + sessionSocket.localAddress!, + sessionSocket.localPort + ) + : null; let tlsInfo: TlsInfo | null; if (session.encrypted) { const tlsSocket: TLSSocket = sessionSocket as TLSSocket; - const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); + const cipherInfo: CipherNameAndProtocol & { standardName?: string } = + tlsSocket.getCipher(); const certificate = tlsSocket.getCertificate(); const peerCertificate = tlsSocket.getPeerCertificate(); tlsInfo = { cipherSuiteStandardName: cipherInfo.standardName ?? null, - cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, - localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, - remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null + cipherSuiteOtherName: cipherInfo.standardName + ? null + : cipherInfo.name, + localCertificate: + certificate && 'raw' in certificate ? certificate.raw : null, + remoteCertificate: + peerCertificate && 'raw' in peerCertificate + ? peerCertificate.raw + : null, }; } else { tlsInfo = null; @@ -229,20 +268,24 @@ export class Server { messagesReceived: sessionInfo.messagesReceived, keepAlivesSent: 0, lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: sessionInfo.streamTracker.lastCallStartedTimestamp, + lastRemoteStreamCreatedTimestamp: + sessionInfo.streamTracker.lastCallStartedTimestamp, lastMessageSentTimestamp: sessionInfo.lastMessageSentTimestamp, lastMessageReceivedTimestamp: sessionInfo.lastMessageReceivedTimestamp, localFlowControlWindow: session.state.localWindowSize ?? null, - remoteFlowControlWindow: session.state.remoteWindowSize ?? null + remoteFlowControlWindow: session.state.remoteWindowSize ?? null, }; return socketInfo; }; } private trace(text: string): void { - logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + text); + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '(' + this.channelzRef.id + ') ' + text + ); } - addProtoService(): never { throw new Error('Not implemented. Use addService() instead'); @@ -267,7 +310,7 @@ export class Server { throw new Error('Cannot add an empty service to a server'); } - serviceKeys.forEach((name) => { + serviceKeys.forEach(name => { const attrs = service[name]; let methodType: HandlerType; @@ -318,7 +361,7 @@ export class Server { } const serviceKeys = Object.keys(service); - serviceKeys.forEach((name) => { + serviceKeys.forEach(name => { const attrs = service[name]; this.unregister(attrs.path); }); @@ -362,9 +405,8 @@ export class Server { maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, }; if ('grpc-node.max_session_memory' in this.options) { - serverOptions.maxSessionMemory = this.options[ - 'grpc-node.max_session_memory' - ]; + serverOptions.maxSessionMemory = + this.options['grpc-node.max_session_memory']; } else { /* By default, set a very large max session memory limit, to effectively * disable enforcement of the limit. Some testing indicates that Node's @@ -380,7 +422,7 @@ export class Server { const deferredCallback = (error: Error | null, port: number) => { process.nextTick(() => callback(error, port)); - } + }; const setupServer = (): http2.Http2Server | http2.Http2SecureServer => { let http2Server: http2.Http2Server | http2.Http2SecureServer; @@ -394,7 +436,9 @@ export class Server { /* These errors need to be handled by the user of Http2SecureServer, * according to https://github.com/nodejs/node/issues/35824 */ socket.on('error', (e: Error) => { - this.trace('An incoming TLS connection closed with error: ' + e.message); + this.trace( + 'An incoming TLS connection closed with error: ' + e.message + ); }); }); } else { @@ -415,8 +459,10 @@ export class Server { return Promise.resolve({ port: portNum, count: previousCount }); } return Promise.all( - addressList.map((address) => { - this.trace('Attempting to bind ' + subchannelAddressToString(address)); + addressList.map(address => { + this.trace( + 'Attempting to bind ' + subchannelAddressToString(address) + ); let addr: SubchannelAddress; if (isTcpSubchannelAddress(address)) { addr = { @@ -430,9 +476,14 @@ export class Server { const http2Server = setupServer(); return new Promise((resolve, reject) => { const onError = (err: Error) => { - this.trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); + this.trace( + 'Failed to bind ' + + subchannelAddressToString(address) + + ' with error ' + + err.message + ); resolve(err); - } + }; http2Server.once('error', onError); @@ -441,46 +492,60 @@ export class Server { let boundSubchannelAddress: SubchannelAddress; if (typeof boundAddress === 'string') { boundSubchannelAddress = { - path: boundAddress + path: boundAddress, }; } else { boundSubchannelAddress = { host: boundAddress.address, - port: boundAddress.port - } - } - let channelzRef: SocketRef; - channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null + port: boundAddress.port, }; - }, this.channelzEnabled); + } + + const channelzRef = registerChannelzSocket( + subchannelAddressToString(boundSubchannelAddress), + () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null, + }; + }, + this.channelzEnabled + ); if (this.channelzEnabled) { this.listenerChildrenTracker.refChild(channelzRef); } - this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); - this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); - resolve('port' in boundSubchannelAddress ? boundSubchannelAddress.port : portNum); + this.http2ServerList.push({ + server: http2Server, + channelzRef: channelzRef, + }); + this.trace( + 'Successfully bound ' + + subchannelAddressToString(boundSubchannelAddress) + ); + resolve( + 'port' in boundSubchannelAddress + ? boundSubchannelAddress.port + : portNum + ); http2Server.removeListener('error', onError); }); }); }) - ).then((results) => { + ).then(results => { let count = 0; for (const result of results) { if (typeof result === 'number') { @@ -509,9 +574,14 @@ export class Server { const http2Server = setupServer(); return new Promise((resolve, reject) => { const onError = (err: Error) => { - this.trace('Failed to bind ' + subchannelAddressToString(address) + ' with error ' + err.message); + this.trace( + 'Failed to bind ' + + subchannelAddressToString(address) + + ' with error ' + + err.message + ); resolve(bindWildcardPort(addressList.slice(1))); - } + }; http2Server.once('error', onError); @@ -519,41 +589,44 @@ export class Server { const boundAddress = http2Server.address() as AddressInfo; const boundSubchannelAddress: SubchannelAddress = { host: boundAddress.address, - port: boundAddress.port + port: boundAddress.port, }; - let channelzRef: SocketRef; - channelzRef = registerChannelzSocket(subchannelAddressToString(boundSubchannelAddress), () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null - }; - }, this.channelzEnabled); + const channelzRef = registerChannelzSocket( + subchannelAddressToString(boundSubchannelAddress), + () => { + return { + localAddress: boundSubchannelAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null, + }; + }, + this.channelzEnabled + ); if (this.channelzEnabled) { this.listenerChildrenTracker.refChild(channelzRef); } - this.http2ServerList.push({server: http2Server, channelzRef: channelzRef}); - this.trace('Successfully bound ' + subchannelAddressToString(boundSubchannelAddress)); - resolve( - bindSpecificPort( - addressList.slice(1), - boundAddress.port, - 1 - ) + this.http2ServerList.push({ + server: http2Server, + channelzRef: channelzRef, + }); + this.trace( + 'Successfully bound ' + + subchannelAddressToString(boundSubchannelAddress) ); + resolve(bindSpecificPort(addressList.slice(1), boundAddress.port, 1)); http2Server.removeListener('error', onError); }); }); @@ -568,7 +641,10 @@ export class Server { // We only want one resolution result. Discard all future results resolverListener.onSuccessfulResolution = () => {}; if (addressList.length === 0) { - deferredCallback(new Error(`No addresses resolved for port ${port}`), 0); + deferredCallback( + new Error(`No addresses resolved for port ${port}`), + 0 + ); return; } let bindResultPromise: Promise; @@ -587,7 +663,7 @@ export class Server { bindResultPromise = bindSpecificPort(addressList, 1, 0); } bindResultPromise.then( - (bindResult) => { + bindResult => { if (bindResult.count === 0) { const errorString = `No address added out of total ${addressList.length} resolved`; logging.log(LogVerbosity.ERROR, errorString); @@ -602,14 +678,14 @@ export class Server { deferredCallback(null, bindResult.port); } }, - (error) => { + error => { const errorString = `No address added out of total ${addressList.length} resolved`; logging.log(LogVerbosity.ERROR, errorString); deferredCallback(new Error(errorString), 0); } ); }, - onError: (error) => { + onError: error => { deferredCallback(new Error(error.details), 0); }, }; @@ -621,7 +697,8 @@ export class Server { forceShutdown(): void { // Close the server if it is still running. - for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { + for (const { server: http2Server, channelzRef: ref } of this + .http2ServerList) { if (http2Server.listening) { http2Server.close(() => { if (this.channelzEnabled) { @@ -677,7 +754,7 @@ export class Server { if ( this.http2ServerList.length === 0 || this.http2ServerList.every( - ({server: http2Server}) => http2Server.listening !== true + ({ server: http2Server }) => http2Server.listening !== true ) ) { throw new Error('server must be bound in order to start'); @@ -712,7 +789,8 @@ export class Server { // Close the server if necessary. this.started = false; - for (const {server: http2Server, channelzRef: ref} of this.http2ServerList) { + for (const { server: http2Server, channelzRef: ref } of this + .http2ServerList) { if (http2Server.listening) { pendingChecks++; http2Server.close(() => { @@ -743,13 +821,16 @@ export class Server { /** * Get the channelz reference object for this server. The returned value is * garbage if channelz is disabled for this server. - * @returns + * @returns */ getChannelzRef() { return this.channelzRef; } - private _verifyContentType(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders): boolean { + private _verifyContentType( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ): boolean { const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE]; if ( @@ -763,20 +844,22 @@ export class Server { }, { endStream: true } ); - return false + return false; } - return true + return true; } - private _retrieveHandler(headers: http2.IncomingHttpHeaders): Handler { - const path = headers[HTTP2_HEADER_PATH] as string + private _retrieveHandler( + headers: http2.IncomingHttpHeaders + ): Handler { + const path = headers[HTTP2_HEADER_PATH] as string; this.trace( 'Received call to method ' + - path + - ' at address ' + - this.serverAddressString + path + + ' at address ' + + this.serverAddressString ); const handler = this.handlers.get(path); @@ -784,59 +867,68 @@ export class Server { if (handler === undefined) { this.trace( 'No handler registered for method ' + - path + - '. Sending UNIMPLEMENTED status.' + path + + '. Sending UNIMPLEMENTED status.' ); throw getUnimplementedStatusResponse(path); } - return handler + return handler; } - + private _respondWithError>( - err: T, - stream: http2.ServerHttp2Stream, + err: T, + stream: http2.ServerHttp2Stream, channelzSessionInfo: ChannelzSessionInfo | null = null ) { const call = new Http2ServerCallStream(stream, null!, this.options); - + if (err.code === undefined) { err.code = Status.INTERNAL; } if (this.channelzEnabled) { this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed() + channelzSessionInfo?.streamTracker.addCallFailed(); } call.sendError(err); } - private _channelzHandler(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) { - const channelzSessionInfo = this.sessions.get(stream.session as http2.ServerHttp2Session); - + private _channelzHandler( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) { + const channelzSessionInfo = this.sessions.get( + stream.session as http2.ServerHttp2Session + ); + this.callTracker.addCallStarted(); channelzSessionInfo?.streamTracker.addCallStarted(); if (!this._verifyContentType(stream, headers)) { this.callTracker.addCallFailed(); channelzSessionInfo?.streamTracker.addCallFailed(); - return + return; } - let handler: Handler + let handler: Handler; try { - handler = this._retrieveHandler(headers) + handler = this._retrieveHandler(headers); } catch (err) { - this._respondWithError({ - details: getErrorMessage(err), - code: getErrorCode(err) ?? undefined - }, stream, channelzSessionInfo) - return + this._respondWithError( + { + details: getErrorMessage(err), + code: getErrorCode(err) ?? undefined, + }, + stream, + channelzSessionInfo + ); + return; } - + const call = new Http2ServerCallStream(stream, handler, this.options); - + call.once('callEnd', (code: Status) => { if (code === Status.OK) { this.callTracker.addCallSucceeded(); @@ -844,7 +936,7 @@ export class Server { this.callTracker.addCallFailed(); } }); - + if (channelzSessionInfo) { call.once('streamEnd', (success: boolean) => { if (success) { @@ -865,46 +957,58 @@ export class Server { if (!this._runHandlerForCall(call, handler, headers)) { this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed() + channelzSessionInfo?.streamTracker.addCallFailed(); call.sendError({ code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}` + details: `Unknown handler type: ${handler.type}`, }); } } - private _streamHandler(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) { + private _streamHandler( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) { if (this._verifyContentType(stream, headers) !== true) { - return + return; } - let handler: Handler + let handler: Handler; try { - handler = this._retrieveHandler(headers) + handler = this._retrieveHandler(headers); } catch (err) { - this._respondWithError({ - details: getErrorMessage(err), - code: getErrorCode(err) ?? undefined - }, stream, null) - return + this._respondWithError( + { + details: getErrorMessage(err), + code: getErrorCode(err) ?? undefined, + }, + stream, + null + ); + return; } - const call = new Http2ServerCallStream(stream, handler, this.options) + const call = new Http2ServerCallStream(stream, handler, this.options); if (!this._runHandlerForCall(call, handler, headers)) { call.sendError({ code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}` + details: `Unknown handler type: ${handler.type}`, }); } } - private _runHandlerForCall(call: Http2ServerCallStream, handler: Handler, headers: http2.IncomingHttpHeaders): boolean { + private _runHandlerForCall( + call: Http2ServerCallStream, + handler: Handler, + headers: http2.IncomingHttpHeaders + ): boolean { const metadata = call.receiveMetadata(headers); - const encoding = (metadata.get('grpc-encoding')[0] as string | undefined) ?? 'identity'; + const encoding = + (metadata.get('grpc-encoding')[0] as string | undefined) ?? 'identity'; metadata.remove('grpc-encoding'); - const { type } = handler + const { type } = handler; if (type === 'unary') { handleUnary(call, handler as UntypedUnaryHandler, metadata, encoding); } else if (type === 'clientStream') { @@ -929,10 +1033,10 @@ export class Server { encoding ); } else { - return false + return false; } - return true + return true; } private _setupHandlers( @@ -943,30 +1047,32 @@ export class Server { } const serverAddress = http2Server.address(); - let serverAddressString = 'null' + let serverAddressString = 'null'; if (serverAddress) { if (typeof serverAddress === 'string') { - serverAddressString = serverAddress + serverAddressString = serverAddress; } else { - serverAddressString = - serverAddress.address + ':' + serverAddress.port + serverAddressString = serverAddress.address + ':' + serverAddress.port; } } - this.serverAddressString = serverAddressString + this.serverAddressString = serverAddressString; - const handler = this.channelzEnabled - ? this._channelzHandler - : this._streamHandler + const handler = this.channelzEnabled + ? this._channelzHandler + : this._streamHandler; - http2Server.on('stream', handler.bind(this)) - http2Server.on('session', (session) => { + http2Server.on('stream', handler.bind(this)); + http2Server.on('session', session => { if (!this.started) { session.destroy(); return; } - let channelzRef: SocketRef; - channelzRef = registerChannelzSocket(session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session), this.channelzEnabled); + const channelzRef = registerChannelzSocket( + session.socket.remoteAddress ?? 'unknown', + this.getChannelzSessionInfoGetter(session), + this.channelzEnabled + ); const channelzSessionInfo: ChannelzSessionInfo = { ref: channelzRef, @@ -974,13 +1080,16 @@ export class Server { messagesSent: 0, messagesReceived: 0, lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null + lastMessageReceivedTimestamp: null, }; this.sessions.set(session, channelzSessionInfo); const clientAddress = session.socket.remoteAddress; if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection established by client ' + clientAddress + ); this.sessionChildrenTracker.refChild(channelzRef); } let connectionAgeTimer: NodeJS.Timer | null = null; @@ -993,10 +1102,17 @@ export class Server { connectionAgeTimer = setTimeout(() => { sessionClosedByServer = true; if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by max connection age from ' + clientAddress); + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped by max connection age from ' + clientAddress + ); } try { - session.goaway(http2.constants.NGHTTP2_NO_ERROR, ~(1<<31), Buffer.from('max_age')); + session.goaway( + http2.constants.NGHTTP2_NO_ERROR, + ~(1 << 31), + Buffer.from('max_age') + ); } catch (e) { // The goaway can't be sent because the session is already closed session.destroy(); @@ -1016,14 +1132,19 @@ export class Server { const timeoutTImer = setTimeout(() => { sessionClosedByServer = true; if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by keepalive timeout from ' + clientAddress); + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped by keepalive timeout from ' + clientAddress + ); } session.close(); }, this.keepaliveTimeoutMs).unref?.(); try { - session.ping((err: Error | null, duration: number, payload: Buffer) => { - clearTimeout(timeoutTImer); - }); + session.ping( + (err: Error | null, duration: number, payload: Buffer) => { + clearTimeout(timeoutTImer); + } + ); } catch (e) { // The ping can't be sent because the session is already closed session.destroy(); @@ -1032,7 +1153,10 @@ export class Server { session.on('close', () => { if (this.channelzEnabled) { if (!sessionClosedByServer) { - this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped by client ' + clientAddress + ); } this.sessionChildrenTracker.unrefChild(channelzRef); unregisterChannelzRef(channelzRef); @@ -1060,8 +1184,8 @@ function handleUnary( ): void { call.receiveUnaryMessage(encoding, (err, request) => { if (err) { - call.sendError(err) - return + call.sendError(err); + return; } if (request === undefined || call.cancelled) { @@ -1127,8 +1251,8 @@ function handleServerStreaming( ): void { call.receiveUnaryMessage(encoding, (err, request) => { if (err) { - call.sendError(err) - return + call.sendError(err); + return; } if (request === undefined || call.cancelled) { diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index 201c0c648..91bee52c2 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -112,35 +112,71 @@ function validateName(obj: any): MethodConfigName { } function validateRetryPolicy(obj: any): RetryPolicy { - if (!('maxAttempts' in obj) || !Number.isInteger(obj.maxAttempts) || obj.maxAttempts < 2) { - throw new Error('Invalid method config retry policy: maxAttempts must be an integer at least 2'); - } - if (!('initialBackoff' in obj) || typeof obj.initialBackoff !== 'string' || !DURATION_REGEX.test(obj.initialBackoff)) { - throw new Error('Invalid method config retry policy: initialBackoff must be a string consisting of a positive integer followed by s'); - } - if (!('maxBackoff' in obj) || typeof obj.maxBackoff !== 'string' || !DURATION_REGEX.test(obj.maxBackoff)) { - throw new Error('Invalid method config retry policy: maxBackoff must be a string consisting of a positive integer followed by s'); - } - if (!('backoffMultiplier' in obj) || typeof obj.backoffMultiplier !== 'number' || obj.backoffMultiplier <= 0) { - throw new Error('Invalid method config retry policy: backoffMultiplier must be a number greater than 0'); - } - if (!(('retryableStatusCodes' in obj) && Array.isArray(obj.retryableStatusCodes))) { - throw new Error('Invalid method config retry policy: retryableStatusCodes is required'); + if ( + !('maxAttempts' in obj) || + !Number.isInteger(obj.maxAttempts) || + obj.maxAttempts < 2 + ) { + throw new Error( + 'Invalid method config retry policy: maxAttempts must be an integer at least 2' + ); + } + if ( + !('initialBackoff' in obj) || + typeof obj.initialBackoff !== 'string' || + !DURATION_REGEX.test(obj.initialBackoff) + ) { + throw new Error( + 'Invalid method config retry policy: initialBackoff must be a string consisting of a positive integer followed by s' + ); + } + if ( + !('maxBackoff' in obj) || + typeof obj.maxBackoff !== 'string' || + !DURATION_REGEX.test(obj.maxBackoff) + ) { + throw new Error( + 'Invalid method config retry policy: maxBackoff must be a string consisting of a positive integer followed by s' + ); + } + if ( + !('backoffMultiplier' in obj) || + typeof obj.backoffMultiplier !== 'number' || + obj.backoffMultiplier <= 0 + ) { + throw new Error( + 'Invalid method config retry policy: backoffMultiplier must be a number greater than 0' + ); + } + if ( + !('retryableStatusCodes' in obj && Array.isArray(obj.retryableStatusCodes)) + ) { + throw new Error( + 'Invalid method config retry policy: retryableStatusCodes is required' + ); } if (obj.retryableStatusCodes.length === 0) { - throw new Error('Invalid method config retry policy: retryableStatusCodes must be non-empty'); + throw new Error( + 'Invalid method config retry policy: retryableStatusCodes must be non-empty' + ); } for (const value of obj.retryableStatusCodes) { if (typeof value === 'number') { if (!Object.values(Status).includes(value)) { - throw new Error('Invalid method config retry policy: retryableStatusCodes value not in status code range'); + throw new Error( + 'Invalid method config retry policy: retryableStatusCodes value not in status code range' + ); } } else if (typeof value === 'string') { if (!Object.values(Status).includes(value.toUpperCase())) { - throw new Error('Invalid method config retry policy: retryableStatusCodes value not a status code name'); + throw new Error( + 'Invalid method config retry policy: retryableStatusCodes value not a status code name' + ); } } else { - throw new Error('Invalid method config retry policy: retryableStatusCodes value must be a string or number'); + throw new Error( + 'Invalid method config retry policy: retryableStatusCodes value must be a string or number' + ); } } return { @@ -148,35 +184,53 @@ function validateRetryPolicy(obj: any): RetryPolicy { initialBackoff: obj.initialBackoff, maxBackoff: obj.maxBackoff, backoffMultiplier: obj.backoffMultiplier, - retryableStatusCodes: obj.retryableStatusCodes + retryableStatusCodes: obj.retryableStatusCodes, }; } function validateHedgingPolicy(obj: any): HedgingPolicy { - if (!('maxAttempts' in obj) || !Number.isInteger(obj.maxAttempts) || obj.maxAttempts < 2) { - throw new Error('Invalid method config hedging policy: maxAttempts must be an integer at least 2'); - } - if (('hedgingDelay' in obj) && (typeof obj.hedgingDelay !== 'string' || !DURATION_REGEX.test(obj.hedgingDelay))) { - throw new Error('Invalid method config hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s'); - } - if (('nonFatalStatusCodes' in obj) && Array.isArray(obj.nonFatalStatusCodes)) { + if ( + !('maxAttempts' in obj) || + !Number.isInteger(obj.maxAttempts) || + obj.maxAttempts < 2 + ) { + throw new Error( + 'Invalid method config hedging policy: maxAttempts must be an integer at least 2' + ); + } + if ( + 'hedgingDelay' in obj && + (typeof obj.hedgingDelay !== 'string' || + !DURATION_REGEX.test(obj.hedgingDelay)) + ) { + throw new Error( + 'Invalid method config hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s' + ); + } + if ('nonFatalStatusCodes' in obj && Array.isArray(obj.nonFatalStatusCodes)) { for (const value of obj.nonFatalStatusCodes) { if (typeof value === 'number') { if (!Object.values(Status).includes(value)) { - throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value not in status code range'); + throw new Error( + 'Invlid method config hedging policy: nonFatalStatusCodes value not in status code range' + ); } } else if (typeof value === 'string') { if (!Object.values(Status).includes(value.toUpperCase())) { - throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value not a status code name'); + throw new Error( + 'Invlid method config hedging policy: nonFatalStatusCodes value not a status code name' + ); } } else { - throw new Error('Invlid method config hedging policy: nonFatalStatusCodes value must be a string or number'); + throw new Error( + 'Invlid method config hedging policy: nonFatalStatusCodes value must be a string or number' + ); } } } const result: HedgingPolicy = { - maxAttempts: obj.maxAttempts - } + maxAttempts: obj.maxAttempts, + }; if (obj.hedgingDelay) { result.hedgingDelay = obj.hedgingDelay; } @@ -246,7 +300,9 @@ function validateMethodConfig(obj: any): MethodConfig { } if ('retryPolicy' in obj) { if ('hedgingPolicy' in obj) { - throw new Error('Invalid method config: retryPolicy and hedgingPolicy cannot both be specified'); + throw new Error( + 'Invalid method config: retryPolicy and hedgingPolicy cannot both be specified' + ); } else { result.retryPolicy = validateRetryPolicy(obj.retryPolicy); } @@ -257,15 +313,28 @@ function validateMethodConfig(obj: any): MethodConfig { } export function validateRetryThrottling(obj: any): RetryThrottling { - if (!('maxTokens' in obj) || typeof obj.maxTokens !== 'number' || obj.maxTokens <=0 || obj.maxTokens > 1000) { - throw new Error('Invalid retryThrottling: maxTokens must be a number in (0, 1000]'); - } - if (!('tokenRatio' in obj) || typeof obj.tokenRatio !== 'number' || obj.tokenRatio <= 0) { - throw new Error('Invalid retryThrottling: tokenRatio must be a number greater than 0'); + if ( + !('maxTokens' in obj) || + typeof obj.maxTokens !== 'number' || + obj.maxTokens <= 0 || + obj.maxTokens > 1000 + ) { + throw new Error( + 'Invalid retryThrottling: maxTokens must be a number in (0, 1000]' + ); + } + if ( + !('tokenRatio' in obj) || + typeof obj.tokenRatio !== 'number' || + obj.tokenRatio <= 0 + ) { + throw new Error( + 'Invalid retryThrottling: tokenRatio must be a number greater than 0' + ); } return { maxTokens: +(obj.maxTokens as number).toFixed(3), - tokenRatio: +(obj.tokenRatio as number).toFixed(3) + tokenRatio: +(obj.tokenRatio as number).toFixed(3), }; } diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts index e542e645e..1ab88f45d 100644 --- a/packages/grpc-js/src/subchannel-address.ts +++ b/packages/grpc-js/src/subchannel-address.ts @@ -15,7 +15,7 @@ * */ -import { isIP } from "net"; +import { isIP } from 'net'; export interface TcpSubchannelAddress { port: number; @@ -71,15 +71,18 @@ export function subchannelAddressToString(address: SubchannelAddress): string { const DEFAULT_PORT = 443; -export function stringToSubchannelAddress(addressString: string, port?: number): SubchannelAddress { +export function stringToSubchannelAddress( + addressString: string, + port?: number +): SubchannelAddress { if (isIP(addressString)) { return { host: addressString, - port: port ?? DEFAULT_PORT + port: port ?? DEFAULT_PORT, }; } else { return { - path: addressString + path: addressString, }; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 969282e19..5d87fc80d 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -25,16 +25,18 @@ import * as logging from './logging'; import { LogVerbosity } from './constants'; import { ServerSurfaceCall } from './server-call'; import { Deadline } from './deadline'; -import { InterceptingListener, MessageContext, StatusObject, WriteCallback } from './call-interface'; +import { + InterceptingListener, + MessageContext, + StatusObject, + WriteCallback, +} from './call-interface'; import { CallEventTracker, Transport } from './transport'; const TRACER_NAME = 'subchannel_call'; -const { - HTTP2_HEADER_STATUS, - HTTP2_HEADER_CONTENT_TYPE, - NGHTTP2_CANCEL, -} = http2.constants; +const { HTTP2_HEADER_STATUS, HTTP2_HEADER_CONTENT_TYPE, NGHTTP2_CANCEL } = + http2.constants; /** * https://nodejs.org/api/errors.html#errors_class_systemerror @@ -79,7 +81,8 @@ export interface StatusObjectWithRstCode extends StatusObject { rstCode?: number; } -export interface SubchannelCallInterceptingListener extends InterceptingListener { +export interface SubchannelCallInterceptingListener + extends InterceptingListener { onReceiveStatus(status: StatusObjectWithRstCode): void; } @@ -235,7 +238,10 @@ export class Http2SubchannelCall implements SubchannelCall { * "Internal server error" message. */ details = `Received RST_STREAM with code ${http2Stream.rstCode} (Internal server error)`; } else { - if (this.internalError.code === 'ECONNRESET' || this.internalError.code === 'ETIMEDOUT') { + if ( + this.internalError.code === 'ECONNRESET' || + this.internalError.code === 'ETIMEDOUT' + ) { code = Status.UNAVAILABLE; details = this.internalError.message; } else { @@ -255,7 +261,12 @@ export class Http2SubchannelCall implements SubchannelCall { // This is OK, because status codes emitted here correspond to more // catastrophic issues that prevent us from receiving trailers in the // first place. - this.endCall({ code, details, metadata: new Metadata(), rstCode: http2Stream.rstCode }); + this.endCall({ + code, + details, + metadata: new Metadata(), + rstCode: http2Stream.rstCode, + }); }); }); http2Stream.on('error', (err: SystemError) => { @@ -488,7 +499,7 @@ export class Http2SubchannelCall implements SubchannelCall { return; } /* Only resume reading from the http2Stream if we don't have any pending - * messages to emit */ + * messages to emit */ this.http2Stream.resume(); } @@ -496,7 +507,9 @@ export class Http2SubchannelCall implements SubchannelCall { this.trace('write() called with message of length ' + message.length); const cb: WriteCallback = (error?: Error | null) => { let code: Status = Status.UNAVAILABLE; - if ((error as NodeJS.ErrnoException)?.code === 'ERR_STREAM_WRITE_AFTER_END') { + if ( + (error as NodeJS.ErrnoException)?.code === 'ERR_STREAM_WRITE_AFTER_END' + ) { code = Status.INTERNAL; } if (error) { @@ -512,7 +525,7 @@ export class Http2SubchannelCall implements SubchannelCall { this.endCall({ code: Status.UNAVAILABLE, details: `Write failed with error ${(error as Error).message}`, - metadata: new Metadata() + metadata: new Metadata(), }); } } diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts index 165ebc3e1..557d62870 100644 --- a/packages/grpc-js/src/subchannel-interface.ts +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -15,9 +15,9 @@ * */ -import { SubchannelRef } from "./channelz"; -import { ConnectivityState } from "./connectivity-state"; -import { Subchannel } from "./subchannel"; +import { SubchannelRef } from './channelz'; +import { ConnectivityState } from './connectivity-state'; +import { Subchannel } from './subchannel'; export type ConnectivityStateListener = ( subchannel: SubchannelInterface, @@ -30,7 +30,7 @@ export type ConnectivityStateListener = ( * This is an interface for load balancing policies to use to interact with * subchannels. This allows load balancing policies to wrap and unwrap * subchannels. - * + * * Any load balancing policy that wraps subchannels must unwrap the subchannel * in the picker, so that other load balancing policies consistently have * access to their own wrapper objects. @@ -84,4 +84,4 @@ export abstract class BaseSubchannelWrapper implements SubchannelInterface { getRealSubchannel(): Subchannel { return this.child.getRealSubchannel(); } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts index bbfbea02b..0cbc028ed 100644 --- a/packages/grpc-js/src/subchannel-pool.ts +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -68,7 +68,7 @@ export class SubchannelPool { const subchannelObjArray = this.pool[channelTarget]; const refedSubchannels = subchannelObjArray.filter( - (value) => !value.subchannel.unrefIfOneRef() + value => !value.subchannel.unrefIfOneRef() ); if (refedSubchannels.length > 0) { diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 307f6b81e..480314e4a 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -27,7 +27,15 @@ import { SubchannelAddress, subchannelAddressToString, } from './subchannel-address'; -import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, unregisterChannelzRef } from './channelz'; +import { + SubchannelRef, + ChannelzTrace, + ChannelzChildrenTracker, + SubchannelInfo, + registerChannelzSubchannel, + ChannelzCallTracker, + unregisterChannelzRef, +} from './channelz'; import { ConnectivityStateListener } from './subchannel-interface'; import { SubchannelCallInterceptingListener } from './subchannel-call'; import { SubchannelCall } from './subchannel-call'; @@ -117,11 +125,18 @@ export class Subchannel { this.channelzEnabled = false; } this.channelzTrace = new ChannelzTrace(); - this.channelzRef = registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); + this.channelzRef = registerChannelzSubchannel( + this.subchannelAddressString, + () => this.getChannelzInfo(), + this.channelzEnabled + ); if (this.channelzEnabled) { this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); } - this.trace('Subchannel constructed with options ' + JSON.stringify(options, undefined, 2)); + this.trace( + 'Subchannel constructed with options ' + + JSON.stringify(options, undefined, 2) + ); } private getChannelzInfo(): SubchannelInfo { @@ -130,16 +145,34 @@ export class Subchannel { trace: this.channelzTrace, callTracker: this.callTracker, children: this.childrenTracker.getChildLists(), - target: this.subchannelAddressString + target: this.subchannelAddressString, }; } private trace(text: string): void { - logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '(' + + this.channelzRef.id + + ') ' + + this.subchannelAddressString + + ' ' + + text + ); } private refTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, 'subchannel_refcount', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + logging.trace( + LogVerbosity.DEBUG, + 'subchannel_refcount', + '(' + + this.channelzRef.id + + ') ' + + this.subchannelAddressString + + ' ' + + text + ); } private handleBackoffTimer() { @@ -171,36 +204,52 @@ export class Subchannel { private startConnectingInternal() { let options = this.options; if (options['grpc.keepalive_time_ms']) { - const adjustedKeepaliveTime = Math.min(this.keepaliveTime, KEEPALIVE_MAX_TIME_MS); - options = {...options, 'grpc.keepalive_time_ms': adjustedKeepaliveTime}; + const adjustedKeepaliveTime = Math.min( + this.keepaliveTime, + KEEPALIVE_MAX_TIME_MS + ); + options = { ...options, 'grpc.keepalive_time_ms': adjustedKeepaliveTime }; } - this.connector.connect(this.subchannelAddress, this.credentials, options).then( - transport => { - if (this.transitionToState([ConnectivityState.CONNECTING], ConnectivityState.READY)) { - this.transport = transport; - if (this.channelzEnabled) { - this.childrenTracker.refChild(transport.getChannelzRef()); - } - transport.addDisconnectListener((tooManyPings) => { - this.transitionToState([ConnectivityState.READY], ConnectivityState.IDLE); - if (tooManyPings && this.keepaliveTime > 0) { - this.keepaliveTime *= 2; - logging.log( - LogVerbosity.ERROR, - `Connection to ${uriToString(this.channelTarget)} at ${ - this.subchannelAddressString - } rejected by server because of excess pings. Increasing ping interval to ${ - this.keepaliveTime - } ms` - ); + this.connector + .connect(this.subchannelAddress, this.credentials, options) + .then( + transport => { + if ( + this.transitionToState( + [ConnectivityState.CONNECTING], + ConnectivityState.READY + ) + ) { + this.transport = transport; + if (this.channelzEnabled) { + this.childrenTracker.refChild(transport.getChannelzRef()); } - }); + transport.addDisconnectListener(tooManyPings => { + this.transitionToState( + [ConnectivityState.READY], + ConnectivityState.IDLE + ); + if (tooManyPings && this.keepaliveTime > 0) { + this.keepaliveTime *= 2; + logging.log( + LogVerbosity.ERROR, + `Connection to ${uriToString(this.channelTarget)} at ${ + this.subchannelAddressString + } rejected by server because of excess pings. Increasing ping interval to ${ + this.keepaliveTime + } ms` + ); + } + }); + } + }, + error => { + this.transitionToState( + [ConnectivityState.CONNECTING], + ConnectivityState.TRANSIENT_FAILURE + ); } - }, - error => { - this.transitionToState([ConnectivityState.CONNECTING], ConnectivityState.TRANSIENT_FAILURE); - } - ) + ); } /** @@ -223,7 +272,12 @@ export class Subchannel { ConnectivityState[newState] ); if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', ConnectivityState[this.connectivityState] + ' -> ' + ConnectivityState[newState]); + this.channelzTrace.addTrace( + 'CT_INFO', + ConnectivityState[this.connectivityState] + + ' -> ' + + ConnectivityState[newState] + ); } const previousState = this.connectivityState; this.connectivityState = newState; @@ -268,22 +322,12 @@ export class Subchannel { } ref() { - this.refTrace( - 'refcount ' + - this.refcount + - ' -> ' + - (this.refcount + 1) - ); + this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount + 1)); this.refcount += 1; } unref() { - this.refTrace( - 'refcount ' + - this.refcount + - ' -> ' + - (this.refcount - 1) - ); + this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount - 1)); this.refcount -= 1; if (this.refcount === 0) { if (this.channelzEnabled) { @@ -309,7 +353,12 @@ export class Subchannel { return false; } - createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener): SubchannelCall { + createCall( + metadata: Metadata, + host: string, + method: string, + listener: SubchannelCallInterceptingListener + ): SubchannelCall { if (!this.transport) { throw new Error('Cannot create call, subchannel not READY'); } @@ -324,12 +373,18 @@ export class Subchannel { } else { this.callTracker.addCallFailed(); } - } - } + }, + }; } else { statsTracker = {}; } - return this.transport.createCall(metadata, host, method, listener, statsTracker); + return this.transport.createCall( + metadata, + host, + method, + listener, + statsTracker + ); } /** @@ -341,9 +396,9 @@ export class Subchannel { startConnecting() { process.nextTick(() => { /* First, try to transition from IDLE to connecting. If that doesn't happen - * because the state is not currently IDLE, check if it is - * TRANSIENT_FAILURE, and if so indicate that it should go back to - * connecting after the backoff timer ends. Otherwise do nothing */ + * because the state is not currently IDLE, check if it is + * TRANSIENT_FAILURE, and if so indicate that it should go back to + * connecting after the backoff timer ends. Otherwise do nothing */ if ( !this.transitionToState( [ConnectivityState.IDLE], diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 8abc13aba..bfdc11480 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -16,19 +16,40 @@ */ import * as http2 from 'http2'; -import { checkServerIdentity, CipherNameAndProtocol, ConnectionOptions, PeerCertificate, TLSSocket } from 'tls'; +import { + checkServerIdentity, + CipherNameAndProtocol, + ConnectionOptions, + PeerCertificate, + TLSSocket, +} from 'tls'; import { StatusObject } from './call-interface'; import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; -import { ChannelzCallTracker, registerChannelzSocket, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; +import { + ChannelzCallTracker, + registerChannelzSocket, + SocketInfo, + SocketRef, + TlsInfo, + unregisterChannelzRef, +} from './channelz'; import { LogVerbosity } from './constants'; import { getProxiedConnection, ProxyConnectionResult } from './http_proxy'; import * as logging from './logging'; import { getDefaultAuthority } from './resolver'; -import { stringToSubchannelAddress, SubchannelAddress, subchannelAddressToString } from './subchannel-address'; +import { + stringToSubchannelAddress, + SubchannelAddress, + subchannelAddressToString, +} from './subchannel-address'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import * as net from 'net'; -import { Http2SubchannelCall, SubchannelCall, SubchannelCallInterceptingListener } from './subchannel-call'; +import { + Http2SubchannelCall, + SubchannelCall, + SubchannelCallInterceptingListener, +} from './subchannel-call'; import { Metadata } from './metadata'; import { getNextCallNumber } from './call-number'; @@ -66,7 +87,13 @@ export interface TransportDisconnectListener { export interface Transport { getChannelzRef(): SocketRef; getPeerName(): string; - createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener, subchannelCallStatsTracker: Partial): SubchannelCall; + createCall( + metadata: Metadata, + host: string, + method: string, + listener: SubchannelCallInterceptingListener, + subchannelCallStatsTracker: Partial + ): SubchannelCall; addDisconnectListener(listener: TransportDisconnectListener): void; shutdown(): void; } @@ -77,7 +104,7 @@ class Http2Transport implements Transport { /** * The amount of time in between sending pings */ - private keepaliveTimeMs: number = -1; + private keepaliveTimeMs = -1; /** * The amount of time to wait for an acknowledgement after sending a ping */ @@ -131,9 +158,9 @@ class Http2Transport implements Transport { `grpc-node-js/${clientVersion}`, options['grpc.secondary_user_agent'], ] - .filter((e) => e) + .filter(e => e) .join(' '); // remove falsey values first - + if ('grpc.keepalive_time_ms' in options) { this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!; } @@ -157,36 +184,37 @@ class Http2Transport implements Transport { if (options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; } - this.channelzRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); + this.channelzRef = registerChannelzSocket( + this.subchannelAddressString, + () => this.getChannelzInfo(), + this.channelzEnabled + ); session.once('close', () => { this.trace('session closed'); this.stopKeepalivePings(); this.handleDisconnect(); }); - session.once('goaway', (errorCode: number, lastStreamID: number, opaqueData: Buffer) => { - let tooManyPings = false; - /* See the last paragraph of - * https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */ - if ( - errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM && - opaqueData.equals(tooManyPingsData) - ) { - tooManyPings = true; + session.once( + 'goaway', + (errorCode: number, lastStreamID: number, opaqueData: Buffer) => { + let tooManyPings = false; + /* See the last paragraph of + * https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */ + if ( + errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM && + opaqueData.equals(tooManyPingsData) + ) { + tooManyPings = true; + } + this.trace('connection closed by GOAWAY with code ' + errorCode); + this.reportDisconnectToOwner(tooManyPings); } - this.trace( - 'connection closed by GOAWAY with code ' + - errorCode - ); - this.reportDisconnectToOwner(tooManyPings); - }); + ); session.once('error', error => { /* Do nothing here. Any error should also trigger a close event, which is * where we want to handle that. */ - this.trace( - 'connection closed with error ' + - (error as Error).message - ); + this.trace('connection closed with error ' + (error as Error).message); }); if (logging.isTracerEnabled(TRACER_NAME)) { session.on('remoteSettings', (settings: http2.Settings) => { @@ -210,19 +238,34 @@ class Http2Transport implements Transport { private getChannelzInfo(): SocketInfo { const sessionSocket = this.session.socket; - const remoteAddress = sessionSocket.remoteAddress ? stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; - const localAddress = sessionSocket.localAddress ? stringToSubchannelAddress(sessionSocket.localAddress, sessionSocket.localPort) : null; + const remoteAddress = sessionSocket.remoteAddress + ? stringToSubchannelAddress( + sessionSocket.remoteAddress, + sessionSocket.remotePort + ) + : null; + const localAddress = sessionSocket.localAddress + ? stringToSubchannelAddress( + sessionSocket.localAddress, + sessionSocket.localPort + ) + : null; let tlsInfo: TlsInfo | null; if (this.session.encrypted) { const tlsSocket: TLSSocket = sessionSocket as TLSSocket; - const cipherInfo: CipherNameAndProtocol & {standardName?: string} = tlsSocket.getCipher(); + const cipherInfo: CipherNameAndProtocol & { standardName?: string } = + tlsSocket.getCipher(); const certificate = tlsSocket.getCertificate(); const peerCertificate = tlsSocket.getPeerCertificate(); tlsInfo = { cipherSuiteStandardName: cipherInfo.standardName ?? null, cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, - localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null, - remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null + localCertificate: + certificate && 'raw' in certificate ? certificate.raw : null, + remoteCertificate: + peerCertificate && 'raw' in peerCertificate + ? peerCertificate.raw + : null, }; } else { tlsInfo = null; @@ -238,30 +281,67 @@ class Http2Transport implements Transport { messagesSent: this.messagesSent, messagesReceived: this.messagesReceived, keepAlivesSent: this.keepalivesSent, - lastLocalStreamCreatedTimestamp: this.streamTracker.lastCallStartedTimestamp, + lastLocalStreamCreatedTimestamp: + this.streamTracker.lastCallStartedTimestamp, lastRemoteStreamCreatedTimestamp: null, lastMessageSentTimestamp: this.lastMessageSentTimestamp, lastMessageReceivedTimestamp: this.lastMessageReceivedTimestamp, localFlowControlWindow: this.session.state.localWindowSize ?? null, - remoteFlowControlWindow: this.session.state.remoteWindowSize ?? null + remoteFlowControlWindow: this.session.state.remoteWindowSize ?? null, }; return socketInfo; } private trace(text: string): void { - logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '(' + + this.channelzRef.id + + ') ' + + this.subchannelAddressString + + ' ' + + text + ); } private keepaliveTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, 'keepalive', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + logging.trace( + LogVerbosity.DEBUG, + 'keepalive', + '(' + + this.channelzRef.id + + ') ' + + this.subchannelAddressString + + ' ' + + text + ); } private flowControlTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + logging.trace( + LogVerbosity.DEBUG, + FLOW_CONTROL_TRACER_NAME, + '(' + + this.channelzRef.id + + ') ' + + this.subchannelAddressString + + ' ' + + text + ); } private internalsTrace(text: string): void { - logging.trace(LogVerbosity.DEBUG, 'transport_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text); + logging.trace( + LogVerbosity.DEBUG, + 'transport_internals', + '(' + + this.channelzRef.id + + ') ' + + this.subchannelAddressString + + ' ' + + text + ); } /** @@ -271,7 +351,7 @@ class Http2Transport implements Transport { * @param tooManyPings If true, this was triggered by a GOAWAY with data * indicating that the session was closed becaues the client sent too many * pings. - * @returns + * @returns */ private reportDisconnectToOwner(tooManyPings: boolean) { if (this.disconnectHandled) { @@ -311,7 +391,9 @@ class Http2Transport implements Transport { if (this.channelzEnabled) { this.keepalivesSent += 1; } - this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); + this.keepaliveTrace( + 'Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms' + ); if (!this.keepaliveTimeoutId) { this.keepaliveTimeoutId = setTimeout(() => { this.keepaliveTrace('Ping timeout passed without response'); @@ -375,7 +457,13 @@ class Http2Transport implements Transport { this.activeCalls.add(call); } - createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener, subchannelCallStatsTracker: Partial): Http2SubchannelCall { + createCall( + metadata: Metadata, + host: string, + method: string, + listener: SubchannelCallInterceptingListener, + subchannelCallStatsTracker: Partial + ): Http2SubchannelCall { const headers = metadata.toHttp2Headers(); headers[HTTP2_HEADER_AUTHORITY] = host; headers[HTTP2_HEADER_USER_AGENT] = this.userAgent; @@ -405,13 +493,15 @@ class Http2Transport implements Transport { this.session.state.remoteWindowSize ); this.internalsTrace( - 'session.closed=' + - this.session.closed + - ' session.destroyed=' + - this.session.destroyed + - ' session.socket.destroyed=' + - this.session.socket.destroyed); + 'session.closed=' + + this.session.closed + + ' session.destroyed=' + + this.session.destroyed + + ' session.socket.destroyed=' + + this.session.socket.destroyed + ); let eventTracker: CallEventTracker; + // eslint-disable-next-line prefer-const let call: Http2SubchannelCall; if (this.channelzEnabled) { this.streamTracker.addCallStarted(); @@ -437,8 +527,8 @@ class Http2Transport implements Transport { this.streamTracker.addCallFailed(); } subchannelCallStatsTracker.onStreamEnd?.(success); - } - } + }, + }; } else { eventTracker = { addMessageSent: () => { @@ -447,16 +537,22 @@ class Http2Transport implements Transport { addMessageReceived: () => { subchannelCallStatsTracker.addMessageReceived?.(); }, - onCallEnd: (status) => { + onCallEnd: status => { subchannelCallStatsTracker.onCallEnd?.(status); this.removeActiveCall(call); }, - onStreamEnd: (success) => { + onStreamEnd: success => { subchannelCallStatsTracker.onStreamEnd?.(success); - } - } + }, + }; } - call = new Http2SubchannelCall(http2Stream, eventTracker, listener, this, getNextCallNumber()); + call = new Http2SubchannelCall( + http2Stream, + eventTracker, + listener, + this, + getNextCallNumber() + ); this.addActiveCall(call); return call; } @@ -476,7 +572,11 @@ class Http2Transport implements Transport { } export interface SubchannelConnector { - connect(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions): Promise; + connect( + address: SubchannelAddress, + credentials: ChannelCredentials, + options: ChannelOptions + ): Promise; shutdown(): void; } @@ -484,10 +584,13 @@ export class Http2SubchannelConnector implements SubchannelConnector { private session: http2.ClientHttp2Session | null = null; private isShutdown = false; constructor(private channelTarget: GrpcUri) {} - private trace(text: string) { - - } - private createSession(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions, proxyConnectionResult: ProxyConnectionResult): Promise { + private trace(text: string) {} + private createSession( + address: SubchannelAddress, + credentials: ChannelCredentials, + options: ChannelOptions, + proxyConnectionResult: ProxyConnectionResult + ): Promise { if (this.isShutdown) { return Promise.reject(); } @@ -495,10 +598,15 @@ export class Http2SubchannelConnector implements SubchannelConnector { let remoteName: string | null; if (proxyConnectionResult.realTarget) { remoteName = uriToString(proxyConnectionResult.realTarget); - this.trace('creating HTTP/2 session through proxy to ' + uriToString(proxyConnectionResult.realTarget)); + this.trace( + 'creating HTTP/2 session through proxy to ' + + uriToString(proxyConnectionResult.realTarget) + ); } else { remoteName = null; - this.trace('creating HTTP/2 session to ' + subchannelAddressToString(address)); + this.trace( + 'creating HTTP/2 session to ' + subchannelAddressToString(address) + ); } const targetAuthority = getDefaultAuthority( proxyConnectionResult.realTarget ?? this.channelTarget @@ -507,9 +615,8 @@ export class Http2SubchannelConnector implements SubchannelConnector { credentials._getConnectionOptions() || {}; connectionOptions.maxSendHeaderBlockLength = Number.MAX_SAFE_INTEGER; if ('grpc-node.max_session_memory' in options) { - connectionOptions.maxSessionMemory = options[ - 'grpc-node.max_session_memory' - ]; + connectionOptions.maxSessionMemory = + options['grpc-node.max_session_memory']; } else { /* By default, set a very large max session memory limit, to effectively * disable enforcement of the limit. Some testing indicates that Node's @@ -524,9 +631,8 @@ export class Http2SubchannelConnector implements SubchannelConnector { // to override the target hostname when checking server identity. // This option is used for testing only. if (options['grpc.ssl_target_name_override']) { - const sslTargetNameOverride = options[ - 'grpc.ssl_target_name_override' - ]!; + const sslTargetNameOverride = + options['grpc.ssl_target_name_override']!; connectionOptions.checkServerIdentity = ( host: string, cert: PeerCertificate @@ -565,12 +671,12 @@ export class Http2SubchannelConnector implements SubchannelConnector { } }; } - + connectionOptions = { ...connectionOptions, ...address, }; - + /* http2.connect uses the options here: * https://github.com/nodejs/node/blob/70c32a6d190e2b5d7b9ff9d5b6a459d14e8b7d59/lib/internal/http2/core.js#L3028-L3036 * The spread operator overides earlier values with later ones, so any port @@ -604,11 +710,15 @@ export class Http2SubchannelConnector implements SubchannelConnector { reject(); }); session.once('error', error => { - this.trace('connection failed with error ' + (error as Error).message) + this.trace('connection failed with error ' + (error as Error).message); }); }); } - connect(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions): Promise { + connect( + address: SubchannelAddress, + credentials: ChannelCredentials, + options: ChannelOptions + ): Promise { if (this.isShutdown) { return Promise.reject(); } @@ -625,9 +735,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { // to override the target hostname when checking server identity. // This option is used for testing only. if (options['grpc.ssl_target_name_override']) { - const sslTargetNameOverride = options[ - 'grpc.ssl_target_name_override' - ]!; + const sslTargetNameOverride = options['grpc.ssl_target_name_override']!; connectionOptions.checkServerIdentity = ( host: string, cert: PeerCertificate @@ -652,11 +760,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { } } - return getProxiedConnection( - address, - options, - connectionOptions - ).then( + return getProxiedConnection(address, options, connectionOptions).then( result => this.createSession(address, credentials, options, result) ); } @@ -666,4 +770,4 @@ export class Http2SubchannelConnector implements SubchannelConnector { this.session?.close(); this.session = null; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/test/test-call-credentials.ts b/packages/grpc-js/test/test-call-credentials.ts index e952c5a10..007ed4847 100644 --- a/packages/grpc-js/test/test-call-credentials.ts +++ b/packages/grpc-js/test/test-call-credentials.ts @@ -86,21 +86,16 @@ describe('CallCredentials', () => { const callCredentials = CallCredentials.createFromMetadataGenerator( generateFromServiceURL ); - let metadata: Metadata; - try { - metadata = await callCredentials.generateMetadata({ - service_url: 'foo', - }); - } catch (err) { - throw err; - } + const metadata: Metadata = await callCredentials.generateMetadata({ + service_url: 'foo', + }); + assert.deepStrictEqual(metadata.get('service_url'), ['foo']); }); it('should emit an error if the associated metadataGenerator does', async () => { - const callCredentials = CallCredentials.createFromMetadataGenerator( - generateWithError - ); + const callCredentials = + CallCredentials.createFromMetadataGenerator(generateWithError); let metadata: Metadata | null = null; try { metadata = await callCredentials.generateMetadata({ service_url: '' }); @@ -112,14 +107,10 @@ describe('CallCredentials', () => { it('should combine metadata from multiple generators', async () => { const [callCreds1, callCreds2, callCreds3, callCreds4] = [ - 50, - 100, - 150, - 200, + 50, 100, 150, 200, ].map(ms => { - const generator: CallMetadataGenerator = makeAfterMsElapsedGenerator( - ms - ); + const generator: CallMetadataGenerator = + makeAfterMsElapsedGenerator(ms); return CallCredentials.createFromMetadataGenerator(generator); }); const testCases = [ @@ -147,12 +138,10 @@ describe('CallCredentials', () => { await Promise.all( testCases.map(async testCase => { const { credentials, expected } = testCase; - let metadata: Metadata; - try { - metadata = await credentials.generateMetadata({ service_url: '' }); - } catch (err) { - throw err; - } + const metadata: Metadata = await credentials.generateMetadata({ + service_url: '', + }); + assert.deepStrictEqual(metadata.get('msElapsed'), expected); }) ); diff --git a/packages/grpc-js/test/test-call-propagation.ts b/packages/grpc-js/test/test-call-propagation.ts index 3ce57be17..9ede91318 100644 --- a/packages/grpc-js/test/test-call-propagation.ts +++ b/packages/grpc-js/test/test-call-propagation.ts @@ -29,7 +29,7 @@ function multiDone(done: () => void, target: number) { if (count >= target) { done(); } - } + }; } describe('Call propagation', () => { @@ -39,33 +39,48 @@ describe('Call propagation', () => { let proxyServer: grpc.Server; let proxyClient: ServiceClient; - before((done) => { - Client = loadProtoFile(__dirname + '/fixtures/test_service.proto').TestService as ServiceClientConstructor; + before(done => { + Client = loadProtoFile(__dirname + '/fixtures/test_service.proto') + .TestService as ServiceClientConstructor; server = new grpc.Server(); server.addService(Client.service, { unary: () => {}, clientStream: () => {}, serverStream: () => {}, - bidiStream: () => {} + bidiStream: () => {}, }); proxyServer = new grpc.Server(); - server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - if (error) { - done(error); - return; - } - server.start(); - client = new Client(`localhost:${port}`, grpc.credentials.createInsecure()); - proxyServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, proxyPort) => { + server.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { if (error) { done(error); return; } - proxyServer.start(); - proxyClient = new Client(`localhost:${proxyPort}`, grpc.credentials.createInsecure()); - done(); - }); - }); + server.start(); + client = new Client( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + proxyServer.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, proxyPort) => { + if (error) { + done(error); + return; + } + proxyServer.start(); + proxyClient = new Client( + `localhost:${proxyPort}`, + grpc.credentials.createInsecure() + ); + done(); + } + ); + } + ); }); afterEach(() => { proxyServer.removeService(Client.service); @@ -75,63 +90,84 @@ describe('Call propagation', () => { proxyServer.forceShutdown(); }); describe('Cancellation', () => { - it('should work with unary requests', (done) => { + it('should work with unary requests', done => { done = multiDone(done, 2); + // eslint-disable-next-line prefer-const let call: grpc.ClientUnaryCall; proxyServer.addService(Client.service, { - unary: (parent: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { - client.unary(parent.request, {parent: parent}, (error: grpc.ServiceError, value: unknown) => { - callback(error, value); - assert(error); - assert.strictEqual(error.code, grpc.status.CANCELLED); - done(); - }); + unary: ( + parent: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + client.unary( + parent.request, + { parent: parent }, + (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + } + ); /* Cancel the original call after the server starts processing it to * ensure that it does reach the server. */ call.cancel(); - } - }); - call = proxyClient.unary({}, (error: grpc.ServiceError, value: unknown) => { - assert(error); - assert.strictEqual(error.code, grpc.status.CANCELLED); - done(); + }, }); + call = proxyClient.unary( + {}, + (error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + } + ); }); - it('Should work with client streaming requests', (done) => { + it('Should work with client streaming requests', done => { done = multiDone(done, 2); + // eslint-disable-next-line prefer-const let call: grpc.ClientWritableStream; proxyServer.addService(Client.service, { - clientStream: (parent: grpc.ServerReadableStream, callback: grpc.sendUnaryData) => { - client.clientStream({parent: parent}, (error: grpc.ServiceError, value: unknown) => { - callback(error, value); - assert(error); - assert.strictEqual(error.code, grpc.status.CANCELLED); - done(); - }); + clientStream: ( + parent: grpc.ServerReadableStream, + callback: grpc.sendUnaryData + ) => { + client.clientStream( + { parent: parent }, + (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + } + ); /* Cancel the original call after the server starts processing it to * ensure that it does reach the server. */ call.cancel(); - } - }); - call = proxyClient.clientStream((error: grpc.ServiceError, value: unknown) => { - assert(error); - assert.strictEqual(error.code, grpc.status.CANCELLED); - done(); + }, }); + call = proxyClient.clientStream( + (error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + } + ); }); - it('Should work with server streaming requests', (done) => { + it('Should work with server streaming requests', done => { done = multiDone(done, 2); + // eslint-disable-next-line prefer-const let call: grpc.ClientReadableStream; proxyServer.addService(Client.service, { serverStream: (parent: grpc.ServerWritableStream) => { - const child = client.serverStream(parent.request, {parent: parent}); + const child = client.serverStream(parent.request, { parent: parent }); child.on('error', () => {}); child.on('status', (status: grpc.StatusObject) => { assert.strictEqual(status.code, grpc.status.CANCELLED); done(); }); call.cancel(); - } + }, }); call = proxyClient.serverStream({}); call.on('error', () => {}); @@ -140,19 +176,20 @@ describe('Call propagation', () => { done(); }); }); - it('Should work with bidi streaming requests', (done) => { + it('Should work with bidi streaming requests', done => { done = multiDone(done, 2); + // eslint-disable-next-line prefer-const let call: grpc.ClientDuplexStream; proxyServer.addService(Client.service, { bidiStream: (parent: grpc.ServerDuplexStream) => { - const child = client.bidiStream({parent: parent}); + const child = client.bidiStream({ parent: parent }); child.on('error', () => {}); child.on('status', (status: grpc.StatusObject) => { assert.strictEqual(status.code, grpc.status.CANCELLED); done(); }); call.cancel(); - } + }, }); call = proxyClient.bidiStream(); call.on('error', () => {}); @@ -163,86 +200,113 @@ describe('Call propagation', () => { }); }); describe('Deadlines', () => { - it('should work with unary requests', (done) => { + it('should work with unary requests', done => { done = multiDone(done, 2); - let call: grpc.ClientUnaryCall; proxyServer.addService(Client.service, { - unary: (parent: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { - client.unary(parent.request, {parent: parent, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { - callback(error, value); - assert(error); - assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); - done(); - }); - } + unary: ( + parent: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + client.unary( + parent.request, + { parent: parent, propagate_flags: grpc.propagate.DEADLINE }, + (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + } + ); + }, }); const deadline = new Date(); deadline.setMilliseconds(deadline.getMilliseconds() + 100); - call = proxyClient.unary({}, {deadline}, (error: grpc.ServiceError, value: unknown) => { - assert(error); - assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); - done(); - }); + proxyClient.unary( + {}, + { deadline }, + (error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + } + ); }); - it('Should work with client streaming requests', (done) => { + it('Should work with client streaming requests', done => { done = multiDone(done, 2); - let call: grpc.ClientWritableStream; + proxyServer.addService(Client.service, { - clientStream: (parent: grpc.ServerReadableStream, callback: grpc.sendUnaryData) => { - client.clientStream({parent: parent, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { - callback(error, value); - assert(error); - assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); - done(); - }); - } + clientStream: ( + parent: grpc.ServerReadableStream, + callback: grpc.sendUnaryData + ) => { + client.clientStream( + { parent: parent, propagate_flags: grpc.propagate.DEADLINE }, + (error: grpc.ServiceError, value: unknown) => { + callback(error, value); + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + } + ); + }, }); const deadline = new Date(); deadline.setMilliseconds(deadline.getMilliseconds() + 100); - call = proxyClient.clientStream({deadline, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { - assert(error); - assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); - done(); - }); + proxyClient.clientStream( + { deadline, propagate_flags: grpc.propagate.DEADLINE }, + (error: grpc.ServiceError, value: unknown) => { + assert(error); + assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); + done(); + } + ); }); - it('Should work with server streaming requests', (done) => { + it('Should work with server streaming requests', done => { done = multiDone(done, 2); let call: grpc.ClientReadableStream; proxyServer.addService(Client.service, { serverStream: (parent: grpc.ServerWritableStream) => { - const child = client.serverStream(parent.request, {parent: parent, propagate_flags: grpc.propagate.DEADLINE}); + const child = client.serverStream(parent.request, { + parent: parent, + propagate_flags: grpc.propagate.DEADLINE, + }); child.on('error', () => {}); child.on('status', (status: grpc.StatusObject) => { assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); done(); }); - } + }, }); const deadline = new Date(); deadline.setMilliseconds(deadline.getMilliseconds() + 100); - call = proxyClient.serverStream({}, {deadline}); + // eslint-disable-next-line prefer-const + call = proxyClient.serverStream({}, { deadline }); call.on('error', () => {}); call.on('status', (status: grpc.StatusObject) => { assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); done(); }); }); - it('Should work with bidi streaming requests', (done) => { + it('Should work with bidi streaming requests', done => { done = multiDone(done, 2); let call: grpc.ClientDuplexStream; proxyServer.addService(Client.service, { bidiStream: (parent: grpc.ServerDuplexStream) => { - const child = client.bidiStream({parent: parent, propagate_flags: grpc.propagate.DEADLINE}); + const child = client.bidiStream({ + parent: parent, + propagate_flags: grpc.propagate.DEADLINE, + }); child.on('error', () => {}); child.on('status', (status: grpc.StatusObject) => { assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); done(); }); - } + }, }); const deadline = new Date(); deadline.setMilliseconds(deadline.getMilliseconds() + 100); - call = proxyClient.bidiStream({deadline}); + // eslint-disable-next-line prefer-const + call = proxyClient.bidiStream({ deadline }); call.on('error', () => {}); call.on('status', (status: grpc.StatusObject) => { assert.strictEqual(status.code, grpc.status.DEADLINE_EXCEEDED); @@ -250,4 +314,4 @@ describe('Call propagation', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-channel-credentials.ts b/packages/grpc-js/test/test-channel-credentials.ts index 2b537ac97..d52bb59d0 100644 --- a/packages/grpc-js/test/test-channel-credentials.ts +++ b/packages/grpc-js/test/test-channel-credentials.ts @@ -25,14 +25,18 @@ import { CallCredentials } from '../src/call-credentials'; import { ChannelCredentials } from '../src/channel-credentials'; import * as grpc from '../src'; import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; -import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; +import { + TestServiceClient, + TestServiceHandlers, +} from './generated/TestService'; import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; import { assert2, loadProtoFile, mockFunction } from './common'; import { sendUnaryData, ServerUnaryCall, ServiceError } from '../src'; const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); -const echoService = loadProtoFile(protoFile).EchoService as ServiceClientConstructor; +const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; class CallCredentialsMock implements CallCredentials { child: CallCredentialsMock | null = null; @@ -153,17 +157,20 @@ describe('ChannelCredentials usage', () => { let client: ServiceClient; let server: grpc.Server; before(async () => { - const {ca, key, cert} = await pFixtures; - const serverCreds = grpc.ServerCredentials.createSsl(null, [{private_key: key, cert_chain: cert}]); + const { ca, key, cert } = await pFixtures; + const serverCreds = grpc.ServerCredentials.createSsl(null, [ + { private_key: key, cert_chain: cert }, + ]); const channelCreds = ChannelCredentials.createSsl(ca); - const callCreds = CallCredentials.createFromMetadataGenerator((options, cb) => { - const metadata = new grpc.Metadata(); - metadata.set('test-key', 'test-value'); - cb(null, metadata); - }); + const callCreds = CallCredentials.createFromMetadataGenerator( + (options, cb) => { + const metadata = new grpc.Metadata(); + metadata.set('test-key', 'test-value'); + cb(null, metadata); + } + ); const combinedCreds = channelCreds.compose(callCreds); return new Promise((resolve, reject) => { - server = new grpc.Server(); server.addService(echoService.service, { echo(call: ServerUnaryCall, callback: sendUnaryData) { @@ -171,31 +178,26 @@ describe('ChannelCredentials usage', () => { callback(null, call.request); }, }); - - server.bindAsync( - 'localhost:0', - serverCreds, - (err, port) => { - if (err) { - reject(err); - return; - } - client = new echoService( - `localhost:${port}`, - combinedCreds, - {'grpc.ssl_target_name_override': 'foo.test.google.fr', 'grpc.default_authority': 'foo.test.google.fr'} - ); - server.start(); - resolve(); + + server.bindAsync('localhost:0', serverCreds, (err, port) => { + if (err) { + reject(err); + return; } - ); + client = new echoService(`localhost:${port}`, combinedCreds, { + 'grpc.ssl_target_name_override': 'foo.test.google.fr', + 'grpc.default_authority': 'foo.test.google.fr', + }); + server.start(); + resolve(); + }); }); }); after(() => { server.forceShutdown(); }); - it('Should send the metadata from call credentials attached to channel credentials', (done) => { + it('Should send the metadata from call credentials attached to channel credentials', done => { const call = client.echo( { value: 'test value', value2: 3 }, assert2.mustCall((error: ServiceError, response: any) => { @@ -203,10 +205,12 @@ describe('ChannelCredentials usage', () => { assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); }) ); - call.on('metadata', assert2.mustCall((metadata: grpc.Metadata) => { - assert.deepStrictEqual(metadata.get('test-key'), ['test-value']); - - })); + call.on( + 'metadata', + assert2.mustCall((metadata: grpc.Metadata) => { + assert.deepStrictEqual(metadata.get('test-key'), ['test-value']); + }) + ); assert2.afterMustCallsSatisfied(done); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-channelz.ts b/packages/grpc-js/test/test-channelz.ts index f14145c37..86c90b771 100644 --- a/packages/grpc-js/test/test-channelz.ts +++ b/packages/grpc-js/test/test-channelz.ts @@ -19,7 +19,7 @@ import * as assert from 'assert'; import * as protoLoader from '@grpc/proto-loader'; import * as grpc from '../src'; -import { ProtoGrpcType } from '../src/generated/channelz' +import { ProtoGrpcType } from '../src/generated/channelz'; import { ChannelzClient } from '../src/generated/grpc/channelz/v1/Channelz'; import { Channel__Output } from '../src/generated/grpc/channelz/v1/Channel'; import { Server__Output } from '../src/generated/grpc/channelz/v1/Server'; @@ -32,28 +32,33 @@ const loadedChannelzProto = protoLoader.loadSync('channelz.proto', { enums: String, defaults: true, oneofs: true, - includeDirs: [ - `${__dirname}/../../proto` - ] + includeDirs: [`${__dirname}/../../proto`], }); -const channelzGrpcObject = grpc.loadPackageDefinition(loadedChannelzProto) as unknown as ProtoGrpcType; +const channelzGrpcObject = grpc.loadPackageDefinition( + loadedChannelzProto +) as unknown as ProtoGrpcType; -const TestServiceClient = loadProtoFile(`${__dirname}/fixtures/test_service.proto`).TestService as ServiceClientConstructor; +const TestServiceClient = loadProtoFile( + `${__dirname}/fixtures/test_service.proto` +).TestService as ServiceClientConstructor; const testServiceImpl: grpc.UntypedServiceImplementation = { - unary(call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) { + unary( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) { if (call.request.error) { setTimeout(() => { callback({ code: grpc.status.INVALID_ARGUMENT, - details: call.request.message + details: call.request.message, }); - }, call.request.errorAfter) + }, call.request.errorAfter); } else { - callback(null, {count: 1}); + callback(null, { count: 1 }); } - } -} + }, +}; describe('Channelz', () => { let channelzServer: grpc.Server; @@ -61,18 +66,28 @@ describe('Channelz', () => { let testServer: grpc.Server; let testClient: ServiceClient; - before((done) => { + before(done => { channelzServer = new grpc.Server(); - channelzServer.addService(grpc.getChannelzServiceDefinition(), grpc.getChannelzHandlers()); - channelzServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - if (error) { - done(error); - return; + channelzServer.addService( + grpc.getChannelzServiceDefinition(), + grpc.getChannelzHandlers() + ); + channelzServer.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + if (error) { + done(error); + return; + } + channelzServer.start(); + channelzClient = new channelzGrpcObject.grpc.channelz.v1.Channelz( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + done(); } - channelzServer.start(); - channelzClient = new channelzGrpcObject.grpc.channelz.v1.Channelz(`localhost:${port}`, grpc.credentials.createInsecure()); - done(); - }); + ); }); after(() => { @@ -80,18 +95,25 @@ describe('Channelz', () => { channelzServer.forceShutdown(); }); - beforeEach((done) => { + beforeEach(done => { testServer = new grpc.Server(); testServer.addService(TestServiceClient.service, testServiceImpl); - testServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - if (error) { - done(error); - return; + testServer.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + if (error) { + done(error); + return; + } + testServer.start(); + testClient = new TestServiceClient( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + done(); } - testServer.start(); - testClient = new TestServiceClient(`localhost:${port}`, grpc.credentials.createInsecure()); - done(); - }); + ); }); afterEach(() => { @@ -99,210 +121,439 @@ describe('Channelz', () => { testServer.forceShutdown(); }); - it('should see a newly created channel', (done) => { + it('should see a newly created channel', done => { // Test that the specific test client channel info can be retrieved - channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, result) => { - assert.ifError(error); - assert(result); - assert(result.channel); - assert(result.channel.ref); - assert.strictEqual(+result.channel.ref.channel_id, testClient.getChannel().getChannelzRef().id); - // Test that the channel is in the list of top channels - channelzClient.getTopChannels({start_channel_id: testClient.getChannel().getChannelzRef().id, max_results:1}, (error, result) => { + channelzClient.GetChannel( + { channel_id: testClient.getChannel().getChannelzRef().id }, + (error, result) => { assert.ifError(error); assert(result); - assert.strictEqual(result.channel.length, 1); - assert(result.channel[0].ref); - assert.strictEqual(+result.channel[0].ref.channel_id, testClient.getChannel().getChannelzRef().id); - done(); - }); - }); + assert(result.channel); + assert(result.channel.ref); + assert.strictEqual( + +result.channel.ref.channel_id, + testClient.getChannel().getChannelzRef().id + ); + // Test that the channel is in the list of top channels + channelzClient.getTopChannels( + { + start_channel_id: testClient.getChannel().getChannelzRef().id, + max_results: 1, + }, + (error, result) => { + assert.ifError(error); + assert(result); + assert.strictEqual(result.channel.length, 1); + assert(result.channel[0].ref); + assert.strictEqual( + +result.channel[0].ref.channel_id, + testClient.getChannel().getChannelzRef().id + ); + done(); + } + ); + } + ); }); - it('should see a newly created server', (done) => { + it('should see a newly created server', done => { // Test that the specific test server info can be retrieved - channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, result) => { - assert.ifError(error); - assert(result); - assert(result.server); - assert(result.server.ref); - assert.strictEqual(+result.server.ref.server_id, testServer.getChannelzRef().id); - // Test that the server is in the list of servers - channelzClient.getServers({start_server_id: testServer.getChannelzRef().id, max_results: 1}, (error, result) => { + channelzClient.getServer( + { server_id: testServer.getChannelzRef().id }, + (error, result) => { assert.ifError(error); assert(result); - assert.strictEqual(result.server.length, 1); - assert(result.server[0].ref); - assert.strictEqual(+result.server[0].ref.server_id, testServer.getChannelzRef().id); - done(); - }); - }); + assert(result.server); + assert(result.server.ref); + assert.strictEqual( + +result.server.ref.server_id, + testServer.getChannelzRef().id + ); + // Test that the server is in the list of servers + channelzClient.getServers( + { start_server_id: testServer.getChannelzRef().id, max_results: 1 }, + (error, result) => { + assert.ifError(error); + assert(result); + assert.strictEqual(result.server.length, 1); + assert(result.server[0].ref); + assert.strictEqual( + +result.server[0].ref.server_id, + testServer.getChannelzRef().id + ); + done(); + } + ); + } + ); }); - it('should count successful calls', (done) => { + it('should count successful calls', done => { testClient.unary({}, (error: grpc.ServiceError, value: unknown) => { assert.ifError(error); // Channel data tests - channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, channelResult) => { - assert.ifError(error); - assert(channelResult); - assert(channelResult.channel); - assert(channelResult.channel.ref); - assert(channelResult.channel.data); - assert.strictEqual(+channelResult.channel.data.calls_started, 1); - assert.strictEqual(+channelResult.channel.data.calls_succeeded, 1); - assert.strictEqual(+channelResult.channel.data.calls_failed, 0); - assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); - channelzClient.getSubchannel({subchannel_id: channelResult.channel.subchannel_ref[0].subchannel_id}, (error, subchannelResult) => { + channelzClient.GetChannel( + { channel_id: testClient.getChannel().getChannelzRef().id }, + (error, channelResult) => { assert.ifError(error); - assert(subchannelResult); - assert(subchannelResult.subchannel); - assert(subchannelResult.subchannel.ref); - assert(subchannelResult.subchannel.data); - assert.strictEqual(subchannelResult.subchannel.ref.subchannel_id, channelResult.channel!.subchannel_ref[0].subchannel_id); - assert.strictEqual(+subchannelResult.subchannel.data.calls_started, 1); - assert.strictEqual(+subchannelResult.subchannel.data.calls_succeeded, 1); - assert.strictEqual(+subchannelResult.subchannel.data.calls_failed, 0); - assert.strictEqual(subchannelResult.subchannel.socket_ref.length, 1); - channelzClient.getSocket({socket_id: subchannelResult.subchannel.socket_ref[0].socket_id}, (error, socketResult) => { - assert.ifError(error); - assert(socketResult); - assert(socketResult.socket); - assert(socketResult.socket.ref); - assert(socketResult.socket.data); - assert.strictEqual(socketResult.socket.ref.socket_id, subchannelResult.subchannel!.socket_ref[0].socket_id); - assert.strictEqual(+socketResult.socket.data.streams_started, 1); - assert.strictEqual(+socketResult.socket.data.streams_succeeded, 1); - assert.strictEqual(+socketResult.socket.data.streams_failed, 0); - assert.strictEqual(+socketResult.socket.data.messages_received, 1); - assert.strictEqual(+socketResult.socket.data.messages_sent, 1); - // Server data tests - channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, serverResult) => { + assert(channelResult); + assert(channelResult.channel); + assert(channelResult.channel.ref); + assert(channelResult.channel.data); + assert.strictEqual(+channelResult.channel.data.calls_started, 1); + assert.strictEqual(+channelResult.channel.data.calls_succeeded, 1); + assert.strictEqual(+channelResult.channel.data.calls_failed, 0); + assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); + channelzClient.getSubchannel( + { + subchannel_id: + channelResult.channel.subchannel_ref[0].subchannel_id, + }, + (error, subchannelResult) => { assert.ifError(error); - assert(serverResult); - assert(serverResult.server); - assert(serverResult.server.ref); - assert(serverResult.server.data); - assert.strictEqual(+serverResult.server.ref.server_id, testServer.getChannelzRef().id); - assert.strictEqual(+serverResult.server.data.calls_started, 1); - assert.strictEqual(+serverResult.server.data.calls_succeeded, 1); - assert.strictEqual(+serverResult.server.data.calls_failed, 0); - channelzClient.getServerSockets({server_id: testServer.getChannelzRef().id}, (error, socketsResult) => { - assert.ifError(error); - assert(socketsResult); - assert.strictEqual(socketsResult.socket_ref.length, 1); - channelzClient.getSocket({socket_id: socketsResult.socket_ref[0].socket_id}, (error, serverSocketResult) => { + assert(subchannelResult); + assert(subchannelResult.subchannel); + assert(subchannelResult.subchannel.ref); + assert(subchannelResult.subchannel.data); + assert.strictEqual( + subchannelResult.subchannel.ref.subchannel_id, + channelResult.channel!.subchannel_ref[0].subchannel_id + ); + assert.strictEqual( + +subchannelResult.subchannel.data.calls_started, + 1 + ); + assert.strictEqual( + +subchannelResult.subchannel.data.calls_succeeded, + 1 + ); + assert.strictEqual( + +subchannelResult.subchannel.data.calls_failed, + 0 + ); + assert.strictEqual( + subchannelResult.subchannel.socket_ref.length, + 1 + ); + channelzClient.getSocket( + { + socket_id: + subchannelResult.subchannel.socket_ref[0].socket_id, + }, + (error, socketResult) => { assert.ifError(error); - assert(serverSocketResult); - assert(serverSocketResult.socket); - assert(serverSocketResult.socket.ref); - assert(serverSocketResult.socket.data); - assert.strictEqual(serverSocketResult.socket.ref.socket_id, socketsResult.socket_ref[0].socket_id); - assert.strictEqual(+serverSocketResult.socket.data.streams_started, 1); - assert.strictEqual(+serverSocketResult.socket.data.streams_succeeded, 1); - assert.strictEqual(+serverSocketResult.socket.data.streams_failed, 0); - assert.strictEqual(+serverSocketResult.socket.data.messages_received, 1); - assert.strictEqual(+serverSocketResult.socket.data.messages_sent, 1); - done(); - }); - }); - }); - }); - }); - }); + assert(socketResult); + assert(socketResult.socket); + assert(socketResult.socket.ref); + assert(socketResult.socket.data); + assert.strictEqual( + socketResult.socket.ref.socket_id, + subchannelResult.subchannel!.socket_ref[0].socket_id + ); + assert.strictEqual( + +socketResult.socket.data.streams_started, + 1 + ); + assert.strictEqual( + +socketResult.socket.data.streams_succeeded, + 1 + ); + assert.strictEqual( + +socketResult.socket.data.streams_failed, + 0 + ); + assert.strictEqual( + +socketResult.socket.data.messages_received, + 1 + ); + assert.strictEqual( + +socketResult.socket.data.messages_sent, + 1 + ); + // Server data tests + channelzClient.getServer( + { server_id: testServer.getChannelzRef().id }, + (error, serverResult) => { + assert.ifError(error); + assert(serverResult); + assert(serverResult.server); + assert(serverResult.server.ref); + assert(serverResult.server.data); + assert.strictEqual( + +serverResult.server.ref.server_id, + testServer.getChannelzRef().id + ); + assert.strictEqual( + +serverResult.server.data.calls_started, + 1 + ); + assert.strictEqual( + +serverResult.server.data.calls_succeeded, + 1 + ); + assert.strictEqual( + +serverResult.server.data.calls_failed, + 0 + ); + channelzClient.getServerSockets( + { server_id: testServer.getChannelzRef().id }, + (error, socketsResult) => { + assert.ifError(error); + assert(socketsResult); + assert.strictEqual( + socketsResult.socket_ref.length, + 1 + ); + channelzClient.getSocket( + { + socket_id: socketsResult.socket_ref[0].socket_id, + }, + (error, serverSocketResult) => { + assert.ifError(error); + assert(serverSocketResult); + assert(serverSocketResult.socket); + assert(serverSocketResult.socket.ref); + assert(serverSocketResult.socket.data); + assert.strictEqual( + serverSocketResult.socket.ref.socket_id, + socketsResult.socket_ref[0].socket_id + ); + assert.strictEqual( + +serverSocketResult.socket.data.streams_started, + 1 + ); + assert.strictEqual( + +serverSocketResult.socket.data + .streams_succeeded, + 1 + ); + assert.strictEqual( + +serverSocketResult.socket.data.streams_failed, + 0 + ); + assert.strictEqual( + +serverSocketResult.socket.data + .messages_received, + 1 + ); + assert.strictEqual( + +serverSocketResult.socket.data.messages_sent, + 1 + ); + done(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); }); }); - it('should count failed calls', (done) => { - testClient.unary({error: true}, (error: grpc.ServiceError, value: unknown) => { - assert(error); - // Channel data tests - channelzClient.GetChannel({channel_id: testClient.getChannel().getChannelzRef().id}, (error, channelResult) => { - assert.ifError(error); - assert(channelResult); - assert(channelResult.channel); - assert(channelResult.channel.ref); - assert(channelResult.channel.data); - assert.strictEqual(+channelResult.channel.data.calls_started, 1); - assert.strictEqual(+channelResult.channel.data.calls_succeeded, 0); - assert.strictEqual(+channelResult.channel.data.calls_failed, 1); - assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); - channelzClient.getSubchannel({subchannel_id: channelResult.channel.subchannel_ref[0].subchannel_id}, (error, subchannelResult) => { - assert.ifError(error); - assert(subchannelResult); - assert(subchannelResult.subchannel); - assert(subchannelResult.subchannel.ref); - assert(subchannelResult.subchannel.data); - assert.strictEqual(subchannelResult.subchannel.ref.subchannel_id, channelResult.channel!.subchannel_ref[0].subchannel_id); - assert.strictEqual(+subchannelResult.subchannel.data.calls_started, 1); - assert.strictEqual(+subchannelResult.subchannel.data.calls_succeeded, 0); - assert.strictEqual(+subchannelResult.subchannel.data.calls_failed, 1); - assert.strictEqual(subchannelResult.subchannel.socket_ref.length, 1); - channelzClient.getSocket({socket_id: subchannelResult.subchannel.socket_ref[0].socket_id}, (error, socketResult) => { + it('should count failed calls', done => { + testClient.unary( + { error: true }, + (error: grpc.ServiceError, value: unknown) => { + assert(error); + // Channel data tests + channelzClient.GetChannel( + { channel_id: testClient.getChannel().getChannelzRef().id }, + (error, channelResult) => { assert.ifError(error); - assert(socketResult); - assert(socketResult.socket); - assert(socketResult.socket.ref); - assert(socketResult.socket.data); - assert.strictEqual(socketResult.socket.ref.socket_id, subchannelResult.subchannel!.socket_ref[0].socket_id); - assert.strictEqual(+socketResult.socket.data.streams_started, 1); - assert.strictEqual(+socketResult.socket.data.streams_succeeded, 1); - assert.strictEqual(+socketResult.socket.data.streams_failed, 0); - assert.strictEqual(+socketResult.socket.data.messages_received, 0); - assert.strictEqual(+socketResult.socket.data.messages_sent, 1); - // Server data tests - channelzClient.getServer({server_id: testServer.getChannelzRef().id}, (error, serverResult) => { - assert.ifError(error); - assert(serverResult); - assert(serverResult.server); - assert(serverResult.server.ref); - assert(serverResult.server.data); - assert.strictEqual(+serverResult.server.ref.server_id, testServer.getChannelzRef().id); - assert.strictEqual(+serverResult.server.data.calls_started, 1); - assert.strictEqual(+serverResult.server.data.calls_succeeded, 0); - assert.strictEqual(+serverResult.server.data.calls_failed, 1); - channelzClient.getServerSockets({server_id: testServer.getChannelzRef().id}, (error, socketsResult) => { + assert(channelResult); + assert(channelResult.channel); + assert(channelResult.channel.ref); + assert(channelResult.channel.data); + assert.strictEqual(+channelResult.channel.data.calls_started, 1); + assert.strictEqual(+channelResult.channel.data.calls_succeeded, 0); + assert.strictEqual(+channelResult.channel.data.calls_failed, 1); + assert.strictEqual(channelResult.channel.subchannel_ref.length, 1); + channelzClient.getSubchannel( + { + subchannel_id: + channelResult.channel.subchannel_ref[0].subchannel_id, + }, + (error, subchannelResult) => { assert.ifError(error); - assert(socketsResult); - assert.strictEqual(socketsResult.socket_ref.length, 1); - channelzClient.getSocket({socket_id: socketsResult.socket_ref[0].socket_id}, (error, serverSocketResult) => { - assert.ifError(error); - assert(serverSocketResult); - assert(serverSocketResult.socket); - assert(serverSocketResult.socket.ref); - assert(serverSocketResult.socket.data); - assert.strictEqual(serverSocketResult.socket.ref.socket_id, socketsResult.socket_ref[0].socket_id); - assert.strictEqual(+serverSocketResult.socket.data.streams_started, 1); - assert.strictEqual(+serverSocketResult.socket.data.streams_succeeded, 0); - assert.strictEqual(+serverSocketResult.socket.data.streams_failed, 1); - assert.strictEqual(+serverSocketResult.socket.data.messages_received, 1); - assert.strictEqual(+serverSocketResult.socket.data.messages_sent, 0); - done(); - }); - }); - }); - }); - }); - }); - }); + assert(subchannelResult); + assert(subchannelResult.subchannel); + assert(subchannelResult.subchannel.ref); + assert(subchannelResult.subchannel.data); + assert.strictEqual( + subchannelResult.subchannel.ref.subchannel_id, + channelResult.channel!.subchannel_ref[0].subchannel_id + ); + assert.strictEqual( + +subchannelResult.subchannel.data.calls_started, + 1 + ); + assert.strictEqual( + +subchannelResult.subchannel.data.calls_succeeded, + 0 + ); + assert.strictEqual( + +subchannelResult.subchannel.data.calls_failed, + 1 + ); + assert.strictEqual( + subchannelResult.subchannel.socket_ref.length, + 1 + ); + channelzClient.getSocket( + { + socket_id: + subchannelResult.subchannel.socket_ref[0].socket_id, + }, + (error, socketResult) => { + assert.ifError(error); + assert(socketResult); + assert(socketResult.socket); + assert(socketResult.socket.ref); + assert(socketResult.socket.data); + assert.strictEqual( + socketResult.socket.ref.socket_id, + subchannelResult.subchannel!.socket_ref[0].socket_id + ); + assert.strictEqual( + +socketResult.socket.data.streams_started, + 1 + ); + assert.strictEqual( + +socketResult.socket.data.streams_succeeded, + 1 + ); + assert.strictEqual( + +socketResult.socket.data.streams_failed, + 0 + ); + assert.strictEqual( + +socketResult.socket.data.messages_received, + 0 + ); + assert.strictEqual( + +socketResult.socket.data.messages_sent, + 1 + ); + // Server data tests + channelzClient.getServer( + { server_id: testServer.getChannelzRef().id }, + (error, serverResult) => { + assert.ifError(error); + assert(serverResult); + assert(serverResult.server); + assert(serverResult.server.ref); + assert(serverResult.server.data); + assert.strictEqual( + +serverResult.server.ref.server_id, + testServer.getChannelzRef().id + ); + assert.strictEqual( + +serverResult.server.data.calls_started, + 1 + ); + assert.strictEqual( + +serverResult.server.data.calls_succeeded, + 0 + ); + assert.strictEqual( + +serverResult.server.data.calls_failed, + 1 + ); + channelzClient.getServerSockets( + { server_id: testServer.getChannelzRef().id }, + (error, socketsResult) => { + assert.ifError(error); + assert(socketsResult); + assert.strictEqual( + socketsResult.socket_ref.length, + 1 + ); + channelzClient.getSocket( + { + socket_id: + socketsResult.socket_ref[0].socket_id, + }, + (error, serverSocketResult) => { + assert.ifError(error); + assert(serverSocketResult); + assert(serverSocketResult.socket); + assert(serverSocketResult.socket.ref); + assert(serverSocketResult.socket.data); + assert.strictEqual( + serverSocketResult.socket.ref.socket_id, + socketsResult.socket_ref[0].socket_id + ); + assert.strictEqual( + +serverSocketResult.socket.data + .streams_started, + 1 + ); + assert.strictEqual( + +serverSocketResult.socket.data + .streams_succeeded, + 0 + ); + assert.strictEqual( + +serverSocketResult.socket.data + .streams_failed, + 1 + ); + assert.strictEqual( + +serverSocketResult.socket.data + .messages_received, + 1 + ); + assert.strictEqual( + +serverSocketResult.socket.data.messages_sent, + 0 + ); + done(); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); + } + ); }); }); describe('Disabling channelz', () => { let testServer: grpc.Server; let testClient: ServiceClient; - beforeEach((done) => { - testServer = new grpc.Server({'grpc.enable_channelz': 0}); + beforeEach(done => { + testServer = new grpc.Server({ 'grpc.enable_channelz': 0 }); testServer.addService(TestServiceClient.service, testServiceImpl); - testServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - if (error) { - done(error); - return; + testServer.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + if (error) { + done(error); + return; + } + testServer.start(); + testClient = new TestServiceClient( + `localhost:${port}`, + grpc.credentials.createInsecure(), + { 'grpc.enable_channelz': 0 } + ); + done(); } - testServer.start(); - testClient = new TestServiceClient(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.enable_channelz': 0}); - done(); - }); + ); }); afterEach(() => { @@ -310,12 +561,16 @@ describe('Disabling channelz', () => { testServer.forceShutdown(); }); - it('Should still work', (done) => { + it('Should still work', done => { const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 1); - testClient.unary({}, {deadline}, (error: grpc.ServiceError, value: unknown) => { - assert.ifError(error); - done(); - }); + testClient.unary( + {}, + { deadline }, + (error: grpc.ServiceError, value: unknown) => { + assert.ifError(error); + done(); + } + ); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-client.ts b/packages/grpc-js/test/test-client.ts index 21dad99f1..67b396015 100644 --- a/packages/grpc-js/test/test-client.ts +++ b/packages/grpc-js/test/test-client.ts @@ -20,7 +20,7 @@ import * as assert from 'assert'; import * as grpc from '../src'; import { Server, ServerCredentials } from '../src'; import { Client } from '../src'; -import { ConnectivityState } from "../src/connectivity-state"; +import { ConnectivityState } from '../src/connectivity-state'; const clientInsecureCreds = grpc.credentials.createInsecure(); const serverInsecureCreds = ServerCredentials.createInsecure(); @@ -32,19 +32,12 @@ describe('Client', () => { before(done => { server = new Server(); - server.bindAsync( - 'localhost:0', - serverInsecureCreds, - (err, port) => { - assert.ifError(err); - client = new Client( - `localhost:${port}`, - clientInsecureCreds - ); - server.start(); - done(); - } - ); + server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { + assert.ifError(err); + client = new Client(`localhost:${port}`, clientInsecureCreds); + server.start(); + done(); + }); }); after(done => { @@ -79,18 +72,30 @@ describe('Client without a server', () => { after(() => { client.close(); }); - it('should fail multiple calls to the nonexistent server', function(done) { + it('should fail multiple calls to the nonexistent server', function (done) { this.timeout(5000); // Regression test for https://github.com/grpc/grpc-node/issues/1411 - client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { - assert(error); - assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); - client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + client.makeUnaryRequest( + '/service/method', + x => x, + x => x, + Buffer.from([]), + (error, value) => { assert(error); assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); - done(); - }); - }); + client.makeUnaryRequest( + '/service/method', + x => x, + x => x, + Buffer.from([]), + (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + done(); + } + ); + } + ); }); }); @@ -103,17 +108,29 @@ describe('Client with a nonexistent target domain', () => { after(() => { client.close(); }); - it('should fail multiple calls', function(done) { + it('should fail multiple calls', function (done) { this.timeout(5000); // Regression test for https://github.com/grpc/grpc-node/issues/1411 - client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { - assert(error); - assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); - client.makeUnaryRequest('/service/method', x => x, x => x, Buffer.from([]), (error, value) => { + client.makeUnaryRequest( + '/service/method', + x => x, + x => x, + Buffer.from([]), + (error, value) => { assert(error); assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); - done(); - }); - }); + client.makeUnaryRequest( + '/service/method', + x => x, + x => x, + Buffer.from([]), + (error, value) => { + assert(error); + assert.strictEqual(error?.code, grpc.status.UNAVAILABLE); + done(); + } + ); + } + ); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-deadline.ts b/packages/grpc-js/test/test-deadline.ts index bb6b3ba9b..315f8b3cf 100644 --- a/packages/grpc-js/test/test-deadline.ts +++ b/packages/grpc-js/test/test-deadline.ts @@ -29,40 +29,49 @@ const serverInsecureCreds = ServerCredentials.createInsecure(); const TIMEOUT_SERVICE_CONFIG: ServiceConfig = { loadBalancingConfig: [], - methodConfig: [{ - name: [ - {service: 'TestService'} - ], - timeout: { - seconds: 1, - nanos: 0 - } - }] + methodConfig: [ + { + name: [{ service: 'TestService' }], + timeout: { + seconds: 1, + nanos: 0, + }, + }, + ], }; describe('Client with configured timeout', () => { let server: grpc.Server; let Client: ServiceClientConstructor; let client: ServiceClient; - + before(done => { - Client = loadProtoFile(__dirname + '/fixtures/test_service.proto').TestService as ServiceClientConstructor; + Client = loadProtoFile(__dirname + '/fixtures/test_service.proto') + .TestService as ServiceClientConstructor; server = new grpc.Server(); server.addService(Client.service, { unary: () => {}, clientStream: () => {}, serverStream: () => {}, - bidiStream: () => {} + bidiStream: () => {}, }); - server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - if (error) { - done(error); - return; + server.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + if (error) { + done(error); + return; + } + server.start(); + client = new Client( + `localhost:${port}`, + grpc.credentials.createInsecure(), + { 'grpc.service_config': JSON.stringify(TIMEOUT_SERVICE_CONFIG) } + ); + done(); } - server.start(); - client = new Client(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(TIMEOUT_SERVICE_CONFIG)}); - done(); - }); + ); }); after(done => { @@ -71,7 +80,7 @@ describe('Client with configured timeout', () => { }); it('Should end calls without explicit deadline with DEADLINE_EXCEEDED', done => { - client.unary({}, (error: grpc.ServiceError, value: unknown) =>{ + client.unary({}, (error: grpc.ServiceError, value: unknown) => { assert(error); assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); done(); @@ -81,10 +90,10 @@ describe('Client with configured timeout', () => { it('Should end calls with a long explicit deadline with DEADLINE_EXCEEDED', done => { const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 20); - client.unary({}, (error: grpc.ServiceError, value: unknown) =>{ + client.unary({}, (error: grpc.ServiceError, value: unknown) => { assert(error); assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); done(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-global-subchannel-pool.ts b/packages/grpc-js/test/test-global-subchannel-pool.ts index 999a11bf7..f49221446 100644 --- a/packages/grpc-js/test/test-global-subchannel-pool.ts +++ b/packages/grpc-js/test/test-global-subchannel-pool.ts @@ -19,13 +19,20 @@ import * as assert from 'assert'; import * as path from 'path'; import * as grpc from '../src'; -import {sendUnaryData, Server, ServerCredentials, ServerUnaryCall, ServiceClientConstructor, ServiceError} from '../src'; +import { + sendUnaryData, + Server, + ServerCredentials, + ServerUnaryCall, + ServiceClientConstructor, + ServiceError, +} from '../src'; -import {loadProtoFile} from './common'; +import { loadProtoFile } from './common'; const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); -const echoService = - loadProtoFile(protoFile).EchoService as ServiceClientConstructor; +const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; describe('Global subchannel pool', () => { let server: Server; @@ -45,72 +52,84 @@ describe('Global subchannel pool', () => { }); server.bindAsync( - 'localhost:0', ServerCredentials.createInsecure(), (err, port) => { - assert.ifError(err); - serverPort = port; - server.start(); - done(); - }); + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + serverPort = port; + server.start(); + done(); + } + ); }); beforeEach(() => { promises = []; - }) + }); after(done => { server.tryShutdown(done); }); function callService(client: InstanceType) { - return new Promise((resolve) => { - const request = {value: 'test value', value2: 3}; + return new Promise(resolve => { + const request = { value: 'test value', value2: 3 }; client.echo(request, (error: ServiceError, response: any) => { assert.ifError(error); assert.deepStrictEqual(response, request); resolve(); }); - }) + }); } function connect() { const grpcOptions = { 'grpc.use_local_subchannel_pool': 0, - } + }; client1 = new echoService( - `127.0.0.1:${serverPort}`, grpc.credentials.createInsecure(), - grpcOptions); + `127.0.0.1:${serverPort}`, + grpc.credentials.createInsecure(), + grpcOptions + ); client2 = new echoService( - `127.0.0.1:${serverPort}`, grpc.credentials.createInsecure(), - grpcOptions); + `127.0.0.1:${serverPort}`, + grpc.credentials.createInsecure(), + grpcOptions + ); } /* This is a regression test for a bug where client1.close in the * waitForReady callback would cause the subchannel to transition to IDLE * even though client2 is also using it. */ - it('Should handle client.close calls in waitForReady', - done => { - connect(); - - promises.push(new Promise((resolve) => { - client1.waitForReady(Date.now() + 50, (error) => { - assert.ifError(error); - client1.close(); - resolve(); - }); - })) - - promises.push(new Promise((resolve) => { - client2.waitForReady(Date.now() + 50, (error) => { + it('Should handle client.close calls in waitForReady', done => { + connect(); + + promises.push( + new Promise(resolve => { + client1.waitForReady(Date.now() + 50, error => { assert.ifError(error); + client1.close(); resolve(); - }); - })) + }); + }) + ); - Promise.all(promises).then(() => {done()}); - }) + promises.push( + new Promise(resolve => { + client2.waitForReady(Date.now() + 50, error => { + assert.ifError(error); + resolve(); + }); + }) + ); + + Promise.all(promises).then(() => { + done(); + }); + }); it('Call the service', done => { promises.push(callService(client2)); @@ -118,13 +137,13 @@ describe('Global subchannel pool', () => { Promise.all(promises).then(() => { done(); }); - }) + }); it('Should complete the client lifecycle without error', done => { setTimeout(() => { client1.close(); client2.close(); - done() + done(); }, 500); }); }); diff --git a/packages/grpc-js/test/test-local-subchannel-pool.ts b/packages/grpc-js/test/test-local-subchannel-pool.ts index 081b2d3dc..00da9c64e 100644 --- a/packages/grpc-js/test/test-local-subchannel-pool.ts +++ b/packages/grpc-js/test/test-local-subchannel-pool.ts @@ -18,7 +18,14 @@ import * as assert from 'assert'; import * as path from 'path'; import * as grpc from '../src'; -import { sendUnaryData, Server, ServerCredentials, ServerUnaryCall, ServiceClientConstructor, ServiceError } from "../src"; +import { + sendUnaryData, + Server, + ServerCredentials, + ServerUnaryCall, + ServiceClientConstructor, + ServiceError, +} from '../src'; import { loadProtoFile } from './common'; const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); @@ -30,7 +37,6 @@ describe('Local subchannel pool', () => { let serverPort: number; before(done => { - server = new Server(); server.addService(echoService.service, { echo(call: ServerUnaryCall, callback: sendUnaryData) { @@ -58,7 +64,7 @@ describe('Local subchannel pool', () => { const client = new echoService( `localhost:${serverPort}`, grpc.credentials.createInsecure(), - {'grpc.use_local_subchannel_pool': 1} + { 'grpc.use_local_subchannel_pool': 1 } ); client.echo( { value: 'test value', value2: 3 }, @@ -70,4 +76,4 @@ describe('Local subchannel pool', () => { } ); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts index c9021e605..aa35f45e6 100644 --- a/packages/grpc-js/test/test-outlier-detection.ts +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -19,7 +19,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as grpc from '../src'; import { loadProtoFile } from './common'; -import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection' +import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection'; import { ServiceClient } from '../src/make-client'; function multiDone(done: Mocha.Done, target: number) { @@ -32,7 +32,7 @@ function multiDone(done: Mocha.Done, target: number) { if (count >= target) { done(); } - } + }; } const defaultOutlierDetectionServiceConfig = { @@ -42,13 +42,15 @@ const defaultOutlierDetectionServiceConfig = { outlier_detection: { success_rate_ejection: {}, failure_percentage_ejection: {}, - child_policy: [{round_robin: {}}] - } - } - ] + child_policy: [{ round_robin: {} }], + }, + }, + ], }; -const defaultOutlierDetectionServiceConfigString = JSON.stringify(defaultOutlierDetectionServiceConfig); +const defaultOutlierDetectionServiceConfigString = JSON.stringify( + defaultOutlierDetectionServiceConfig +); const successRateOutlierDetectionServiceConfig = { methodConfig: [], @@ -57,22 +59,24 @@ const successRateOutlierDetectionServiceConfig = { outlier_detection: { interval: { seconds: 1, - nanos: 0 + nanos: 0, }, base_ejection_time: { seconds: 3, - nanos: 0 + nanos: 0, }, success_rate_ejection: { - request_volume: 5 + request_volume: 5, }, - child_policy: [{round_robin: {}}] - } - } - ] + child_policy: [{ round_robin: {} }], + }, + }, + ], }; -const successRateOutlierDetectionServiceConfigString = JSON.stringify(successRateOutlierDetectionServiceConfig); +const successRateOutlierDetectionServiceConfigString = JSON.stringify( + successRateOutlierDetectionServiceConfig +); const failurePercentageOutlierDetectionServiceConfig = { methodConfig: [], @@ -81,37 +85,45 @@ const failurePercentageOutlierDetectionServiceConfig = { outlier_detection: { interval: { seconds: 1, - nanos: 0 + nanos: 0, }, base_ejection_time: { seconds: 3, - nanos: 0 + nanos: 0, }, failure_percentage_ejection: { - request_volume: 5 + request_volume: 5, }, - child_policy: [{round_robin: {}}] - } - } - ] + child_policy: [{ round_robin: {} }], + }, + }, + ], }; -const falurePercentageOutlierDetectionServiceConfigString = JSON.stringify(failurePercentageOutlierDetectionServiceConfig); +const falurePercentageOutlierDetectionServiceConfigString = JSON.stringify( + failurePercentageOutlierDetectionServiceConfig +); const goodService = { - echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { - callback(null, call.request) - } + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + callback(null, call.request); + }, }; const badService = { - echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { callback({ code: grpc.status.PERMISSION_DENIED, - details: 'Permission denied' - }) - } -} + details: 'Permission denied', + }); + }, +}; const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); const EchoService = loadProtoFile(protoFile) @@ -123,9 +135,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { interval: { seconds: -1, - nanos: 0 + nanos: 0, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -135,9 +147,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { interval: { seconds: 1e12, - nanos: 0 + nanos: 0, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -147,9 +159,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { interval: { seconds: 0, - nanos: -1 + nanos: -1, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -159,9 +171,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { interval: { seconds: 0, - nanos: 1e12 + nanos: 1e12, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -173,9 +185,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { base_ejection_time: { seconds: -1, - nanos: 0 + nanos: 0, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -185,9 +197,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { base_ejection_time: { seconds: 1e12, - nanos: 0 + nanos: 0, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -197,9 +209,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { base_ejection_time: { seconds: 0, - nanos: -1 + nanos: -1, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -209,9 +221,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { base_ejection_time: { seconds: 0, - nanos: 1e12 + nanos: 1e12, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -223,9 +235,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { max_ejection_time: { seconds: -1, - nanos: 0 + nanos: 0, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -235,9 +247,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { max_ejection_time: { seconds: 1e12, - nanos: 0 + nanos: 0, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -247,9 +259,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { max_ejection_time: { seconds: 0, - nanos: -1 + nanos: -1, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -259,9 +271,9 @@ describe('Outlier detection config validation', () => { const loadBalancingConfig = { max_ejection_time: { seconds: 0, - nanos: 1e12 + nanos: 1e12, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -272,7 +284,7 @@ describe('Outlier detection config validation', () => { it('Should reject a value above 100', () => { const loadBalancingConfig = { max_ejection_percent: 101, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -281,7 +293,7 @@ describe('Outlier detection config validation', () => { it('Should reject a negative value', () => { const loadBalancingConfig = { max_ejection_percent: -1, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -292,9 +304,9 @@ describe('Outlier detection config validation', () => { it('Should reject a value above 100', () => { const loadBalancingConfig = { success_rate_ejection: { - enforcement_percentage: 101 + enforcement_percentage: 101, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -303,9 +315,9 @@ describe('Outlier detection config validation', () => { it('Should reject a negative value', () => { const loadBalancingConfig = { success_rate_ejection: { - enforcement_percentage: -1 + enforcement_percentage: -1, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -316,9 +328,9 @@ describe('Outlier detection config validation', () => { it('Should reject a value above 100', () => { const loadBalancingConfig = { failure_percentage_ejection: { - threshold: 101 + threshold: 101, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -327,9 +339,9 @@ describe('Outlier detection config validation', () => { it('Should reject a negative value', () => { const loadBalancingConfig = { failure_percentage_ejection: { - threshold: -1 + threshold: -1, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -340,9 +352,9 @@ describe('Outlier detection config validation', () => { it('Should reject a value above 100', () => { const loadBalancingConfig = { failure_percentage_ejection: { - enforcement_percentage: 101 + enforcement_percentage: 101, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -351,9 +363,9 @@ describe('Outlier detection config validation', () => { it('Should reject a negative value', () => { const loadBalancingConfig = { failure_percentage_ejection: { - enforcement_percentage: -1 + enforcement_percentage: -1, }, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); @@ -377,32 +389,44 @@ describe('Outlier detection', () => { goodServer = new grpc.Server(); goodServer.addService(EchoService.service, goodService); for (let i = 0; i < GOOD_PORTS; i++) { - goodServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + goodServer.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + if (error) { + eachDone(error); + return; + } + goodPorts.push(port); + eachDone(); + } + ); + } + badServer = new grpc.Server(); + badServer.addService(EchoService.service, badService); + badServer.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { if (error) { eachDone(error); return; } - goodPorts.push(port); + badPort = port; eachDone(); - }); - } - badServer = new grpc.Server(); - badServer.addService(EchoService.service, badService); - badServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - if (error) { - eachDone(error); - return; } - badPort = port; - eachDone(); - }); + ); }); after(() => { goodServer.forceShutdown(); badServer.forceShutdown(); }); - function makeManyRequests(makeOneRequest: (callback: (error?: Error) => void) => void, total: number, callback: (error?: Error) => void) { + function makeManyRequests( + makeOneRequest: (callback: (error?: Error) => void) => void, + total: number, + callback: (error?: Error) => void + ) { if (total === 0) { callback(); return; @@ -417,7 +441,11 @@ describe('Outlier detection', () => { } it('Should allow normal operation with one server', done => { - const client = new EchoService(`localhost:${goodPorts[0]}`, grpc.credentials.createInsecure(), {'grpc.service_config': defaultOutlierDetectionServiceConfigString}); + const client = new EchoService( + `localhost:${goodPorts[0]}`, + grpc.credentials.createInsecure(), + { 'grpc.service_config': defaultOutlierDetectionServiceConfigString } + ); client.echo( { value: 'test value', value2: 3 }, (error: grpc.ServiceError, response: any) => { @@ -429,10 +457,19 @@ describe('Outlier detection', () => { }); describe('Success rate', () => { let makeCheckedRequest: (callback: () => void) => void; - let makeUncheckedRequest:(callback: (error?: Error) => void) => void; + let makeUncheckedRequest: (callback: (error?: Error) => void) => void; before(() => { - const target = 'ipv4:///' + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + `,127.0.0.1:${badPort}`; - const client = new EchoService(target, grpc.credentials.createInsecure(), {'grpc.service_config': successRateOutlierDetectionServiceConfigString}); + const target = + 'ipv4:///' + + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + + `,127.0.0.1:${badPort}`; + const client = new EchoService( + target, + grpc.credentials.createInsecure(), + { + 'grpc.service_config': successRateOutlierDetectionServiceConfigString, + } + ); makeUncheckedRequest = (callback: () => void) => { client.echo( { value: 'test value', value2: 3 }, @@ -460,7 +497,7 @@ describe('Outlier detection', () => { }, 1000); }); }); - it('Should uneject a server after the ejection period', function(done) { + it('Should uneject a server after the ejection period', function (done) { this.timeout(5000); makeManyRequests(makeUncheckedRequest, 50, () => { setTimeout(() => { @@ -477,15 +514,25 @@ describe('Outlier detection', () => { }, 3000); }); }, 1000); - }) + }); }); }); describe('Failure percentage', () => { let makeCheckedRequest: (callback: () => void) => void; - let makeUncheckedRequest:(callback: (error?: Error) => void) => void; + let makeUncheckedRequest: (callback: (error?: Error) => void) => void; before(() => { - const target = 'ipv4:///' + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + `,127.0.0.1:${badPort}`; - const client = new EchoService(target, grpc.credentials.createInsecure(), {'grpc.service_config': falurePercentageOutlierDetectionServiceConfigString}); + const target = + 'ipv4:///' + + goodPorts.map(port => `127.0.0.1:${port}`).join(',') + + `,127.0.0.1:${badPort}`; + const client = new EchoService( + target, + grpc.credentials.createInsecure(), + { + 'grpc.service_config': + falurePercentageOutlierDetectionServiceConfigString, + } + ); makeUncheckedRequest = (callback: () => void) => { client.echo( { value: 'test value', value2: 3 }, @@ -513,7 +560,7 @@ describe('Outlier detection', () => { }, 1000); }); }); - it('Should uneject a server after the ejection period', function(done) { + it('Should uneject a server after the ejection period', function (done) { this.timeout(5000); makeManyRequests(makeUncheckedRequest, 50, () => { setTimeout(() => { @@ -530,7 +577,7 @@ describe('Outlier detection', () => { }, 3000); }); }, 1000); - }) + }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-prototype-pollution.ts b/packages/grpc-js/test/test-prototype-pollution.ts index 6dc4b293c..0d4bdd68c 100644 --- a/packages/grpc-js/test/test-prototype-pollution.ts +++ b/packages/grpc-js/test/test-prototype-pollution.ts @@ -21,11 +21,11 @@ import { loadPackageDefinition } from '../src'; describe('loadPackageDefinition', () => { it('Should not allow prototype pollution', () => { - loadPackageDefinition({'__proto__.polluted': true} as any); - assert.notStrictEqual(({} as any).polluted, true); + loadPackageDefinition({ '__proto__.polluted': true } as any); + assert.notStrictEqual(({} as any).polluted, true); }); it('Should not allow prototype pollution #2', () => { - loadPackageDefinition({'constructor.prototype.polluted': true} as any); - assert.notStrictEqual(({} as any).polluted, true); + loadPackageDefinition({ 'constructor.prototype.polluted': true } as any); + assert.notStrictEqual(({} as any).polluted, true); }); }); diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index 1d458125b..98d74823b 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -24,7 +24,11 @@ import * as resolver_uds from '../src/resolver-uds'; import * as resolver_ip from '../src/resolver-ip'; import { ServiceConfig } from '../src/service-config'; import { StatusObject } from '../src/call-interface'; -import { SubchannelAddress, isTcpSubchannelAddress, subchannelAddressToString } from "../src/subchannel-address"; +import { + SubchannelAddress, + isTcpSubchannelAddress, + subchannelAddressToString, +} from '../src/subchannel-address'; import { parseUri, GrpcUri } from '../src/uri-parser'; describe('Name Resolver', () => { @@ -33,11 +37,13 @@ describe('Name Resolver', () => { resolver_uds.setup(); resolver_ip.setup(); }); - describe('DNS Names', function() { + describe('DNS Names', function () { // For some reason DNS queries sometimes take a long time on Windows this.timeout(4000); it('Should resolve localhost properly', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('localhost:50051')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('localhost:50051')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -72,7 +78,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('Should default to port 443', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('localhost')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('localhost')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -161,7 +169,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('Should correctly represent a bracketed ipv6 address', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('[::1]:50051')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('[::1]:50051')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -188,7 +198,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('Should resolve a public address', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('example.com')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('example.com')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -210,7 +222,9 @@ describe('Name Resolver', () => { // Created DNS TXT record using TXT sample from https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md // "grpc_config=[{\"serviceConfig\":{\"loadBalancingPolicy\":\"round_robin\",\"methodConfig\":[{\"name\":[{\"service\":\"MyService\",\"method\":\"Foo\"}],\"waitForReady\":true}]}}]" it.skip('Should resolve a name with TXT service config', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('grpctest.kleinsch.com')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('grpctest.kleinsch.com')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -232,39 +246,36 @@ describe('Name Resolver', () => { const resolver = resolverManager.createResolver(target, listener, {}); resolver.updateResolution(); }); - it.skip( - 'Should not resolve TXT service config if we disabled service config', - (done) => { - const target = resolverManager.mapUriDefaultScheme( - parseUri('grpctest.kleinsch.com')! - )!; - let count = 0; - const listener: resolverManager.ResolverListener = { - onSuccessfulResolution: ( - addressList: SubchannelAddress[], - serviceConfig: ServiceConfig | null, - serviceConfigError: StatusObject | null - ) => { - assert( - serviceConfig === null, - 'Should not have found service config' - ); - count++; - }, - onError: (error: StatusObject) => { - done(new Error(`Failed with status ${error.details}`)); - }, - }; - const resolver = resolverManager.createResolver(target, listener, { - 'grpc.service_config_disable_resolution': 1, - }); - resolver.updateResolution(); - setTimeout(() => { - assert(count === 1, 'Should have only resolved once'); - done(); - }, 2_000); - } - ); + it.skip('Should not resolve TXT service config if we disabled service config', done => { + const target = resolverManager.mapUriDefaultScheme( + parseUri('grpctest.kleinsch.com')! + )!; + let count = 0; + const listener: resolverManager.ResolverListener = { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert( + serviceConfig === null, + 'Should not have found service config' + ); + count++; + }, + onError: (error: StatusObject) => { + done(new Error(`Failed with status ${error.details}`)); + }, + }; + const resolver = resolverManager.createResolver(target, listener, { + 'grpc.service_config_disable_resolution': 1, + }); + resolver.updateResolution(); + setTimeout(() => { + assert(count === 1, 'Should have only resolved once'); + done(); + }, 2_000); + }); /* The DNS entry for loopback4.unittest.grpc.io only has a single A record * with the address 127.0.0.1, but the Mac DNS resolver appears to use * NAT64 to create an IPv6 address in that case, so it instead returns @@ -274,7 +285,9 @@ describe('Name Resolver', () => { * and the test 'Should resolve gRPC interop servers' tests the same thing. */ it.skip('Should resolve a name with multiple dots', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('loopback4.unittest.grpc.io')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('loopback4.unittest.grpc.io')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -289,7 +302,10 @@ describe('Name Resolver', () => { isTcpSubchannelAddress(addr) && addr.host === '127.0.0.1' && addr.port === 443 - ), `None of [${addressList.map(addr => subchannelAddressToString(addr))}] matched '127.0.0.1:443'` + ), + `None of [${addressList.map(addr => + subchannelAddressToString(addr) + )}] matched '127.0.0.1:443'` ); done(); }, @@ -303,7 +319,9 @@ describe('Name Resolver', () => { /* TODO(murgatroid99): re-enable this test, once we can get the IPv6 result * consistently */ it.skip('Should resolve a DNS name to an IPv6 address', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('loopback6.unittest.grpc.io')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('loopback6.unittest.grpc.io')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -333,7 +351,9 @@ describe('Name Resolver', () => { * IPv6 address on Mac. There is no result that we can consistently test * for here. */ it.skip('Should resolve a DNS name to IPv4 and IPv6 addresses', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('loopback46.unittest.grpc.io')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('loopback46.unittest.grpc.io')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -348,7 +368,10 @@ describe('Name Resolver', () => { isTcpSubchannelAddress(addr) && addr.host === '127.0.0.1' && addr.port === 443 - ), `None of [${addressList.map(addr => subchannelAddressToString(addr))}] matched '127.0.0.1:443'` + ), + `None of [${addressList.map(addr => + subchannelAddressToString(addr) + )}] matched '127.0.0.1:443'` ); /* TODO(murgatroid99): check for IPv6 result, once we can get that * consistently */ @@ -364,7 +387,9 @@ describe('Name Resolver', () => { it('Should resolve a name with a hyphen', done => { /* TODO(murgatroid99): Find or create a better domain name to test this with. * This is just the first one I found with a hyphen. */ - const target = resolverManager.mapUriDefaultScheme(parseUri('network-tools.com')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('network-tools.com')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -389,8 +414,12 @@ describe('Name Resolver', () => { * unless there is another test for the same issue. */ it('Should resolve gRPC interop servers', done => { let completeCount = 0; - const target1 = resolverManager.mapUriDefaultScheme(parseUri('grpc-test.sandbox.googleapis.com')!)!; - const target2 = resolverManager.mapUriDefaultScheme(parseUri('grpc-test4.sandbox.googleapis.com')!)!; + const target1 = resolverManager.mapUriDefaultScheme( + parseUri('grpc-test.sandbox.googleapis.com')! + )!; + const target2 = resolverManager.mapUriDefaultScheme( + parseUri('grpc-test4.sandbox.googleapis.com')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -415,39 +444,45 @@ describe('Name Resolver', () => { resolver2.updateResolution(); }); it('should not keep repeating successful resolutions', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('localhost')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('localhost')! + )!; let resultCount = 0; - const resolver = resolverManager.createResolver(target, { - onSuccessfulResolution: ( - addressList: SubchannelAddress[], - serviceConfig: ServiceConfig | null, - serviceConfigError: StatusObject | null - ) => { - assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 443 - ) - ); - assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 443 - ) - ); - resultCount += 1; - if (resultCount === 1) { - process.nextTick(() => resolver.updateResolution()); - } - }, - onError: (error: StatusObject) => { - assert.ifError(error); + const resolver = resolverManager.createResolver( + target, + { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '127.0.0.1' && + addr.port === 443 + ) + ); + assert( + addressList.some( + addr => + isTcpSubchannelAddress(addr) && + addr.host === '::1' && + addr.port === 443 + ) + ); + resultCount += 1; + if (resultCount === 1) { + process.nextTick(() => resolver.updateResolution()); + } + }, + onError: (error: StatusObject) => { + assert.ifError(error); + }, }, - }, {'grpc.dns_min_time_between_resolutions_ms': 2000}); + { 'grpc.dns_min_time_between_resolutions_ms': 2000 } + ); resolver.updateResolution(); setTimeout(() => { assert.strictEqual(resultCount, 2, `resultCount ${resultCount} !== 2`); @@ -455,23 +490,29 @@ describe('Name Resolver', () => { }, 10_000); }).timeout(15_000); it('should not keep repeating failed resolutions', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('host.invalid')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('host.invalid')! + )!; let resultCount = 0; - const resolver = resolverManager.createResolver(target, { - onSuccessfulResolution: ( - addressList: SubchannelAddress[], - serviceConfig: ServiceConfig | null, - serviceConfigError: StatusObject | null - ) => { - assert.fail('Resolution succeeded unexpectedly'); - }, - onError: (error: StatusObject) => { - resultCount += 1; - if (resultCount === 1) { - process.nextTick(() => resolver.updateResolution()); - } + const resolver = resolverManager.createResolver( + target, + { + onSuccessfulResolution: ( + addressList: SubchannelAddress[], + serviceConfig: ServiceConfig | null, + serviceConfigError: StatusObject | null + ) => { + assert.fail('Resolution succeeded unexpectedly'); + }, + onError: (error: StatusObject) => { + resultCount += 1; + if (resultCount === 1) { + process.nextTick(() => resolver.updateResolution()); + } + }, }, - }, {}); + {} + ); resolver.updateResolution(); setTimeout(() => { assert.strictEqual(resultCount, 2, `resultCount ${resultCount} !== 2`); @@ -481,7 +522,9 @@ describe('Name Resolver', () => { }); describe('UDS Names', () => { it('Should handle a relative Unix Domain Socket name', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('unix:socket')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('unix:socket')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -505,7 +548,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('Should handle an absolute Unix Domain Socket name', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('unix:///tmp/socket')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('unix:///tmp/socket')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -532,7 +577,9 @@ describe('Name Resolver', () => { }); describe('IP Addresses', () => { it('should handle one IPv4 address with no port', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('ipv4:127.0.0.1')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('ipv4:127.0.0.1')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -559,7 +606,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('should handle one IPv4 address with a port', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('ipv4:127.0.0.1:50051')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('ipv4:127.0.0.1:50051')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -586,7 +635,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('should handle multiple IPv4 addresses with different ports', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('ipv4:127.0.0.1:50051,127.0.0.1:50052')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('ipv4:127.0.0.1:50051,127.0.0.1:50052')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -621,7 +672,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('should handle one IPv6 address with no port', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('ipv6:::1')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('ipv6:::1')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -648,7 +701,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('should handle one IPv6 address with a port', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('ipv6:[::1]:50051')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('ipv6:[::1]:50051')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -675,7 +730,9 @@ describe('Name Resolver', () => { resolver.updateResolution(); }); it('should handle multiple IPv6 addresses with different ports', done => { - const target = resolverManager.mapUriDefaultScheme(parseUri('ipv6:[::1]:50051,[::1]:50052')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('ipv6:[::1]:50051,[::1]:50052')! + )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( addressList: SubchannelAddress[], @@ -716,8 +773,7 @@ describe('Name Resolver', () => { return []; } - destroy() { - } + destroy() {} static getDefaultAuthority(target: GrpcUri): string { return 'other'; @@ -726,7 +782,9 @@ describe('Name Resolver', () => { it('Should return the correct authority if a different resolver has been registered', () => { resolverManager.registerResolver('other', OtherResolver); - const target = resolverManager.mapUriDefaultScheme(parseUri('other:name')!)!; + const target = resolverManager.mapUriDefaultScheme( + parseUri('other:name')! + )!; console.log(target); const authority = resolverManager.getDefaultAuthority(target); diff --git a/packages/grpc-js/test/test-retry-config.ts b/packages/grpc-js/test/test-retry-config.ts index e27f236e2..77952e668 100644 --- a/packages/grpc-js/test/test-retry-config.ts +++ b/packages/grpc-js/test/test-retry-config.ts @@ -15,22 +15,24 @@ * */ -import assert = require("assert"); -import { validateServiceConfig } from "../src/service-config"; +import assert = require('assert'); +import { validateServiceConfig } from '../src/service-config'; function createRetryServiceConfig(retryConfig: object): object { return { loadBalancingConfig: [], methodConfig: [ { - name: [{ - service: 'A', - method: 'B' - }], + name: [ + { + service: 'A', + method: 'B', + }, + ], - retryPolicy: retryConfig - } - ] + retryPolicy: retryConfig, + }, + ], }; } @@ -39,14 +41,16 @@ function createHedgingServiceConfig(hedgingConfig: object): object { loadBalancingConfig: [], methodConfig: [ { - name: [{ - service: 'A', - method: 'B' - }], + name: [ + { + service: 'A', + method: 'B', + }, + ], - hedgingPolicy: hedgingConfig - } - ] + hedgingPolicy: hedgingConfig, + }, + ], }; } @@ -54,7 +58,7 @@ function createThrottlingServiceConfig(retryThrottling: object): object { return { loadBalancingConfig: [], methodConfig: [], - retryThrottling: retryThrottling + retryThrottling: retryThrottling, }; } @@ -69,7 +73,7 @@ const validRetryConfig = { initialBackoff: '1s', maxBackoff: '1s', backoffMultiplier: 1, - retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'], }; const RETRY_TEST_CASES: TestCase[] = [ @@ -79,14 +83,14 @@ const RETRY_TEST_CASES: TestCase[] = [ initialBackoff: '1s', maxBackoff: '1s', backoffMultiplier: 1, - retryableStatusCodes: [14] + retryableStatusCodes: [14], }, - error: /retry policy: maxAttempts must be an integer at least 2/ + error: /retry policy: maxAttempts must be an integer at least 2/, }, { description: 'a low maxAttempts', - config: {...validRetryConfig, maxAttempts: 1}, - error: /retry policy: maxAttempts must be an integer at least 2/ + config: { ...validRetryConfig, maxAttempts: 1 }, + error: /retry policy: maxAttempts must be an integer at least 2/, }, { description: 'omitted initialBackoff', @@ -94,19 +98,22 @@ const RETRY_TEST_CASES: TestCase[] = [ maxAttempts: 2, maxBackoff: '1s', backoffMultiplier: 1, - retryableStatusCodes: [14] + retryableStatusCodes: [14], }, - error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + error: + /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/, }, { description: 'a non-numeric initialBackoff', - config: {...validRetryConfig, initialBackoff: 'abcs'}, - error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + config: { ...validRetryConfig, initialBackoff: 'abcs' }, + error: + /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/, }, { description: 'an initialBackoff without an s', - config: {...validRetryConfig, initialBackoff: '123'}, - error: /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/ + config: { ...validRetryConfig, initialBackoff: '123' }, + error: + /retry policy: initialBackoff must be a string consisting of a positive integer followed by s/, }, { description: 'omitted maxBackoff', @@ -114,19 +121,22 @@ const RETRY_TEST_CASES: TestCase[] = [ maxAttempts: 2, initialBackoff: '1s', backoffMultiplier: 1, - retryableStatusCodes: [14] + retryableStatusCodes: [14], }, - error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + error: + /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/, }, { description: 'a non-numeric maxBackoff', - config: {...validRetryConfig, maxBackoff: 'abcs'}, - error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + config: { ...validRetryConfig, maxBackoff: 'abcs' }, + error: + /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/, }, { description: 'an maxBackoff without an s', - config: {...validRetryConfig, maxBackoff: '123'}, - error: /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/ + config: { ...validRetryConfig, maxBackoff: '123' }, + error: + /retry policy: maxBackoff must be a string consisting of a positive integer followed by s/, }, { description: 'omitted backoffMultiplier', @@ -134,14 +144,14 @@ const RETRY_TEST_CASES: TestCase[] = [ maxAttempts: 2, initialBackoff: '1s', maxBackoff: '1s', - retryableStatusCodes: [14] + retryableStatusCodes: [14], }, - error: /retry policy: backoffMultiplier must be a number greater than 0/ + error: /retry policy: backoffMultiplier must be a number greater than 0/, }, { description: 'a negative backoffMultiplier', - config: {...validRetryConfig, backoffMultiplier: -1}, - error: /retry policy: backoffMultiplier must be a number greater than 0/ + config: { ...validRetryConfig, backoffMultiplier: -1 }, + error: /retry policy: backoffMultiplier must be a number greater than 0/, }, { description: 'omitted retryableStatusCodes', @@ -149,95 +159,97 @@ const RETRY_TEST_CASES: TestCase[] = [ maxAttempts: 2, initialBackoff: '1s', maxBackoff: '1s', - backoffMultiplier: 1 + backoffMultiplier: 1, }, - error: /retry policy: retryableStatusCodes is required/ + error: /retry policy: retryableStatusCodes is required/, }, { description: 'empty retryableStatusCodes', - config: {...validRetryConfig, retryableStatusCodes: []}, - error: /retry policy: retryableStatusCodes must be non-empty/ + config: { ...validRetryConfig, retryableStatusCodes: [] }, + error: /retry policy: retryableStatusCodes must be non-empty/, }, { description: 'unknown status code name', - config: {...validRetryConfig, retryableStatusCodes: ['abcd']}, - error: /retry policy: retryableStatusCodes value not a status code name/ + config: { ...validRetryConfig, retryableStatusCodes: ['abcd'] }, + error: /retry policy: retryableStatusCodes value not a status code name/, }, { description: 'out of range status code number', - config: {...validRetryConfig, retryableStatusCodes: [12345]}, - error: /retry policy: retryableStatusCodes value not in status code range/ - } + config: { ...validRetryConfig, retryableStatusCodes: [12345] }, + error: /retry policy: retryableStatusCodes value not in status code range/, + }, ]; const validHedgingConfig = { - maxAttempts: 2 + maxAttempts: 2, }; const HEDGING_TEST_CASES: TestCase[] = [ { description: 'omitted maxAttempts', config: {}, - error: /hedging policy: maxAttempts must be an integer at least 2/ + error: /hedging policy: maxAttempts must be an integer at least 2/, }, { description: 'a low maxAttempts', - config: {...validHedgingConfig, maxAttempts: 1}, - error: /hedging policy: maxAttempts must be an integer at least 2/ + config: { ...validHedgingConfig, maxAttempts: 1 }, + error: /hedging policy: maxAttempts must be an integer at least 2/, }, { description: 'a non-numeric hedgingDelay', - config: {...validHedgingConfig, hedgingDelay: 'abcs'}, - error: /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/ + config: { ...validHedgingConfig, hedgingDelay: 'abcs' }, + error: + /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/, }, { description: 'a hedgingDelay without an s', - config: {...validHedgingConfig, hedgingDelay: '123'}, - error: /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/ + config: { ...validHedgingConfig, hedgingDelay: '123' }, + error: + /hedging policy: hedgingDelay must be a string consisting of a positive integer followed by s/, }, { description: 'unknown status code name', - config: {...validHedgingConfig, nonFatalStatusCodes: ['abcd']}, - error: /hedging policy: nonFatalStatusCodes value not a status code name/ + config: { ...validHedgingConfig, nonFatalStatusCodes: ['abcd'] }, + error: /hedging policy: nonFatalStatusCodes value not a status code name/, }, { description: 'out of range status code number', - config: {...validHedgingConfig, nonFatalStatusCodes: [12345]}, - error: /hedging policy: nonFatalStatusCodes value not in status code range/ - } + config: { ...validHedgingConfig, nonFatalStatusCodes: [12345] }, + error: /hedging policy: nonFatalStatusCodes value not in status code range/, + }, ]; const validThrottlingConfig = { maxTokens: 100, - tokenRatio: 0.1 + tokenRatio: 0.1, }; const THROTTLING_TEST_CASES: TestCase[] = [ { description: 'omitted maxTokens', - config: {tokenRatio: 0.1}, - error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + config: { tokenRatio: 0.1 }, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/, }, { description: 'a large maxTokens', - config: {...validThrottlingConfig, maxTokens: 1001}, - error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + config: { ...validThrottlingConfig, maxTokens: 1001 }, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/, }, { description: 'zero maxTokens', - config: {...validThrottlingConfig, maxTokens: 0}, - error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/ + config: { ...validThrottlingConfig, maxTokens: 0 }, + error: /retryThrottling: maxTokens must be a number in \(0, 1000\]/, }, { description: 'omitted tokenRatio', - config: {maxTokens: 100}, - error: /retryThrottling: tokenRatio must be a number greater than 0/ + config: { maxTokens: 100 }, + error: /retryThrottling: tokenRatio must be a number greater than 0/, }, { description: 'zero tokenRatio', - config: {...validThrottlingConfig, tokenRatio: 0}, - error: /retryThrottling: tokenRatio must be a number greater than 0/ - } + config: { ...validThrottlingConfig, tokenRatio: 0 }, + error: /retryThrottling: tokenRatio must be a number greater than 0/, + }, ]; describe('Retry configs', () => { @@ -261,10 +273,20 @@ describe('Retry configs', () => { validateServiceConfig(createHedgingServiceConfig(validHedgingConfig)); }); assert.doesNotThrow(() => { - validateServiceConfig(createHedgingServiceConfig({...validHedgingConfig, hedgingDelay: '1s'})); + validateServiceConfig( + createHedgingServiceConfig({ + ...validHedgingConfig, + hedgingDelay: '1s', + }) + ); }); assert.doesNotThrow(() => { - validateServiceConfig(createHedgingServiceConfig({...validHedgingConfig, nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED']})); + validateServiceConfig( + createHedgingServiceConfig({ + ...validHedgingConfig, + nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'], + }) + ); }); }); for (const testCase of HEDGING_TEST_CASES) { @@ -278,7 +300,9 @@ describe('Retry configs', () => { describe('Throttling', () => { it('Should accept a valid config', () => { assert.doesNotThrow(() => { - validateServiceConfig(createThrottlingServiceConfig(validThrottlingConfig)); + validateServiceConfig( + createThrottlingServiceConfig(validThrottlingConfig) + ); }); }); for (const testCase of THROTTLING_TEST_CASES) { @@ -289,4 +313,4 @@ describe('Retry configs', () => { }); } }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-retry.ts b/packages/grpc-js/test/test-retry.ts index 66c0f7941..e66e96eb0 100644 --- a/packages/grpc-js/test/test-retry.ts +++ b/packages/grpc-js/test/test-retry.ts @@ -25,37 +25,50 @@ const EchoService = loadProtoFile(protoFile) .EchoService as grpc.ServiceClientConstructor; const serviceImpl = { - echo: (call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { const succeedOnRetryAttempt = call.metadata.get('succeed-on-retry-attempt'); const previousAttempts = call.metadata.get('grpc-previous-rpc-attempts'); - if (succeedOnRetryAttempt.length === 0 || (previousAttempts.length > 0 && previousAttempts[0] === succeedOnRetryAttempt[0])) { + if ( + succeedOnRetryAttempt.length === 0 || + (previousAttempts.length > 0 && + previousAttempts[0] === succeedOnRetryAttempt[0]) + ) { callback(null, call.request); } else { const statusCode = call.metadata.get('respond-with-status'); - const code = statusCode[0] ? Number.parseInt(statusCode[0] as string) : grpc.status.UNKNOWN; + const code = statusCode[0] + ? Number.parseInt(statusCode[0] as string) + : grpc.status.UNKNOWN; callback({ code: code, - details: `Failed on retry ${previousAttempts[0] ?? 0}` + details: `Failed on retry ${previousAttempts[0] ?? 0}`, }); } - } -} + }, +}; describe('Retries', () => { let server: grpc.Server; let port: number; - before((done) => { + before(done => { server = new grpc.Server(); server.addService(EchoService.service, serviceImpl); - server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, portNumber) => { - if (error) { - done(error); - return; + server.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, portNumber) => { + if (error) { + done(error); + return; + } + port = portNumber; + server.start(); + done(); } - port = portNumber; - server.start(); - done(); - }); + ); }); after(() => { @@ -65,14 +78,18 @@ describe('Retries', () => { describe('Client with retries disabled', () => { let client: InstanceType; before(() => { - client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.enable_retries': 0}); + client = new EchoService( + `localhost:${port}`, + grpc.credentials.createInsecure(), + { 'grpc.enable_retries': 0 } + ); }); - after(() =>{ + after(() => { client.close(); }); - it('Should be able to make a basic request', (done) => { + it('Should be able to make a basic request', done => { client.echo( { value: 'test value', value2: 3 }, (error: grpc.ServiceError, response: any) => { @@ -83,7 +100,7 @@ describe('Retries', () => { ); }); - it('Should fail if the server fails the first request', (done) =>{ + it('Should fail if the server fails the first request', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '1'); client.echo( @@ -101,14 +118,17 @@ describe('Retries', () => { describe('Client with retries enabled but not configured', () => { let client: InstanceType; before(() => { - client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure()); + client = new EchoService( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); }); - after(() =>{ + after(() => { client.close(); }); - it('Should be able to make a basic request', (done) => { + it('Should be able to make a basic request', done => { client.echo( { value: 'test value', value2: 3 }, (error: grpc.ServiceError, response: any) => { @@ -119,7 +139,7 @@ describe('Retries', () => { ); }); - it('Should fail if the server fails the first request', (done) =>{ + it('Should fail if the server fails the first request', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '1'); client.echo( @@ -141,27 +161,33 @@ describe('Retries', () => { loadBalancingConfig: [], methodConfig: [ { - name: [{ - service: 'EchoService' - }], + name: [ + { + service: 'EchoService', + }, + ], retryPolicy: { maxAttempts: 3, initialBackoff: '0.1s', maxBackoff: '10s', backoffMultiplier: 1.2, - retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] - } - } - ] - } - client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'], + }, + }, + ], + }; + client = new EchoService( + `localhost:${port}`, + grpc.credentials.createInsecure(), + { 'grpc.service_config': JSON.stringify(serviceConfig) } + ); }); - after(() =>{ + after(() => { client.close(); }); - it('Should be able to make a basic request', (done) => { + it('Should be able to make a basic request', done => { client.echo( { value: 'test value', value2: 3 }, (error: grpc.ServiceError, response: any) => { @@ -172,7 +198,7 @@ describe('Retries', () => { ); }); - it('Should succeed with few required attempts', (done) => { + it('Should succeed with few required attempts', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '2'); metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); @@ -187,7 +213,7 @@ describe('Retries', () => { ); }); - it('Should fail with many required attempts', (done) => { + it('Should fail with many required attempts', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '4'); metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); @@ -202,7 +228,7 @@ describe('Retries', () => { ); }); - it('Should fail with a fatal status code', (done) => { + it('Should fail with a fatal status code', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '2'); metadata.set('respond-with-status', `${grpc.status.NOT_FOUND}`); @@ -217,25 +243,31 @@ describe('Retries', () => { ); }); - it('Should not be able to make more than 5 attempts', (done) => { + it('Should not be able to make more than 5 attempts', done => { const serviceConfig = { loadBalancingConfig: [], methodConfig: [ { - name: [{ - service: 'EchoService' - }], + name: [ + { + service: 'EchoService', + }, + ], retryPolicy: { maxAttempts: 10, initialBackoff: '0.1s', maxBackoff: '10s', backoffMultiplier: 1.2, - retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'] - } - } - ] - } - const client2 = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + retryableStatusCodes: [14, 'RESOURCE_EXHAUSTED'], + }, + }, + ], + }; + const client2 = new EchoService( + `localhost:${port}`, + grpc.credentials.createInsecure(), + { 'grpc.service_config': JSON.stringify(serviceConfig) } + ); const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '6'); metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); @@ -248,7 +280,7 @@ describe('Retries', () => { done(); } ); - }) + }); }); describe('Client with hedging configured', () => { @@ -258,24 +290,30 @@ describe('Retries', () => { loadBalancingConfig: [], methodConfig: [ { - name: [{ - service: 'EchoService' - }], + name: [ + { + service: 'EchoService', + }, + ], hedgingPolicy: { maxAttempts: 3, - nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'] - } - } - ] - } - client = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'], + }, + }, + ], + }; + client = new EchoService( + `localhost:${port}`, + grpc.credentials.createInsecure(), + { 'grpc.service_config': JSON.stringify(serviceConfig) } + ); }); - after(() =>{ + after(() => { client.close(); }); - it('Should be able to make a basic request', (done) => { + it('Should be able to make a basic request', done => { client.echo( { value: 'test value', value2: 3 }, (error: grpc.ServiceError, response: any) => { @@ -286,7 +324,7 @@ describe('Retries', () => { ); }); - it('Should succeed with few required attempts', (done) => { + it('Should succeed with few required attempts', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '2'); metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); @@ -301,7 +339,7 @@ describe('Retries', () => { ); }); - it('Should fail with many required attempts', (done) => { + it('Should fail with many required attempts', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '4'); metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); @@ -316,7 +354,7 @@ describe('Retries', () => { ); }); - it('Should fail with a fatal status code', (done) => { + it('Should fail with a fatal status code', done => { const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '2'); metadata.set('respond-with-status', `${grpc.status.NOT_FOUND}`); @@ -331,22 +369,28 @@ describe('Retries', () => { ); }); - it('Should not be able to make more than 5 attempts', (done) => { + it('Should not be able to make more than 5 attempts', done => { const serviceConfig = { loadBalancingConfig: [], methodConfig: [ { - name: [{ - service: 'EchoService' - }], + name: [ + { + service: 'EchoService', + }, + ], hedgingPolicy: { maxAttempts: 10, - nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'] - } - } - ] - } - const client2 = new EchoService(`localhost:${port}`, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(serviceConfig)}); + nonFatalStatusCodes: [14, 'RESOURCE_EXHAUSTED'], + }, + }, + ], + }; + const client2 = new EchoService( + `localhost:${port}`, + grpc.credentials.createInsecure(), + { 'grpc.service_config': JSON.stringify(serviceConfig) } + ); const metadata = new grpc.Metadata(); metadata.set('succeed-on-retry-attempt', '6'); metadata.set('respond-with-status', `${grpc.status.RESOURCE_EXHAUSTED}`); @@ -359,6 +403,6 @@ describe('Retries', () => { done(); } ); - }) + }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-server-deadlines.ts b/packages/grpc-js/test/test-server-deadlines.ts index c1152309d..2a966e664 100644 --- a/packages/grpc-js/test/test-server-deadlines.ts +++ b/packages/grpc-js/test/test-server-deadlines.ts @@ -42,7 +42,8 @@ describe('Server deadlines', () => { before(done => { const protoFile = path.join(__dirname, 'fixtures', 'test_service.proto'); const testServiceDef = loadProtoFile(protoFile); - const testServiceClient = testServiceDef.TestService as ServiceClientConstructor; + const testServiceClient = + testServiceDef.TestService as ServiceClientConstructor; server = new Server(); server.addService(testServiceClient.service, { @@ -126,7 +127,8 @@ describe('Cancellation', () => { before(done => { const protoFile = path.join(__dirname, 'fixtures', 'test_service.proto'); const testServiceDef = loadProtoFile(protoFile); - const testServiceClient = testServiceDef.TestService as ServiceClientConstructor; + const testServiceClient = + testServiceDef.TestService as ServiceClientConstructor; server = new Server(); server.addService(testServiceClient.service, { diff --git a/packages/grpc-js/test/test-server-errors.ts b/packages/grpc-js/test/test-server-errors.ts index 91b7c196c..24ccfeef3 100644 --- a/packages/grpc-js/test/test-server-errors.ts +++ b/packages/grpc-js/test/test-server-errors.ts @@ -36,7 +36,8 @@ import { loadProtoFile } from './common'; const protoFile = join(__dirname, 'fixtures', 'test_service.proto'); const testServiceDef = loadProtoFile(protoFile); -const testServiceClient = testServiceDef.TestService as ServiceClientConstructor; +const testServiceClient = + testServiceDef.TestService as ServiceClientConstructor; const clientInsecureCreds = grpc.credentials.createInsecure(); const serverInsecureCreds = grpc.ServerCredentials.createInsecure(); @@ -723,7 +724,7 @@ describe('Other conditions', () => { }); describe('should handle server stream errors correctly', () => { - it('should emit data for all messages before error', (done) => { + it('should emit data for all messages before error', done => { const expectedDataCount = 2; const call = client.serverStream({ errorAfter: expectedDataCount }); diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index c67ebc4d6..7a0fb412c 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -27,23 +27,35 @@ import * as grpc from '../src'; import { Server, ServerCredentials } from '../src'; import { ServiceError } from '../src/call'; import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; -import { sendUnaryData, ServerUnaryCall, ServerDuplexStream } from '../src/server-call'; +import { + sendUnaryData, + ServerUnaryCall, + ServerDuplexStream, +} from '../src/server-call'; import { assert2, loadProtoFile } from './common'; -import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; +import { + TestServiceClient, + TestServiceHandlers, +} from './generated/TestService'; import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; import { Request__Output } from './generated/Request'; import { CompressionAlgorithms } from '../src/compression-algorithms'; -const loadedTestServiceProto = protoLoader.loadSync(path.join(__dirname, 'fixtures/test_service.proto'), { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true -}); +const loadedTestServiceProto = protoLoader.loadSync( + path.join(__dirname, 'fixtures/test_service.proto'), + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + } +); -const testServiceGrpcObject = grpc.loadPackageDefinition(loadedTestServiceProto) as unknown as TestServiceGrpcType; +const testServiceGrpcObject = grpc.loadPackageDefinition( + loadedTestServiceProto +) as unknown as TestServiceGrpcType; const ca = fs.readFileSync(path.join(__dirname, 'fixtures', 'ca.pem')); const key = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.key')); @@ -134,7 +146,11 @@ describe('Server', () => { }, /creds must be a ServerCredentials object/); assert.throws(() => { - server.bindAsync('localhost:0', grpc.credentials.createInsecure() as any, noop); + server.bindAsync( + 'localhost:0', + grpc.credentials.createInsecure() as any, + noop + ); }, /creds must be a ServerCredentials object/); assert.throws(() => { @@ -286,7 +302,7 @@ describe('Server', () => { } }; - methodsToVerify.forEach((method) => { + methodsToVerify.forEach(method => { const call = client[method]({}, assertFailsWithUnimplementedError); // for unary call.on('error', assertFailsWithUnimplementedError); // for streamed }); @@ -294,14 +310,12 @@ describe('Server', () => { it('fails for non-object service definition argument', () => { assert.throws(() => { - server.removeService('upsie' as any) - }, /removeService.*requires object as argument/ - ); + server.removeService('upsie' as any); + }, /removeService.*requires object as argument/); }); }); describe('unregister', () => { - let server: Server; let client: ServiceClient; @@ -313,7 +327,7 @@ describe('Server', () => { server = new Server(); server.addService(mathServiceAttrs, { div(call: ServerUnaryCall, callback: sendUnaryData) { - callback(null, {quotient: '42'}); + callback(null, { quotient: '42' }); }, }); server.bindAsync( @@ -338,7 +352,11 @@ describe('Server', () => { it('removes handler by name and returns true', done => { const name = mathServiceAttrs['Div'].path; - assert.strictEqual(server.unregister(name), true, 'Server#unregister should return true on success'); + assert.strictEqual( + server.unregister(name), + true, + 'Server#unregister should return true on success' + ); client.div( { divisor: 4, dividend: 3 }, @@ -351,7 +369,11 @@ describe('Server', () => { }); it('returns false for unknown handler', () => { - assert.strictEqual(server.unregister('noOneHere'), false, 'Server#unregister should return false on failure'); + assert.strictEqual( + server.unregister('noOneHere'), + false, + 'Server#unregister should return false on failure' + ); }); }); @@ -473,11 +495,10 @@ describe('Echo service', () => { call.on('end', () => { call.end(); }); - } + }, }; before(done => { - server = new Server(); server.addService(echoService.service, serviceImplementation); @@ -517,36 +538,46 @@ describe('Echo service', () => { it.skip('should continue a stream after server shutdown', done => { const server2 = new Server(); server2.addService(echoService.service, serviceImplementation); - server2.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { - if (err) { - done(err); - return; - } - const client2 = new echoService(`localhost:${port}`, grpc.credentials.createInsecure()); - server2.start(); - const stream = client2.echoBidiStream(); - const totalMessages = 5; - let messagesSent = 0; - stream.write({ value: 'test value', value2: messagesSent}); - messagesSent += 1; - stream.on('data', () => { - if (messagesSent === 1) { - server2.tryShutdown(assert2.mustCall(() => {})); - } - if (messagesSent >= totalMessages) { - stream.end(); - } else { - stream.write({ value: 'test value', value2: messagesSent}); - messagesSent += 1; + server2.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + if (err) { + done(err); + return; } - }); - stream.on('status', assert2.mustCall((status: grpc.StatusObject) => { - assert.strictEqual(status.code, grpc.status.OK); - assert.strictEqual(messagesSent, totalMessages); - })); - stream.on('error', () => {}); - assert2.afterMustCallsSatisfied(done); - }); + const client2 = new echoService( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server2.start(); + const stream = client2.echoBidiStream(); + const totalMessages = 5; + let messagesSent = 0; + stream.write({ value: 'test value', value2: messagesSent }); + messagesSent += 1; + stream.on('data', () => { + if (messagesSent === 1) { + server2.tryShutdown(assert2.mustCall(() => {})); + } + if (messagesSent >= totalMessages) { + stream.end(); + } else { + stream.write({ value: 'test value', value2: messagesSent }); + messagesSent += 1; + } + }); + stream.on( + 'status', + assert2.mustCall((status: grpc.StatusObject) => { + assert.strictEqual(status.code, grpc.status.OK); + assert.strictEqual(messagesSent, totalMessages); + }) + ); + stream.on('error', () => {}); + assert2.afterMustCallsSatisfied(done); + } + ); }); }); @@ -703,7 +734,7 @@ describe('Compressed requests', () => { call.on('end', () => { call.end(); }); - } + }, }; describe('Test service client and server with deflate', () => { @@ -728,7 +759,8 @@ describe('Compressed requests', () => { `localhost:${assignedPort}`, grpc.credentials.createInsecure(), { - 'grpc.default_compression_algorithm': CompressionAlgorithms.deflate + 'grpc.default_compression_algorithm': + CompressionAlgorithms.deflate, } ); done(); @@ -772,7 +804,7 @@ describe('Compressed requests', () => { timesResponded += 1; }); - serverStream.on('error', (err) => { + serverStream.on('error', err => { assert.ifError(err); done(); }); @@ -792,7 +824,7 @@ describe('Compressed requests', () => { timesResponded += 1; }); - bidiStream.on('error', (err) => { + bidiStream.on('error', err => { assert.ifError(err); done(); }); @@ -809,8 +841,8 @@ describe('Compressed requests', () => { bidiStream.write({ message: 'baz' }, () => { timesRequested += 1; setTimeout(() => bidiStream.end(), 10); - }) - }) + }); + }); }); }); @@ -819,7 +851,7 @@ describe('Compressed requests', () => { `localhost:${assignedPort}`, grpc.credentials.createInsecure(), { - 'grpc.default_compression_algorithm': CompressionAlgorithms.gzip + 'grpc.default_compression_algorithm': CompressionAlgorithms.gzip, } ); @@ -853,7 +885,7 @@ describe('Compressed requests', () => { timesResponded += 1; }); - serverStream.on('error', (err) => { + serverStream.on('error', err => { assert.ifError(err); done(); }); @@ -873,7 +905,7 @@ describe('Compressed requests', () => { timesResponded += 1; }); - bidiStream.on('error', (err) => { + bidiStream.on('error', err => { assert.ifError(err); done(); }); @@ -890,25 +922,27 @@ describe('Compressed requests', () => { bidiStream.write({ message: 'baz' }, () => { timesRequested += 1; setTimeout(() => bidiStream.end(), 10); - }) - }) + }); + }); }); }); it('Should handle large messages', done => { let longMessage = ''; for (let i = 0; i < 400000; i++) { - const letter = 'abcdefghijklmnopqrstuvwxyz'[Math.floor(Math.random() * 26)]; + const letter = 'abcdefghijklmnopqrstuvwxyz'[ + Math.floor(Math.random() * 26) + ]; longMessage = longMessage + letter.repeat(10); } - client.unary({message: longMessage}, (err, response) => { + client.unary({ message: longMessage }, (err, response) => { assert.ifError(err); assert.strictEqual(response?.message, longMessage); done(); - }) - }) - + }); + }); + /* As of Node 16, Writable and Duplex streams validate the encoding * argument to write, and the flags values we are passing there are not * valid. We don't currently have an alternative way to pass that flag @@ -922,7 +956,7 @@ describe('Compressed requests', () => { timesResponded += 1; }); - bidiStream.on('error', (err) => { + bidiStream.on('error', err => { assert.ifError(err); done(); }); diff --git a/packages/grpc-js/test/test-uri-parser.ts b/packages/grpc-js/test/test-uri-parser.ts index d04cae539..1e20e5e26 100644 --- a/packages/grpc-js/test/test-uri-parser.ts +++ b/packages/grpc-js/test/test-uri-parser.ts @@ -19,59 +19,129 @@ import * as assert from 'assert'; import * as uriParser from '../src/uri-parser'; import * as resolver from '../src/resolver'; -describe('URI Parser', function(){ - describe('parseUri', function() { - const expectationList: {target: string, result: uriParser.GrpcUri | null}[] = [ - {target: 'localhost', result: {scheme: undefined, authority: undefined, path: 'localhost'}}, +describe('URI Parser', function () { + describe('parseUri', function () { + const expectationList: { + target: string; + result: uriParser.GrpcUri | null; + }[] = [ + { + target: 'localhost', + result: { scheme: undefined, authority: undefined, path: 'localhost' }, + }, /* This looks weird, but it's OK because the resolver selection code will handle it */ - {target: 'localhost:80', result: {scheme: 'localhost', authority: undefined, path: '80'}}, - {target: 'dns:localhost', result: {scheme: 'dns', authority: undefined, path: 'localhost'}}, - {target: 'dns:///localhost', result: {scheme: 'dns', authority: '', path: 'localhost'}}, - {target: 'dns://authority/localhost', result: {scheme: 'dns', authority: 'authority', path: 'localhost'}}, - {target: '//authority/localhost', result: {scheme: undefined, authority: 'authority', path: 'localhost'}}, + { + target: 'localhost:80', + result: { scheme: 'localhost', authority: undefined, path: '80' }, + }, + { + target: 'dns:localhost', + result: { scheme: 'dns', authority: undefined, path: 'localhost' }, + }, + { + target: 'dns:///localhost', + result: { scheme: 'dns', authority: '', path: 'localhost' }, + }, + { + target: 'dns://authority/localhost', + result: { scheme: 'dns', authority: 'authority', path: 'localhost' }, + }, + { + target: '//authority/localhost', + result: { + scheme: undefined, + authority: 'authority', + path: 'localhost', + }, + }, // Regression test for https://github.com/grpc/grpc-node/issues/1359 - {target: 'dns:foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', result: {scheme: 'dns', authority: undefined, path: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443'}} + { + target: + 'dns:foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', + result: { + scheme: 'dns', + authority: undefined, + path: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', + }, + }, ]; - for (const {target, result} of expectationList) { - it (target, function() { + for (const { target, result } of expectationList) { + it(target, function () { assert.deepStrictEqual(uriParser.parseUri(target), result); }); } }); - describe('parseUri + mapUriDefaultScheme', function() { - const expectationList: {target: string, result: uriParser.GrpcUri | null}[] = [ - {target: 'localhost', result: {scheme: 'dns', authority: undefined, path: 'localhost'}}, - {target: 'localhost:80', result: {scheme: 'dns', authority: undefined, path: 'localhost:80'}}, - {target: 'dns:localhost', result: {scheme: 'dns', authority: undefined, path: 'localhost'}}, - {target: 'dns:///localhost', result: {scheme: 'dns', authority: '', path: 'localhost'}}, - {target: 'dns://authority/localhost', result: {scheme: 'dns', authority: 'authority', path: 'localhost'}}, - {target: 'unix:socket', result: {scheme: 'unix', authority: undefined, path: 'socket'}}, - {target: 'bad:path', result: {scheme: 'dns', authority: undefined, path: 'bad:path'}} + describe('parseUri + mapUriDefaultScheme', function () { + const expectationList: { + target: string; + result: uriParser.GrpcUri | null; + }[] = [ + { + target: 'localhost', + result: { scheme: 'dns', authority: undefined, path: 'localhost' }, + }, + { + target: 'localhost:80', + result: { scheme: 'dns', authority: undefined, path: 'localhost:80' }, + }, + { + target: 'dns:localhost', + result: { scheme: 'dns', authority: undefined, path: 'localhost' }, + }, + { + target: 'dns:///localhost', + result: { scheme: 'dns', authority: '', path: 'localhost' }, + }, + { + target: 'dns://authority/localhost', + result: { scheme: 'dns', authority: 'authority', path: 'localhost' }, + }, + { + target: 'unix:socket', + result: { scheme: 'unix', authority: undefined, path: 'socket' }, + }, + { + target: 'bad:path', + result: { scheme: 'dns', authority: undefined, path: 'bad:path' }, + }, ]; - for (const {target, result} of expectationList) { - it(target, function() { - assert.deepStrictEqual(resolver.mapUriDefaultScheme(uriParser.parseUri(target) ?? {path: 'null'}), result); - }) + for (const { target, result } of expectationList) { + it(target, function () { + assert.deepStrictEqual( + resolver.mapUriDefaultScheme( + uriParser.parseUri(target) ?? { path: 'null' } + ), + result + ); + }); } }); - - describe('splitHostPort', function() { - const expectationList: {path: string, result: uriParser.HostPort | null}[] = [ - {path: 'localhost', result: {host: 'localhost'}}, - {path: 'localhost:123', result: {host: 'localhost', port: 123}}, - {path: '12345:6789', result: {host: '12345', port: 6789}}, - {path: '[::1]:123', result: {host: '::1', port: 123}}, - {path: '[::1]', result: {host: '::1'}}, - {path: '[', result: null}, - {path: '[123]', result: null}, + + describe('splitHostPort', function () { + const expectationList: { + path: string; + result: uriParser.HostPort | null; + }[] = [ + { path: 'localhost', result: { host: 'localhost' } }, + { path: 'localhost:123', result: { host: 'localhost', port: 123 } }, + { path: '12345:6789', result: { host: '12345', port: 6789 } }, + { path: '[::1]:123', result: { host: '::1', port: 123 } }, + { path: '[::1]', result: { host: '::1' } }, + { path: '[', result: null }, + { path: '[123]', result: null }, // Regression test for https://github.com/grpc/grpc-node/issues/1359 - {path: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', result: {host: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443'}} + { + path: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', + result: { + host: 'foo-internal.aws-us-east-2.tracing.staging-edge.foo-data.net:443:443', + }, + }, ]; - for (const {path, result} of expectationList) { - it(path, function() { + for (const { path, result } of expectationList) { + it(path, function () { assert.deepStrictEqual(uriParser.splitHostPort(path), result); }); } }); -}); \ No newline at end of file +}); From 608f0872316108de7f3edc40bf5e74871f3e9433 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 15 Jun 2023 11:07:03 -0700 Subject: [PATCH 497/694] Fix name generation and include type_url in CSDS --- packages/grpc-js-xds/src/csds.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts index 06fb5c50b..2952b46df 100644 --- a/packages/grpc-js-xds/src/csds.ts +++ b/packages/grpc-js-xds/src/csds.ts @@ -62,10 +62,11 @@ function getCurrentConfigList(): ClientConfig[] { for (const [authority, authorityState] of client.authorityStateMap) { for (const [type, typeMap] of authorityState.resourceMap) { for (const [key, resourceState] of typeMap) { - const typeUrl = type.getFullTypeUrl(); + const typeUrl = type.getTypeUrl(); const meta = resourceState.meta; genericConfigList.push({ name: xdsResourceNameToString({authority, key}, typeUrl), + type_url: typeUrl, client_status: meta.clientStatus, version_info: meta.version, xds_config: meta.clientStatus === 'ACKED' ? meta.rawResource : undefined, From 978f4cb0122c7dc7726cd971268148ef85a49952 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 16 Jun 2023 10:12:20 -0700 Subject: [PATCH 498/694] Add tracing, reorder LRS stream start call --- packages/grpc-js-xds/src/xds-client.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index e12c73fb0..dbf226bef 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -680,7 +680,7 @@ class LrsCallState { private handleStreamStatus(status: StatusObject) { this.client.trace( - 'ADS stream ended. code=' + status.code + ' details= ' + status.details + 'LRS stream ended. code=' + status.code + ' details= ' + status.details ); this.client.handleLrsStreamEnd(); } @@ -913,6 +913,7 @@ class XdsSingleServerClient { if (this.adsCallState || this.refcount < 1) { return; } + this.trace('Starting ADS stream'); const metadata = new Metadata({waitForReady: true}); const call = this.adsClient.StreamAggregatedResources(metadata); this.adsCallState = new AdsCallState(this, call, this.xdsClient.adsNode!); @@ -937,6 +938,7 @@ class XdsSingleServerClient { if (this.lrsCallState || this.refcount < 1 || this.clusterStatsMap.size < 1) { return; } + this.trace('Starting LRS stream'); const metadata = new Metadata({waitForReady: true}); const call = this.lrsClient.StreamLoadStats(metadata); this.lrsCallState = new LrsCallState(this, call, this.xdsClient.lrsNode!); @@ -976,11 +978,11 @@ class XdsSingleServerClient { edsServiceName: string ): XdsClusterDropStats { this.trace('addClusterDropStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ')'); - this.maybeStartLrsStream(); const clusterStats = this.clusterStatsMap.getOrCreate( clusterName, edsServiceName ); + this.maybeStartLrsStream(); return { addUncategorizedCallDropped: () => { clusterStats.uncategorizedCallsDropped += 1; @@ -1003,11 +1005,11 @@ class XdsSingleServerClient { locality: Locality__Output ): XdsClusterLocalityStats { this.trace('addClusterLocalityStats(clusterName=' + clusterName + ', edsServiceName=' + edsServiceName + ', locality=' + JSON.stringify(locality) + ')'); - this.maybeStartLrsStream(); const clusterStats = this.clusterStatsMap.getOrCreate( clusterName, edsServiceName ); + this.maybeStartLrsStream(); let localityStats: ClusterLocalityStats | null = null; for (const statsObj of clusterStats.localityStats) { if (localityEqual(locality, statsObj.locality)) { From dc0094d4b0377839f46ecc5e5a9676620ab42707 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 16 Jun 2023 10:32:13 -0700 Subject: [PATCH 499/694] Send initial message at the beginning of a new LRS stream, and send node in initial message --- packages/grpc-js-xds/src/xds-client.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index dbf226bef..2ae9618b3 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -676,6 +676,7 @@ class LrsCallState { this.handleStreamStatus(status); }); call.on('error', () => {}); + this.sendStats(); } private handleStreamStatus(status: StatusObject) { @@ -714,10 +715,13 @@ class LrsCallState { } private sendLrsMessage(clusterStats: ClusterStats[]) { - this.call.write({ - node: this.sentInitialMessage ? this.node : null, + const request: LoadStatsRequest = { + node: this.sentInitialMessage ? null : this.node, cluster_stats: clusterStats - }); + }; + this.client.trace('Sending LRS message ' + JSON.stringify(request, undefined, 2)); + this.call.write(request); + this.sentInitialMessage = true; } private get latestLrsSettings() { @@ -791,7 +795,6 @@ class LrsCallState { } } } - this.client.trace('Sending LRS stats ' + JSON.stringify(clusterStats, undefined, 2)); this.sendLrsMessage(clusterStats); } From fb735d99dc031b9ebef874fab3d6607ffe25bca5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 16 Jun 2023 13:21:07 -0700 Subject: [PATCH 500/694] Correct 'SOTW' flag for endpoint resource --- .../grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts index 9df9c5440..ecdd1f2a1 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts @@ -116,7 +116,7 @@ export class EndpointResourceType extends XdsResourceType { } allResourcesRequiredInSotW(): boolean { - return true; + return false; } static startWatch(client: XdsClient, name: string, watcher: Watcher) { From f253a4966a766222b85d1507c7a29e67a48eaa3f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 16 Jun 2023 13:54:33 -0700 Subject: [PATCH 501/694] grpc-js-xds: Update Node version in old test script --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index 402678e23..dc9a0244a 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -24,7 +24,7 @@ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | b # Load NVM . $NVM_DIR/nvm.sh -nvm install 12 +nvm install 18 set -exu -o pipefail [[ -f /VERSION ]] && cat /VERSION From b4078a36da27d50693e9379d2e69ad96b94b88f1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 20 Jun 2023 10:04:13 -0700 Subject: [PATCH 502/694] grpc-js-xds: Downgrade Node version in old test script to 16 --- packages/grpc-js-xds/scripts/xds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds.sh b/packages/grpc-js-xds/scripts/xds.sh index dc9a0244a..85124cdc1 100755 --- a/packages/grpc-js-xds/scripts/xds.sh +++ b/packages/grpc-js-xds/scripts/xds.sh @@ -24,7 +24,7 @@ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | b # Load NVM . $NVM_DIR/nvm.sh -nvm install 18 +nvm install 16 set -exu -o pipefail [[ -f /VERSION ]] && cat /VERSION From 87b5466b1b5e6d269a9750305c5842c9e30e56e8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 20 Jun 2023 10:25:59 -0700 Subject: [PATCH 503/694] grpc-js: Implement trace function in Http2SubchannelConnector --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/transport.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 7d8c1a597..f0331f1fc 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.15", + "version": "1.8.16", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 8abc13aba..2f89d8f28 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -485,7 +485,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { private isShutdown = false; constructor(private channelTarget: GrpcUri) {} private trace(text: string) { - + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, this.channelTarget + ' ' + text); } private createSession(address: SubchannelAddress, credentials: ChannelCredentials, options: ChannelOptions, proxyConnectionResult: ProxyConnectionResult): Promise { if (this.isShutdown) { From fcff72b9419fadca5400f5793e598366c7121ea2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 20 Jun 2023 10:01:43 -0700 Subject: [PATCH 504/694] grpc-js: Implement channel idle timeout --- packages/grpc-js/README.md | 1 + packages/grpc-js/src/channel-options.ts | 2 + packages/grpc-js/src/internal-channel.ts | 92 +++++++++++++----- .../src/load-balancer-child-handler.ts | 4 + packages/grpc-js/src/resolver-dns.ts | 20 ++++ packages/grpc-js/src/resolver.ts | 5 +- .../grpc-js/src/resolving-load-balancer.ts | 12 ++- packages/grpc-js/test/common.ts | 90 +++++++++++++++++- packages/grpc-js/test/test-idle-timer.ts | 95 +++++++++++++++++++ 9 files changed, 292 insertions(+), 29 deletions(-) create mode 100644 packages/grpc-js/test/test-idle-timer.ts diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 652ce5fef..3b83433e8 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -63,6 +63,7 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.per_rpc_retry_buffer_size` - `grpc.retry_buffer_size` - `grpc.service_config_disable_resolution` + - `grpc.client_idle_timeout_ms` - `grpc-node.max_session_memory` - `channelOverride` - `channelFactoryOverride` diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index a41b89e9b..cf3103198 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -56,6 +56,7 @@ export interface ChannelOptions { 'grpc.max_connection_age_grace_ms'?: number; 'grpc-node.max_session_memory'?: number; 'grpc.service_config_disable_resolution'?: number; + 'grpc.client_idle_timeout_ms'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } @@ -89,6 +90,7 @@ export const recognizedOptions = { 'grpc.max_connection_age_grace_ms': true, 'grpc-node.max_session_memory': true, 'grpc.service_config_disable_resolution': true, + 'grpc.client_idle_timeout_ms': true }; export function channelOptionsEqual( diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 3f3ca5d7f..6a11028ec 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -20,7 +20,7 @@ import { ChannelOptions } from './channel-options'; import { ResolvingLoadBalancer } from './resolving-load-balancer'; import { SubchannelPool, getSubchannelPool } from './subchannel-pool'; import { ChannelControlHelper } from './load-balancer'; -import { UnavailablePicker, Picker, PickResultType } from './picker'; +import { UnavailablePicker, Picker, PickResultType, QueuePicker } from './picker'; import { Metadata } from './metadata'; import { Status, LogVerbosity, Propagate } from './constants'; import { FilterStackFactory } from './filter-stack'; @@ -85,6 +85,11 @@ import { */ const MAX_TIMEOUT_TIME = 2147483647; +const MIN_IDLE_TIMEOUT_MS = 1000; + +// 30 minutes +const DEFAULT_IDLE_TIMEOUT_MS = 30 * 60 * 1000; + interface ConnectivityStateWatcher { currentState: ConnectivityState; timer: NodeJS.Timeout | null; @@ -153,8 +158,8 @@ class ChannelSubchannelWrapper } export class InternalChannel { - private resolvingLoadBalancer: ResolvingLoadBalancer; - private subchannelPool: SubchannelPool; + private readonly resolvingLoadBalancer: ResolvingLoadBalancer; + private readonly subchannelPool: SubchannelPool; private connectivityState: ConnectivityState = ConnectivityState.IDLE; private currentPicker: Picker = new UnavailablePicker(); /** @@ -164,9 +169,9 @@ export class InternalChannel { private configSelectionQueue: ResolvingCall[] = []; private pickQueue: LoadBalancingCall[] = []; private connectivityStateWatchers: ConnectivityStateWatcher[] = []; - private defaultAuthority: string; - private filterStackFactory: FilterStackFactory; - private target: GrpcUri; + private readonly defaultAuthority: string; + private readonly filterStackFactory: FilterStackFactory; + private readonly target: GrpcUri; /** * This timer does not do anything on its own. Its purpose is to hold the * event loop open while there are any pending calls for the channel that @@ -174,7 +179,7 @@ export class InternalChannel { * the invariant is that callRefTimer is reffed if and only if pickQueue * is non-empty. */ - private callRefTimer: NodeJS.Timer; + private readonly callRefTimer: NodeJS.Timer; private configSelector: ConfigSelector | null = null; /** * This is the error from the name resolver if it failed most recently. It @@ -184,17 +189,21 @@ export class InternalChannel { * than TRANSIENT_FAILURE. */ private currentResolutionError: StatusObject | null = null; - private retryBufferTracker: MessageBufferTracker; + private readonly retryBufferTracker: MessageBufferTracker; private keepaliveTime: number; - private wrappedSubchannels: Set = new Set(); + private readonly wrappedSubchannels: Set = new Set(); + + private callCount: number = 0; + private idleTimer: NodeJS.Timer | null = null; + private readonly idleTimeoutMs: number; // Channelz info private readonly channelzEnabled: boolean = true; - private originalTarget: string; - private channelzRef: ChannelRef; - private channelzTrace: ChannelzTrace; - private callTracker = new ChannelzCallTracker(); - private childrenTracker = new ChannelzChildrenTracker(); + private readonly originalTarget: string; + private readonly channelzRef: ChannelRef; + private readonly channelzTrace: ChannelzTrace; + private readonly callTracker = new ChannelzCallTracker(); + private readonly childrenTracker = new ChannelzChildrenTracker(); constructor( target: string, @@ -265,6 +274,7 @@ export class InternalChannel { DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES ); this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; + this.idleTimeoutMs = Math.max(options['grpc.client_idle_timeout_ms'] ?? DEFAULT_IDLE_TIMEOUT_MS, MIN_IDLE_TIMEOUT_MS); const channelControlHelper: ChannelControlHelper = { createSubchannel: ( subchannelAddress: SubchannelAddress, @@ -548,6 +558,45 @@ export class InternalChannel { this.callRefTimerRef(); } + private enterIdle() { + this.resolvingLoadBalancer.destroy(); + this.updateState(ConnectivityState.IDLE); + this.currentPicker = new QueuePicker(this.resolvingLoadBalancer); + } + + private maybeStartIdleTimer() { + if (this.callCount === 0) { + this.idleTimer = setTimeout(() => { + this.trace('Idle timer triggered after ' + this.idleTimeoutMs + 'ms of inactivity'); + this.enterIdle(); + }, this.idleTimeoutMs); + this.idleTimer.unref?.(); + } + } + + private onCallStart() { + if (this.channelzEnabled) { + this.callTracker.addCallStarted(); + } + this.callCount += 1; + if (this.idleTimer) { + clearTimeout(this.idleTimer); + this.idleTimer = null; + } + } + + private onCallEnd(status: StatusObject) { + if (this.channelzEnabled) { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + } + this.callCount -= 1; + this.maybeStartIdleTimer(); + } + createLoadBalancingCall( callConfig: CallConfig, method: string, @@ -653,16 +702,10 @@ export class InternalChannel { callNumber ); - if (this.channelzEnabled) { - this.callTracker.addCallStarted(); - call.addStatusWatcher(status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); - } + this.onCallStart(); + call.addStatusWatcher(status => { + this.onCallEnd(status); + }); return call; } @@ -685,6 +728,7 @@ export class InternalChannel { const connectivityState = this.connectivityState; if (tryToConnect) { this.resolvingLoadBalancer.exitIdle(); + this.maybeStartIdleTimer(); } return connectivityState; } diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index b556db0c6..a4dc90c4f 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -150,6 +150,10 @@ export class ChildLoadBalancerHandler implements LoadBalancer { } } destroy(): void { + /* Note: state updates are only propagated from the child balancer if that + * object is equal to this.currentChild or this.pendingChild. Since this + * function sets both of those to null, no further state updates will + * occur after this function returns. */ if (this.currentChild) { this.currentChild.destroy(); this.currentChild = null; diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index f5ea6ad4b..7f84e0c5c 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -207,6 +207,9 @@ class DnsResolver implements Resolver { this.pendingLookupPromise = dnsLookupPromise(hostname, { all: true }); this.pendingLookupPromise.then( addressList => { + if (this.pendingLookupPromise === null) { + return; + } this.pendingLookupPromise = null; this.backoff.reset(); this.backoff.stop(); @@ -248,6 +251,9 @@ class DnsResolver implements Resolver { ); }, err => { + if (this.pendingLookupPromise === null) { + return; + } trace( 'Resolution error for target ' + uriToString(this.target) + @@ -268,6 +274,9 @@ class DnsResolver implements Resolver { this.pendingTxtPromise = resolveTxtPromise(hostname); this.pendingTxtPromise.then( txtRecord => { + if (this.pendingTxtPromise === null) { + return; + } this.pendingTxtPromise = null; try { this.latestServiceConfig = extractAndSelectServiceConfig( @@ -348,10 +357,21 @@ class DnsResolver implements Resolver { } } + /** + * Reset the resolver to the same state it had when it was created. In-flight + * DNS requests cannot be cancelled, but they are discarded and their results + * will be ignored. + */ destroy() { this.continueResolving = false; + this.backoff.reset(); this.backoff.stop(); this.stopNextResolutionTimer(); + this.pendingLookupPromise = null; + this.pendingTxtPromise = null; + this.latestLookupResult = null; + this.latestServiceConfig = null; + this.latestServiceConfigError = null; } /** diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index 770004487..435086255 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -82,7 +82,10 @@ export interface Resolver { updateResolution(): void; /** - * Destroy the resolver. Should be called when the owning channel shuts down. + * Discard all resources owned by the resolver. A later call to + * `updateResolution` should reinitialize those resources. No + * `ResolverListener` callbacks should be called after `destroy` is called + * until `updateResolution` is called again. */ destroy(): void; } diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 064053bc9..16533354b 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -94,9 +94,9 @@ export class ResolvingLoadBalancer implements LoadBalancer { /** * The resolver class constructed for the target address. */ - private innerResolver: Resolver; + private readonly innerResolver: Resolver; - private childLoadBalancer: ChildLoadBalancerHandler; + private readonly childLoadBalancer: ChildLoadBalancerHandler; private latestChildState: ConnectivityState = ConnectivityState.IDLE; private latestChildPicker: Picker = new QueuePicker(this); /** @@ -324,7 +324,13 @@ export class ResolvingLoadBalancer implements LoadBalancer { destroy() { this.childLoadBalancer.destroy(); this.innerResolver.destroy(); - this.updateState(ConnectivityState.SHUTDOWN, new UnavailablePicker()); + this.backoffTimeout.reset(); + this.backoffTimeout.stop(); + this.latestChildState = ConnectivityState.IDLE; + this.latestChildPicker = new QueuePicker(this); + this.currentState = ConnectivityState.IDLE; + this.previousServiceConfig = null; + this.continueResolving = false; } getTypeName() { diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index 16b393b55..14fec91f9 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -17,8 +17,11 @@ import * as loader from '@grpc/proto-loader'; import * as assert2 from './assert2'; +import * as path from 'path'; +import * as grpc from '../src'; -import { GrpcObject, loadPackageDefinition } from '../src/make-client'; +import { GrpcObject, ServiceClientConstructor, ServiceClient, loadPackageDefinition } from '../src/make-client'; +import { readFileSync } from 'fs'; const protoLoaderOptions = { keepCase: true, @@ -37,4 +40,89 @@ export function loadProtoFile(file: string): GrpcObject { return loadPackageDefinition(packageDefinition); } +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; + +const ca = readFileSync(path.join(__dirname, 'fixtures', 'ca.pem')); +const key = readFileSync(path.join(__dirname, 'fixtures', 'server1.key')); +const cert = readFileSync(path.join(__dirname, 'fixtures', 'server1.pem')); + +const serviceImpl = { + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + callback(null, call.request); + }, +}; + +export class TestServer { + private server: grpc.Server; + public port: number | null = null; + constructor(public useTls: boolean, options?: grpc.ChannelOptions) { + this.server = new grpc.Server(options); + this.server.addService(echoService.service, serviceImpl); + } + start(): Promise { + let credentials: grpc.ServerCredentials; + if (this.useTls) { + credentials = grpc.ServerCredentials.createSsl(null, [{private_key: key, cert_chain: cert}]); + } else { + credentials = grpc.ServerCredentials.createInsecure(); + } + return new Promise((resolve, reject) => { + this.server.bindAsync('localhost:0', credentials, (error, port) => { + if (error) { + reject(error); + return; + } + this.port = port; + this.server.start(); + resolve(); + }); + }); + } + + shutdown() { + this.server.forceShutdown(); + } +} + +export class TestClient { + private client: ServiceClient; + constructor(port: number, useTls: boolean, options?: grpc.ChannelOptions) { + let credentials: grpc.ChannelCredentials; + if (useTls) { + credentials = grpc.credentials.createSsl(ca); + } else { + credentials = grpc.credentials.createInsecure(); + } + this.client = new echoService(`localhost:${port}`, credentials, options); + } + + static createFromServer(server: TestServer, options?: grpc.ChannelOptions) { + if (server.port === null) { + throw new Error('Cannot create client, server not started'); + } + return new TestClient(server.port, server.useTls, options); + } + + waitForReady(deadline: grpc.Deadline, callback: (error?: Error) => void) { + this.client.waitForReady(deadline, callback); + } + + sendRequest(callback: (error: grpc.ServiceError) => void) { + this.client.echo({}, callback); + } + + getChannelState() { + return this.client.getChannel().getConnectivityState(false); + } + + close() { + this.client.close(); + } +} + export { assert2 }; diff --git a/packages/grpc-js/test/test-idle-timer.ts b/packages/grpc-js/test/test-idle-timer.ts new file mode 100644 index 000000000..646d0e075 --- /dev/null +++ b/packages/grpc-js/test/test-idle-timer.ts @@ -0,0 +1,95 @@ +/* + * 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. + * + */ + +import * as assert from 'assert'; +import * as grpc from '../src'; +import { TestClient, TestServer } from "./common"; + +describe('Channel idle timer', () => { + let server: TestServer; + let client: TestClient | null = null; + before(() => { + server = new TestServer(false); + return server.start(); + }); + afterEach(() => { + if (client) { + client.close(); + client = null; + } + }) + after(() => { + server.shutdown(); + }); + it('Should go idle after the specified time after a request ends', function(done) { + this.timeout(5000); + client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 1000}); + client.sendRequest(error => { + assert.ifError(error); + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + setTimeout(() => { + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + done(); + }, 1100); + }); + }); + it('Should be able to make a request after going idle', function(done) { + this.timeout(5000); + client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 1000}); + client.sendRequest(error => { + assert.ifError(error); + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + setTimeout(() => { + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + client!.sendRequest(error => { + assert.ifError(error); + done(); + }); + }, 1100); + }); + }); + it('Should go idle after the specified time after waitForReady ends', function(done) { + this.timeout(5000); + client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 1000}); + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 3); + client.waitForReady(deadline, error => { + assert.ifError(error); + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + setTimeout(() => { + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + done(); + }, 1100); + }); + }); + it('Should ensure that the timeout is at least 1 second', function(done) { + client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 50}); + client.sendRequest(error => { + assert.ifError(error); + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + setTimeout(() => { + // Should still be ready after 100ms + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + setTimeout(() => { + // Should go IDLE after another second + assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + done(); + }, 1000); + }, 100); + }); + }) +}); \ No newline at end of file From 89cd8f7bc390b5fd2ca475b24d39ea2a207cef43 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 20 Jun 2023 15:46:27 -0700 Subject: [PATCH 505/694] grpc-js: Idle timeout: format files --- packages/grpc-js/src/channel-options.ts | 2 +- packages/grpc-js/src/internal-channel.ts | 23 +++++-- packages/grpc-js/test/common.ts | 11 +++- packages/grpc-js/test/test-idle-timer.ts | 77 +++++++++++++++++------- 4 files changed, 84 insertions(+), 29 deletions(-) diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index cf3103198..1120ab848 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -90,7 +90,7 @@ export const recognizedOptions = { 'grpc.max_connection_age_grace_ms': true, 'grpc-node.max_session_memory': true, 'grpc.service_config_disable_resolution': true, - 'grpc.client_idle_timeout_ms': true + 'grpc.client_idle_timeout_ms': true, }; export function channelOptionsEqual( diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 6a11028ec..1eff3cf28 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -20,7 +20,12 @@ import { ChannelOptions } from './channel-options'; import { ResolvingLoadBalancer } from './resolving-load-balancer'; import { SubchannelPool, getSubchannelPool } from './subchannel-pool'; import { ChannelControlHelper } from './load-balancer'; -import { UnavailablePicker, Picker, PickResultType, QueuePicker } from './picker'; +import { + UnavailablePicker, + Picker, + PickResultType, + QueuePicker, +} from './picker'; import { Metadata } from './metadata'; import { Status, LogVerbosity, Propagate } from './constants'; import { FilterStackFactory } from './filter-stack'; @@ -191,9 +196,10 @@ export class InternalChannel { private currentResolutionError: StatusObject | null = null; private readonly retryBufferTracker: MessageBufferTracker; private keepaliveTime: number; - private readonly wrappedSubchannels: Set = new Set(); + private readonly wrappedSubchannels: Set = + new Set(); - private callCount: number = 0; + private callCount = 0; private idleTimer: NodeJS.Timer | null = null; private readonly idleTimeoutMs: number; @@ -274,7 +280,10 @@ export class InternalChannel { DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES ); this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; - this.idleTimeoutMs = Math.max(options['grpc.client_idle_timeout_ms'] ?? DEFAULT_IDLE_TIMEOUT_MS, MIN_IDLE_TIMEOUT_MS); + this.idleTimeoutMs = Math.max( + options['grpc.client_idle_timeout_ms'] ?? DEFAULT_IDLE_TIMEOUT_MS, + MIN_IDLE_TIMEOUT_MS + ); const channelControlHelper: ChannelControlHelper = { createSubchannel: ( subchannelAddress: SubchannelAddress, @@ -567,7 +576,11 @@ export class InternalChannel { private maybeStartIdleTimer() { if (this.callCount === 0) { this.idleTimer = setTimeout(() => { - this.trace('Idle timer triggered after ' + this.idleTimeoutMs + 'ms of inactivity'); + this.trace( + 'Idle timer triggered after ' + + this.idleTimeoutMs + + 'ms of inactivity' + ); this.enterIdle(); }, this.idleTimeoutMs); this.idleTimer.unref?.(); diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index 14fec91f9..26192d3eb 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -20,7 +20,12 @@ import * as assert2 from './assert2'; import * as path from 'path'; import * as grpc from '../src'; -import { GrpcObject, ServiceClientConstructor, ServiceClient, loadPackageDefinition } from '../src/make-client'; +import { + GrpcObject, + ServiceClientConstructor, + ServiceClient, + loadPackageDefinition, +} from '../src/make-client'; import { readFileSync } from 'fs'; const protoLoaderOptions = { @@ -67,7 +72,9 @@ export class TestServer { start(): Promise { let credentials: grpc.ServerCredentials; if (this.useTls) { - credentials = grpc.ServerCredentials.createSsl(null, [{private_key: key, cert_chain: cert}]); + credentials = grpc.ServerCredentials.createSsl(null, [ + { private_key: key, cert_chain: cert }, + ]); } else { credentials = grpc.ServerCredentials.createInsecure(); } diff --git a/packages/grpc-js/test/test-idle-timer.ts b/packages/grpc-js/test/test-idle-timer.ts index 646d0e075..3fdeb1f64 100644 --- a/packages/grpc-js/test/test-idle-timer.ts +++ b/packages/grpc-js/test/test-idle-timer.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import * as grpc from '../src'; -import { TestClient, TestServer } from "./common"; +import { TestClient, TestServer } from './common'; describe('Channel idle timer', () => { let server: TestServer; @@ -31,30 +31,46 @@ describe('Channel idle timer', () => { client.close(); client = null; } - }) + }); after(() => { server.shutdown(); }); - it('Should go idle after the specified time after a request ends', function(done) { + it('Should go idle after the specified time after a request ends', function (done) { this.timeout(5000); - client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 1000}); + client = TestClient.createFromServer(server, { + 'grpc.client_idle_timeout_ms': 1000, + }); client.sendRequest(error => { assert.ifError(error); - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); setTimeout(() => { - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.IDLE + ); done(); }, 1100); }); }); - it('Should be able to make a request after going idle', function(done) { + it('Should be able to make a request after going idle', function (done) { this.timeout(5000); - client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 1000}); + client = TestClient.createFromServer(server, { + 'grpc.client_idle_timeout_ms': 1000, + }); client.sendRequest(error => { assert.ifError(error); - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); setTimeout(() => { - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.IDLE + ); client!.sendRequest(error => { assert.ifError(error); done(); @@ -62,34 +78,53 @@ describe('Channel idle timer', () => { }, 1100); }); }); - it('Should go idle after the specified time after waitForReady ends', function(done) { + it('Should go idle after the specified time after waitForReady ends', function (done) { this.timeout(5000); - client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 1000}); + client = TestClient.createFromServer(server, { + 'grpc.client_idle_timeout_ms': 1000, + }); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 3); client.waitForReady(deadline, error => { assert.ifError(error); - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); setTimeout(() => { - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.IDLE + ); done(); }, 1100); }); }); - it('Should ensure that the timeout is at least 1 second', function(done) { - client = TestClient.createFromServer(server, {'grpc.client_idle_timeout_ms': 50}); + it('Should ensure that the timeout is at least 1 second', function (done) { + client = TestClient.createFromServer(server, { + 'grpc.client_idle_timeout_ms': 50, + }); client.sendRequest(error => { assert.ifError(error); - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); setTimeout(() => { // Should still be ready after 100ms - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.READY); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); setTimeout(() => { // Should go IDLE after another second - assert.strictEqual(client!.getChannelState(), grpc.connectivityState.IDLE); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.IDLE + ); done(); }, 1000); }, 100); }); - }) -}); \ No newline at end of file + }); +}); From 967f903ff883fd4673d02a0241ed42e1d59b319c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 21 Jun 2023 11:25:46 -0700 Subject: [PATCH 506/694] Newlines at ends of files --- packages/grpc-js-xds/src/csds.ts | 2 +- packages/grpc-js-xds/src/environment.ts | 2 +- packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts | 2 +- packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts | 2 +- packages/grpc-js-xds/src/resources.ts | 2 +- .../grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts | 2 +- .../grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts | 2 +- .../grpc-js-xds/src/xds-resource-type/listener-resource-type.ts | 2 +- .../src/xds-resource-type/route-config-resource-type.ts | 2 +- packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts | 2 +- packages/grpc-js-xds/test/client.ts | 2 +- packages/grpc-js-xds/test/framework.ts | 2 +- packages/grpc-js-xds/test/test-core.ts | 2 +- packages/grpc-js-xds/test/test-federation.ts | 2 +- packages/grpc-js-xds/test/test-listener-resource-name.ts | 2 +- packages/grpc-js-xds/test/xds-server.ts | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/grpc-js-xds/src/csds.ts b/packages/grpc-js-xds/src/csds.ts index 2952b46df..f9fac8569 100644 --- a/packages/grpc-js-xds/src/csds.ts +++ b/packages/grpc-js-xds/src/csds.ts @@ -142,4 +142,4 @@ const csdsServiceDefinition = csdsGrpcObject.envoy.service.status.v3.ClientStatu export function setup() { registerAdminService(() => csdsServiceDefinition, () => csdsImplementation); -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 8bd85b51d..32c9f28ba 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -18,4 +18,4 @@ export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; -export const EXPERIMENTAL_FEDERATION = (process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION ?? 'false') === 'true'; \ No newline at end of file +export const EXPERIMENTAL_FEDERATION = (process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION ?? 'false') === 'true'; diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index 6599ba758..edfd52016 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -269,4 +269,4 @@ class XdsClusterImplBalancer implements LoadBalancer { export function setup() { registerLoadBalancerType(TYPE_NAME, XdsClusterImplBalancer, XdsClusterImplLoadBalancingConfig); -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index 6803da5c2..40f67c476 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -483,4 +483,4 @@ export class XdsClusterResolverChildPolicyHandler extends ChildLoadBalancerHandl export function setup() { registerLoadBalancerType(TYPE_NAME, XdsClusterResolver, XdsClusterResolverLoadBalancingConfig); -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/resources.ts b/packages/grpc-js-xds/src/resources.ts index 75cd550f3..4542c5fd6 100644 --- a/packages/grpc-js-xds/src/resources.ts +++ b/packages/grpc-js-xds/src/resources.ts @@ -147,4 +147,4 @@ export function xdsResourceNameToString(name: XdsResourceName, typeUrl: string): return name.key; } return `xdstp://${name.authority}/${typeUrl}/${name.key}`; -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index 04afae11f..f431bf238 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -274,4 +274,4 @@ export class ClusterResourceType extends XdsResourceType { static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { client.cancelResourceWatch(ClusterResourceType.get(), name, watcher); } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts index ecdd1f2a1..6ffd7788d 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts @@ -126,4 +126,4 @@ export class EndpointResourceType extends XdsResourceType { static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { client.cancelResourceWatch(EndpointResourceType.get(), name, watcher); } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts index 09ad3ff09..20e243b86 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts @@ -131,4 +131,4 @@ export class ListenerResourceType extends XdsResourceType { static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { client.cancelResourceWatch(ListenerResourceType.get(), name, watcher); } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts index 278208c47..cdc2e3196 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts @@ -194,4 +194,4 @@ export class RouteConfigurationResourceType extends XdsResourceType { static cancelWatch(client: XdsClient, name: string, watcher: Watcher) { client.cancelResourceWatch(RouteConfigurationResourceType.get(), name, watcher); } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts index 114b164df..8c2dc5e4a 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/xds-resource-type.ts @@ -88,4 +88,4 @@ export abstract class XdsResourceType { resourcesEqual(value1: object | null, value2: object | null): boolean { return deepEqual(value1 as ValueType, value2 as ValueType); } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/test/client.ts b/packages/grpc-js-xds/test/client.ts index c2420b231..6d346f918 100644 --- a/packages/grpc-js-xds/test/client.ts +++ b/packages/grpc-js-xds/test/client.ts @@ -98,4 +98,4 @@ export class XdsTestClient { } sendInner(count, callback); } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index be0e977ea..4bb9fa142 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -319,4 +319,4 @@ export class FakeRouteGroup { } })); } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts index 0df576e17..cb145eb81 100644 --- a/packages/grpc-js-xds/test/test-core.ts +++ b/packages/grpc-js-xds/test/test-core.ts @@ -60,4 +60,4 @@ describe('core xDS functionality', () => { }, reason => done(reason)); }, reason => done(reason)); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js-xds/test/test-federation.ts b/packages/grpc-js-xds/test/test-federation.ts index 0a2a57b6f..5d4099bbf 100644 --- a/packages/grpc-js-xds/test/test-federation.ts +++ b/packages/grpc-js-xds/test/test-federation.ts @@ -206,4 +206,4 @@ describe('Federation', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js-xds/test/test-listener-resource-name.ts b/packages/grpc-js-xds/test/test-listener-resource-name.ts index de434d28c..2aeb2d3d6 100644 --- a/packages/grpc-js-xds/test/test-listener-resource-name.ts +++ b/packages/grpc-js-xds/test/test-listener-resource-name.ts @@ -122,4 +122,4 @@ describe('Listener resource name evaluation', () => { assert.strictEqual(getListenerResourceName(bootstrap, target), 'xdstp://xds.other.com/envoy.config.listener.v3.Listener/server.other.com'); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js-xds/test/xds-server.ts b/packages/grpc-js-xds/test/xds-server.ts index 8b677ac18..c9b829642 100644 --- a/packages/grpc-js-xds/test/xds-server.ts +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -347,4 +347,4 @@ export class XdsServer { } return JSON.stringify(bootstrapInfo); } -} \ No newline at end of file +} From 6fb6544483714c22ac70a2a9b1e23fccf2ebad48 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 21 Jun 2023 11:36:45 -0700 Subject: [PATCH 507/694] grpc-js: Update documentation of compression behavior in README --- packages/grpc-js/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 652ce5fef..1571bd0a3 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -19,7 +19,7 @@ Documentation specifically for the `@grpc/grpc-js` package is currently not avai - Servers - Streaming - Metadata -- Partial compression support: clients can decompress response messages +- Partial compression support: clients can compress and decompress messages, and servers can decompress request messages - Pick first and round robin load balancing policies - Client Interceptors - Connection Keepalives From 4f9c41978a2fe61fb1d43ff7f2675cae827f9c22 Mon Sep 17 00:00:00 2001 From: Xuan Wang Date: Thu, 22 Jun 2023 21:21:14 +0000 Subject: [PATCH 508/694] [PSM interop] Don't fail target if sub-target already failed --- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index 69126c72b..fc74718f2 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -156,7 +156,7 @@ main() { build_docker_images_if_needed # Run tests cd "${TEST_DRIVER_FULL_DIR}" - run_test url_map + run_test url_map || echo "Failed url_map test" } main "$@" From b53f5882f13a5cb3c599804e96304bf5b8407ea6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 22 Jun 2023 14:26:31 -0700 Subject: [PATCH 509/694] grpc-js: Disallow pick_first as child of outlier_detection --- packages/grpc-js/package.json | 2 +- .../grpc-js/src/load-balancer-outlier-detection.ts | 11 +++++++---- packages/grpc-js/src/resolver-dns.ts | 4 ++-- packages/grpc-js/test/test-outlier-detection.ts | 12 +++++++++++- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index f0331f1fc..d4b822600 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.16", + "version": "1.8.17", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 2f72a9625..885c4feb9 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -113,6 +113,9 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig failurePercentageEjection: Partial | null, private readonly childPolicy: LoadBalancingConfig[] ) { + if (childPolicy[0].getLoadBalancerName() === 'pick_first') { + throw new Error('outlier_detection LB policy cannot have a pick_first child policy'); + } this.intervalMs = intervalMs ?? 10_000; this.baseEjectionTimeMs = baseEjectionTimeMs ?? 30_000; this.maxEjectionTimeMs = maxEjectionTimeMs ?? 300_000; @@ -395,8 +398,8 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } private isCountingEnabled(): boolean { - return this.latestConfig !== null && - (this.latestConfig.getSuccessRateEjectionConfig() !== null || + return this.latestConfig !== null && + (this.latestConfig.getSuccessRateEjectionConfig() !== null || this.latestConfig.getFailurePercentageEjectionConfig() !== null); } @@ -496,7 +499,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { if (addressesWithTargetVolume < failurePercentageConfig.minimum_hosts) { return; } - + // Step 2 for (const [address, mapEntry] of this.addressMap.entries()) { // Step 2.i @@ -656,4 +659,4 @@ export function setup() { if (OUTLIER_DETECTION_ENABLED) { registerLoadBalancerType(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig); } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 355ce2dfd..b20b7a543 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -137,7 +137,7 @@ class DnsResolver implements Resolver { details: `Name resolution failed for target ${uriToString(this.target)}`, metadata: new Metadata(), }; - + const backoffOptions: BackoffOptions = { initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'], maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'], @@ -276,7 +276,7 @@ class DnsResolver implements Resolver { } catch (err) { this.latestServiceConfigError = { code: Status.UNAVAILABLE, - details: 'Parsing service config failed', + details: `Parsing service config failed with error ${(err as Error).message}`, metadata: new Metadata(), }; } diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts index c9021e605..d51ccf3fd 100644 --- a/packages/grpc-js/test/test-outlier-detection.ts +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -360,6 +360,16 @@ describe('Outlier detection config validation', () => { }, /failure_percentage_ejection\.enforcement_percentage parse error: value out of range for percentage/); }); }); + describe('child_policy', () => { + it('Should reject a pick_first child_policy', () => { + const loadBalancingConfig = { + child_policy: [{pick_first: {}}] + }; + assert.throws(() => { + OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); + }, /outlier_detection LB policy cannot have a pick_first child policy/); + }); + }); }); describe('Outlier detection', () => { @@ -533,4 +543,4 @@ describe('Outlier detection', () => { }) }); }); -}); \ No newline at end of file +}); From 204cd388dbcfa9e23bc9d72ee4207aab8ea2caff Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 22 Jun 2023 13:11:04 -0700 Subject: [PATCH 510/694] Import examples from core repository --- examples/README.md | 49 + .../dynamic_codegen/greeter_client.js | 57 + .../dynamic_codegen/greeter_server.js | 52 + examples/helloworld/static_codegen/README.md | 7 + .../static_codegen/greeter_client.js | 50 + .../static_codegen/greeter_server.js | 45 + .../static_codegen/helloworld_grpc_pb.js | 61 + .../static_codegen/helloworld_pb.js | 319 +++++ examples/package.json | 13 + examples/protos/BUILD | 82 ++ examples/protos/README.md | 8 + examples/protos/auth_sample.proto | 42 + examples/protos/hellostreamingworld.proto | 39 + examples/protos/helloworld.proto | 40 + examples/protos/keyvaluestore.proto | 33 + examples/protos/route_guide.proto | 111 ++ examples/routeguide/README.md | 5 + .../dynamic_codegen/route_guide_client.js | 237 ++++ .../dynamic_codegen/route_guide_db.json | 601 +++++++++ .../dynamic_codegen/route_guide_server.js | 245 ++++ examples/routeguide/static_codegen/README.md | 7 + .../static_codegen/route_guide_client.js | 237 ++++ .../static_codegen/route_guide_db.json | 601 +++++++++ .../static_codegen/route_guide_grpc_pb.js | 146 +++ .../static_codegen/route_guide_pb.js | 1069 +++++++++++++++++ .../static_codegen/route_guide_server.js | 244 ++++ examples/xds/greeter_client.js | 62 + 27 files changed, 4462 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/helloworld/dynamic_codegen/greeter_client.js create mode 100644 examples/helloworld/dynamic_codegen/greeter_server.js create mode 100644 examples/helloworld/static_codegen/README.md create mode 100644 examples/helloworld/static_codegen/greeter_client.js create mode 100644 examples/helloworld/static_codegen/greeter_server.js create mode 100644 examples/helloworld/static_codegen/helloworld_grpc_pb.js create mode 100644 examples/helloworld/static_codegen/helloworld_pb.js create mode 100644 examples/package.json create mode 100644 examples/protos/BUILD create mode 100644 examples/protos/README.md create mode 100644 examples/protos/auth_sample.proto create mode 100644 examples/protos/hellostreamingworld.proto create mode 100644 examples/protos/helloworld.proto create mode 100644 examples/protos/keyvaluestore.proto create mode 100644 examples/protos/route_guide.proto create mode 100644 examples/routeguide/README.md create mode 100644 examples/routeguide/dynamic_codegen/route_guide_client.js create mode 100644 examples/routeguide/dynamic_codegen/route_guide_db.json create mode 100644 examples/routeguide/dynamic_codegen/route_guide_server.js create mode 100644 examples/routeguide/static_codegen/README.md create mode 100644 examples/routeguide/static_codegen/route_guide_client.js create mode 100644 examples/routeguide/static_codegen/route_guide_db.json create mode 100644 examples/routeguide/static_codegen/route_guide_grpc_pb.js create mode 100644 examples/routeguide/static_codegen/route_guide_pb.js create mode 100644 examples/routeguide/static_codegen/route_guide_server.js create mode 100644 examples/xds/greeter_client.js diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..9958b17e9 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,49 @@ +gRPC in 3 minutes (Node.js) +=========================== + +PREREQUISITES +------------- + +- `node`: This requires Node 8.13.0 or greater. + +INSTALL +------- + + ```sh + $ # Get the gRPC repository + $ export REPO_ROOT=grpc-node # REPO root can be any directory of your choice + $ git clone -b RELEASE_TAG_HERE https://github.com/grpc/grpc-node $REPO_ROOT + $ cd $REPO_ROOT + + $ cd examples + $ npm install + ``` + +TRY IT! +------- + +There are two ways to generate the code needed to work with protocol buffers in Node.js - one approach uses [Protobuf.js](https://github.com/dcodeIO/ProtoBuf.js/) to dynamically generate the code at runtime, the other uses code statically generated using the protocol buffer compiler `protoc`. The examples behave identically, and either server can be used with either client. + + - Run the server + + ```sh + $ # from this directory + $ node ./helloworld/dynamic_codegen/greeter_server.js & + $ # OR + $ node ./helloworld/static_codegen/greeter_server.js & + ``` + + - Run the client + + ```sh + $ # from this directory + $ node ./helloworld/dynamic_codegen/greeter_client.js + $ # OR + $ node ./helloworld/static_codegen/greeter_client.js + ``` + +TUTORIAL +-------- +You can find a more detailed tutorial in [gRPC Basics: Node.js][] + +[gRPC Basics: Node.js]:https://grpc.io/docs/languages/node/basics diff --git a/examples/helloworld/dynamic_codegen/greeter_client.js b/examples/helloworld/dynamic_codegen/greeter_client.js new file mode 100644 index 000000000..17984893f --- /dev/null +++ b/examples/helloworld/dynamic_codegen/greeter_client.js @@ -0,0 +1,57 @@ +/* + * + * Copyright 2015 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. + * + */ + +var PROTO_PATH = __dirname + '/../../protos/helloworld.proto'; + +var parseArgs = require('minimist'); +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +function main() { + var argv = parseArgs(process.argv.slice(2), { + string: 'target' + }); + var target; + if (argv.target) { + target = argv.target; + } else { + target = 'localhost:50051'; + } + var client = new hello_proto.Greeter(target, + grpc.credentials.createInsecure()); + var user; + if (argv._.length > 0) { + user = argv._[0]; + } else { + user = 'world'; + } + client.sayHello({name: user}, function(err, response) { + console.log('Greeting:', response.message); + }); +} + +main(); diff --git a/examples/helloworld/dynamic_codegen/greeter_server.js b/examples/helloworld/dynamic_codegen/greeter_server.js new file mode 100644 index 000000000..c606cd8cc --- /dev/null +++ b/examples/helloworld/dynamic_codegen/greeter_server.js @@ -0,0 +1,52 @@ +/* + * + * Copyright 2015 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. + * + */ + +var PROTO_PATH = __dirname + '/../../protos/helloworld.proto'; + +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +/** + * Implements the SayHello RPC method. + */ +function sayHello(call, callback) { + callback(null, {message: 'Hello ' + call.request.name}); +} + +/** + * Starts an RPC server that receives requests for the Greeter service at the + * sample server port + */ +function main() { + var server = new grpc.Server(); + server.addService(hello_proto.Greeter.service, {sayHello: sayHello}); + server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { + server.start(); + }); +} + +main(); diff --git a/examples/helloworld/static_codegen/README.md b/examples/helloworld/static_codegen/README.md new file mode 100644 index 000000000..201ffb58e --- /dev/null +++ b/examples/helloworld/static_codegen/README.md @@ -0,0 +1,7 @@ +This is the static code generation variant of the Hello World. Code in these examples is pre-generated using protoc and the Node gRPC protoc plugin, and the generated code can be found in various `*_pb.js` files. The command line sequence for generating those files is as follows (assuming that `protoc` and `grpc_node_plugin` are present, and starting in the directory which contains this README.md file): + +```sh +cd ../protos +npm install -g grpc-tools +grpc_tools_node_protoc --js_out=import_style=commonjs,binary:../helloworld/static_codegen/ --grpc_out=grpc_js:../helloworld/static_codegen/ helloworld.proto +``` diff --git a/examples/helloworld/static_codegen/greeter_client.js b/examples/helloworld/static_codegen/greeter_client.js new file mode 100644 index 000000000..668a3f8e4 --- /dev/null +++ b/examples/helloworld/static_codegen/greeter_client.js @@ -0,0 +1,50 @@ +/* + * + * Copyright 2015 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. + * + */ + +var parseArgs = require('minimist'); +var messages = require('./helloworld_pb'); +var services = require('./helloworld_grpc_pb'); + +var grpc = require('@grpc/grpc-js'); + +function main() { + var argv = parseArgs(process.argv.slice(2), { + string: 'target' + }); + var target; + if (argv.target) { + target = argv.target; + } else { + target = 'localhost:50051'; + } + var client = new services.GreeterClient(target, + grpc.credentials.createInsecure()); + var request = new messages.HelloRequest(); + var user; + if (argv._.length > 0) { + user = argv._[0]; + } else { + user = 'world'; + } + request.setName(user); + client.sayHello(request, function(err, response) { + console.log('Greeting:', response.getMessage()); + }); +} + +main(); diff --git a/examples/helloworld/static_codegen/greeter_server.js b/examples/helloworld/static_codegen/greeter_server.js new file mode 100644 index 000000000..7a3e87d80 --- /dev/null +++ b/examples/helloworld/static_codegen/greeter_server.js @@ -0,0 +1,45 @@ +/* + * + * Copyright 2015 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. + * + */ + +var messages = require('./helloworld_pb'); +var services = require('./helloworld_grpc_pb'); + +var grpc = require('@grpc/grpc-js'); + +/** + * Implements the SayHello RPC method. + */ +function sayHello(call, callback) { + var reply = new messages.HelloReply(); + reply.setMessage('Hello ' + call.request.getName()); + callback(null, reply); +} + +/** + * Starts an RPC server that receives requests for the Greeter service at the + * sample server port + */ +function main() { + var server = new grpc.Server(); + server.addService(services.GreeterService, {sayHello: sayHello}); + server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { + server.start(); + }); +} + +main(); diff --git a/examples/helloworld/static_codegen/helloworld_grpc_pb.js b/examples/helloworld/static_codegen/helloworld_grpc_pb.js new file mode 100644 index 000000000..85dc0f0b7 --- /dev/null +++ b/examples/helloworld/static_codegen/helloworld_grpc_pb.js @@ -0,0 +1,61 @@ +// GENERATED CODE -- DO NOT EDIT! + +// Original file comments: +// Copyright 2015 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. +// +'use strict'; +var grpc = require('@grpc/grpc-js'); +var helloworld_pb = require('./helloworld_pb.js'); + +function serialize_helloworld_HelloReply(arg) { + if (!(arg instanceof helloworld_pb.HelloReply)) { + throw new Error('Expected argument of type helloworld.HelloReply'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_helloworld_HelloReply(buffer_arg) { + return helloworld_pb.HelloReply.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_helloworld_HelloRequest(arg) { + if (!(arg instanceof helloworld_pb.HelloRequest)) { + throw new Error('Expected argument of type helloworld.HelloRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_helloworld_HelloRequest(buffer_arg) { + return helloworld_pb.HelloRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +// The greeting service definition. +var GreeterService = exports.GreeterService = { + // Sends a greeting +sayHello: { + path: '/helloworld.Greeter/SayHello', + requestStream: false, + responseStream: false, + requestType: helloworld_pb.HelloRequest, + responseType: helloworld_pb.HelloReply, + requestSerialize: serialize_helloworld_HelloRequest, + requestDeserialize: deserialize_helloworld_HelloRequest, + responseSerialize: serialize_helloworld_HelloReply, + responseDeserialize: deserialize_helloworld_HelloReply, + }, +}; + +exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService); diff --git a/examples/helloworld/static_codegen/helloworld_pb.js b/examples/helloworld/static_codegen/helloworld_pb.js new file mode 100644 index 000000000..e67680281 --- /dev/null +++ b/examples/helloworld/static_codegen/helloworld_pb.js @@ -0,0 +1,319 @@ +// source: helloworld.proto +/** + * @fileoverview + * @enhanceable + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = Function('return this')(); + +goog.exportSymbol('proto.helloworld.HelloReply', null, global); +goog.exportSymbol('proto.helloworld.HelloRequest', null, global); +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.helloworld.HelloRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.helloworld.HelloRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.helloworld.HelloRequest.displayName = 'proto.helloworld.HelloRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.helloworld.HelloReply = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.helloworld.HelloReply, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.helloworld.HelloReply.displayName = 'proto.helloworld.HelloReply'; +} + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.helloworld.HelloRequest.prototype.toObject = function(opt_includeInstance) { + return proto.helloworld.HelloRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.helloworld.HelloRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.helloworld.HelloRequest.toObject = function(includeInstance, msg) { + var f, obj = { + name: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.helloworld.HelloRequest} + */ +proto.helloworld.HelloRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.helloworld.HelloRequest; + return proto.helloworld.HelloRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.helloworld.HelloRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.helloworld.HelloRequest} + */ +proto.helloworld.HelloRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setName(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.helloworld.HelloRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.helloworld.HelloRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.helloworld.HelloRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.helloworld.HelloRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getName(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string name = 1; + * @return {string} + */ +proto.helloworld.HelloRequest.prototype.getName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.helloworld.HelloRequest} returns this + */ +proto.helloworld.HelloRequest.prototype.setName = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.helloworld.HelloReply.prototype.toObject = function(opt_includeInstance) { + return proto.helloworld.HelloReply.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.helloworld.HelloReply} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.helloworld.HelloReply.toObject = function(includeInstance, msg) { + var f, obj = { + message: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.helloworld.HelloReply} + */ +proto.helloworld.HelloReply.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.helloworld.HelloReply; + return proto.helloworld.HelloReply.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.helloworld.HelloReply} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.helloworld.HelloReply} + */ +proto.helloworld.HelloReply.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setMessage(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.helloworld.HelloReply.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.helloworld.HelloReply.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.helloworld.HelloReply} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.helloworld.HelloReply.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getMessage(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string message = 1; + * @return {string} + */ +proto.helloworld.HelloReply.prototype.getMessage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.helloworld.HelloReply} returns this + */ +proto.helloworld.HelloReply.prototype.setMessage = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +goog.object.extend(exports, proto.helloworld); diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 000000000..aee948c3f --- /dev/null +++ b/examples/package.json @@ -0,0 +1,13 @@ +{ + "name": "grpc-examples", + "version": "0.1.0", + "dependencies": { + "@grpc/proto-loader": "^0.6.0", + "async": "^1.5.2", + "google-protobuf": "^3.0.0", + "@grpc/grpc-js": "^1.8.0", + "@grpc/grpc-js-xds": "^1.8.0", + "lodash": "^4.6.1", + "minimist": "^1.2.0" + } +} diff --git a/examples/protos/BUILD b/examples/protos/BUILD new file mode 100644 index 000000000..929cad93a --- /dev/null +++ b/examples/protos/BUILD @@ -0,0 +1,82 @@ +# Copyright 2020 the 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. + +load("@rules_proto//proto:defs.bzl", "proto_library") +load("//bazel:cc_grpc_library.bzl", "cc_grpc_library") +load("//bazel:grpc_build_system.bzl", "grpc_proto_library") +load("//bazel:python_rules.bzl", "py_grpc_library", "py_proto_library") + +licenses(["notice"]) + +package(default_visibility = ["//visibility:public"]) + +grpc_proto_library( + name = "auth_sample", + srcs = ["auth_sample.proto"], +) + +grpc_proto_library( + name = "hellostreamingworld", + srcs = ["hellostreamingworld.proto"], +) + +# The following three rules demonstrate the usage of the cc_grpc_library rule in +# in a mode compatible with the native proto_library and cc_proto_library rules. +proto_library( + name = "helloworld_proto", + srcs = ["helloworld.proto"], +) + +cc_proto_library( + name = "helloworld_cc_proto", + deps = [":helloworld_proto"], +) + +cc_grpc_library( + name = "helloworld_cc_grpc", + srcs = [":helloworld_proto"], + grpc_only = True, + deps = [":helloworld_cc_proto"], +) + +grpc_proto_library( + name = "route_guide", + srcs = ["route_guide.proto"], +) + +proto_library( + name = "keyvaluestore_proto", + srcs = ["keyvaluestore.proto"], +) + +grpc_proto_library( + name = "keyvaluestore", + srcs = ["keyvaluestore.proto"], +) + +py_proto_library( + name = "helloworld_py_pb2", + deps = [":helloworld_proto"], +) + +py_grpc_library( + name = "helloworld_py_pb2_grpc", + srcs = [":helloworld_proto"], + deps = [":helloworld_py_pb2"], +) + +proto_library( + name = "route_guide_proto", + srcs = [":route_guide.proto"], +) diff --git a/examples/protos/README.md b/examples/protos/README.md new file mode 100644 index 000000000..48df7c894 --- /dev/null +++ b/examples/protos/README.md @@ -0,0 +1,8 @@ +# Example protos + +## Contents + +- [helloworld.proto] + - The simple example used in the overview. +- [route_guide.proto] + - An example service described in detail in the tutorial. diff --git a/examples/protos/auth_sample.proto b/examples/protos/auth_sample.proto new file mode 100644 index 000000000..7e63602f0 --- /dev/null +++ b/examples/protos/auth_sample.proto @@ -0,0 +1,42 @@ +// Copyright 2015 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. + +syntax = "proto3"; + +package grpc.testing; + +option objc_class_prefix = "AUTH"; + +// Unary request. +message Request { + // Whether Response should include username. + bool fill_username = 4; + + // Whether Response should include OAuth scope. + bool fill_oauth_scope = 5; +} + +// Unary response, as configured by the request. +message Response { + // The user the request came from, for verifying authentication was + // successful. + string username = 2; + // OAuth scope. + string oauth_scope = 3; +} + +service TestService { + // One request followed by one response. + rpc UnaryCall(Request) returns (Response); +} diff --git a/examples/protos/hellostreamingworld.proto b/examples/protos/hellostreamingworld.proto new file mode 100644 index 000000000..8a322bd61 --- /dev/null +++ b/examples/protos/hellostreamingworld.proto @@ -0,0 +1,39 @@ +// Copyright 2015 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. + +syntax = "proto3"; + +option java_package = "ex.grpc"; +option objc_class_prefix = "HSW"; + +package hellostreamingworld; + +// The greeting service definition. +service MultiGreeter { + // Sends multiple greetings + rpc sayHello (HelloRequest) returns (stream HelloReply) {} +} + +// The request message containing the user's name and how many greetings +// they want. +message HelloRequest { + string name = 1; + string num_greetings = 2; +} + +// A response message containing a greeting +message HelloReply { + string message = 1; +} + diff --git a/examples/protos/helloworld.proto b/examples/protos/helloworld.proto new file mode 100644 index 000000000..7e50d0fc7 --- /dev/null +++ b/examples/protos/helloworld.proto @@ -0,0 +1,40 @@ +// Copyright 2015 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. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + + rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/examples/protos/keyvaluestore.proto b/examples/protos/keyvaluestore.proto new file mode 100644 index 000000000..74ad57e02 --- /dev/null +++ b/examples/protos/keyvaluestore.proto @@ -0,0 +1,33 @@ +// Copyright 2018 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. + +syntax = "proto3"; + +package keyvaluestore; + +// A simple key-value storage service +service KeyValueStore { + // Provides a value for each key request + rpc GetValues (stream Request) returns (stream Response) {} +} + +// The request message containing the key +message Request { + string key = 1; +} + +// The response message containing the value associated with the key +message Response { + string value = 1; +} diff --git a/examples/protos/route_guide.proto b/examples/protos/route_guide.proto new file mode 100644 index 000000000..b519f5582 --- /dev/null +++ b/examples/protos/route_guide.proto @@ -0,0 +1,111 @@ +// Copyright 2015 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. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.routeguide"; +option java_outer_classname = "RouteGuideProto"; +option objc_class_prefix = "RTG"; + +package routeguide; + +// Interface exported by the server. +service RouteGuide { + // A simple RPC. + // + // Obtains the feature at a given position. + // + // A feature with an empty name is returned if there's no feature at the given + // position. + rpc GetFeature(Point) returns (Feature) {} + + // A server-to-client streaming RPC. + // + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + rpc ListFeatures(Rectangle) returns (stream Feature) {} + + // A client-to-server streaming RPC. + // + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + rpc RecordRoute(stream Point) returns (RouteSummary) {} + + // A Bidirectional streaming RPC. + // + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} +} + +// Points are represented as latitude-longitude pairs in the E7 representation +// (degrees multiplied by 10**7 and rounded to the nearest integer). +// Latitudes should be in the range +/- 90 degrees and longitude should be in +// the range +/- 180 degrees (inclusive). +message Point { + int32 latitude = 1; + int32 longitude = 2; +} + +// A latitude-longitude rectangle, represented as two diagonally opposite +// points "lo" and "hi". +message Rectangle { + // One corner of the rectangle. + Point lo = 1; + + // The other corner of the rectangle. + Point hi = 2; +} + +// A feature names something at a given point. +// +// If a feature could not be named, the name is empty. +message Feature { + // The name of the feature. + string name = 1; + + // The point where the feature is detected. + Point location = 2; +} + +// A RouteNote is a message sent while at a given point. +message RouteNote { + // The location from which the message is sent. + Point location = 1; + + // The message to be sent. + string message = 2; +} + +// A RouteSummary is received in response to a RecordRoute rpc. +// +// It contains the number of individual points received, the number of +// detected features, and the total distance covered as the cumulative sum of +// the distance between each point. +message RouteSummary { + // The number of points received. + int32 point_count = 1; + + // The number of known features passed while traversing the route. + int32 feature_count = 2; + + // The distance covered in metres. + int32 distance = 3; + + // The duration of the traversal in seconds. + int32 elapsed_time = 4; +} diff --git a/examples/routeguide/README.md b/examples/routeguide/README.md new file mode 100644 index 000000000..fcd147054 --- /dev/null +++ b/examples/routeguide/README.md @@ -0,0 +1,5 @@ +# gRPC Basics: Node.js sample code + +The files in this folder are the samples used in [gRPC Basics: Node.js][], a detailed tutorial for using gRPC in Node.js. + +[gRPC Basics: Node.js]:https://grpc.io/docs/languages/node/basics diff --git a/examples/routeguide/dynamic_codegen/route_guide_client.js b/examples/routeguide/dynamic_codegen/route_guide_client.js new file mode 100644 index 000000000..781464e6d --- /dev/null +++ b/examples/routeguide/dynamic_codegen/route_guide_client.js @@ -0,0 +1,237 @@ +/* + * + * Copyright 2015 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. + * + */ + +var PROTO_PATH = __dirname + '/../../protos/route_guide.proto'; + +var async = require('async'); +var fs = require('fs'); +var parseArgs = require('minimist'); +var path = require('path'); +var _ = require('lodash'); +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var routeguide = grpc.loadPackageDefinition(packageDefinition).routeguide; +var client = new routeguide.RouteGuide('localhost:50051', + grpc.credentials.createInsecure()); + +var COORD_FACTOR = 1e7; + +/** + * Run the getFeature demo. Calls getFeature with a point known to have a + * feature and a point known not to have a feature. + * @param {function} callback Called when this demo is complete + */ +function runGetFeature(callback) { + var next = _.after(2, callback); + function featureCallback(error, feature) { + if (error) { + callback(error); + return; + } + if (feature.name === '') { + console.log('Found no feature at ' + + feature.location.latitude/COORD_FACTOR + ', ' + + feature.location.longitude/COORD_FACTOR); + } else { + console.log('Found feature called "' + feature.name + '" at ' + + feature.location.latitude/COORD_FACTOR + ', ' + + feature.location.longitude/COORD_FACTOR); + } + next(); + } + var point1 = { + latitude: 409146138, + longitude: -746188906 + }; + var point2 = { + latitude: 0, + longitude: 0 + }; + client.getFeature(point1, featureCallback); + client.getFeature(point2, featureCallback); +} + +/** + * Run the listFeatures demo. Calls listFeatures with a rectangle containing all + * of the features in the pre-generated database. Prints each response as it + * comes in. + * @param {function} callback Called when this demo is complete + */ +function runListFeatures(callback) { + var rectangle = { + lo: { + latitude: 400000000, + longitude: -750000000 + }, + hi: { + latitude: 420000000, + longitude: -730000000 + } + }; + console.log('Looking for features between 40, -75 and 42, -73'); + var call = client.listFeatures(rectangle); + call.on('data', function(feature) { + console.log('Found feature called "' + feature.name + '" at ' + + feature.location.latitude/COORD_FACTOR + ', ' + + feature.location.longitude/COORD_FACTOR); + }); + call.on('end', callback); +} + +/** + * Run the recordRoute demo. Sends several randomly chosen points from the + * pre-generated feature database with a variable delay in between. Prints the + * statistics when they are sent from the server. + * @param {function} callback Called when this demo is complete + */ +function runRecordRoute(callback) { + var argv = parseArgs(process.argv, { + string: 'db_path' + }); + fs.readFile(path.resolve(argv.db_path), function(err, data) { + if (err) { + callback(err); + return; + } + var feature_list = JSON.parse(data); + + var num_points = 10; + var call = client.recordRoute(function(error, stats) { + if (error) { + callback(error); + return; + } + console.log('Finished trip with', stats.point_count, 'points'); + console.log('Passed', stats.feature_count, 'features'); + console.log('Travelled', stats.distance, 'meters'); + console.log('It took', stats.elapsed_time, 'seconds'); + callback(); + }); + /** + * Constructs a function that asynchronously sends the given point and then + * delays sending its callback + * @param {number} lat The latitude to send + * @param {number} lng The longitude to send + * @return {function(function)} The function that sends the point + */ + function pointSender(lat, lng) { + /** + * Sends the point, then calls the callback after a delay + * @param {function} callback Called when complete + */ + return function(callback) { + console.log('Visiting point ' + lat/COORD_FACTOR + ', ' + + lng/COORD_FACTOR); + call.write({ + latitude: lat, + longitude: lng + }); + _.delay(callback, _.random(500, 1500)); + }; + } + var point_senders = []; + for (var i = 0; i < num_points; i++) { + var rand_point = feature_list[_.random(0, feature_list.length - 1)]; + point_senders[i] = pointSender(rand_point.location.latitude, + rand_point.location.longitude); + } + async.series(point_senders, function() { + call.end(); + }); + }); +} + +/** + * Run the routeChat demo. Send some chat messages, and print any chat messages + * that are sent from the server. + * @param {function} callback Called when the demo is complete + */ +function runRouteChat(callback) { + var call = client.routeChat(); + call.on('data', function(note) { + console.log('Got message "' + note.message + '" at ' + + note.location.latitude + ', ' + note.location.longitude); + }); + + call.on('end', callback); + + var notes = [{ + location: { + latitude: 0, + longitude: 0 + }, + message: 'First message' + }, { + location: { + latitude: 0, + longitude: 1 + }, + message: 'Second message' + }, { + location: { + latitude: 1, + longitude: 0 + }, + message: 'Third message' + }, { + location: { + latitude: 0, + longitude: 0 + }, + message: 'Fourth message' + }]; + for (var i = 0; i < notes.length; i++) { + var note = notes[i]; + console.log('Sending message "' + note.message + '" at ' + + note.location.latitude + ', ' + note.location.longitude); + call.write(note); + } + call.end(); +} + +/** + * Run all of the demos in order + */ +function main() { + async.series([ + runGetFeature, + runListFeatures, + runRecordRoute, + runRouteChat + ]); +} + +if (require.main === module) { + main(); +} + +exports.runGetFeature = runGetFeature; + +exports.runListFeatures = runListFeatures; + +exports.runRecordRoute = runRecordRoute; + +exports.runRouteChat = runRouteChat; diff --git a/examples/routeguide/dynamic_codegen/route_guide_db.json b/examples/routeguide/dynamic_codegen/route_guide_db.json new file mode 100644 index 000000000..9d6a980ab --- /dev/null +++ b/examples/routeguide/dynamic_codegen/route_guide_db.json @@ -0,0 +1,601 @@ +[{ + "location": { + "latitude": 407838351, + "longitude": -746143763 + }, + "name": "Patriots Path, Mendham, NJ 07945, USA" +}, { + "location": { + "latitude": 408122808, + "longitude": -743999179 + }, + "name": "101 New Jersey 10, Whippany, NJ 07981, USA" +}, { + "location": { + "latitude": 413628156, + "longitude": -749015468 + }, + "name": "U.S. 6, Shohola, PA 18458, USA" +}, { + "location": { + "latitude": 419999544, + "longitude": -740371136 + }, + "name": "5 Conners Road, Kingston, NY 12401, USA" +}, { + "location": { + "latitude": 414008389, + "longitude": -743951297 + }, + "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA" +}, { + "location": { + "latitude": 419611318, + "longitude": -746524769 + }, + "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA" +}, { + "location": { + "latitude": 406109563, + "longitude": -742186778 + }, + "name": "4001 Tremley Point Road, Linden, NJ 07036, USA" +}, { + "location": { + "latitude": 416802456, + "longitude": -742370183 + }, + "name": "352 South Mountain Road, Wallkill, NY 12589, USA" +}, { + "location": { + "latitude": 412950425, + "longitude": -741077389 + }, + "name": "Bailey Turn Road, Harriman, NY 10926, USA" +}, { + "location": { + "latitude": 412144655, + "longitude": -743949739 + }, + "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA" +}, { + "location": { + "latitude": 415736605, + "longitude": -742847522 + }, + "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA" +}, { + "location": { + "latitude": 413843930, + "longitude": -740501726 + }, + "name": "162 Merrill Road, Highland Mills, NY 10930, USA" +}, { + "location": { + "latitude": 410873075, + "longitude": -744459023 + }, + "name": "Clinton Road, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 412346009, + "longitude": -744026814 + }, + "name": "16 Old Brook Lane, Warwick, NY 10990, USA" +}, { + "location": { + "latitude": 402948455, + "longitude": -747903913 + }, + "name": "3 Drake Lane, Pennington, NJ 08534, USA" +}, { + "location": { + "latitude": 406337092, + "longitude": -740122226 + }, + "name": "6324 8th Avenue, Brooklyn, NY 11220, USA" +}, { + "location": { + "latitude": 406421967, + "longitude": -747727624 + }, + "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA" +}, { + "location": { + "latitude": 416318082, + "longitude": -749677716 + }, + "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA" +}, { + "location": { + "latitude": 415301720, + "longitude": -748416257 + }, + "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA" +}, { + "location": { + "latitude": 402647019, + "longitude": -747071791 + }, + "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA" +}, { + "location": { + "latitude": 412567807, + "longitude": -741058078 + }, + "name": "New York State Reference Route 987E, Southfields, NY 10975, USA" +}, { + "location": { + "latitude": 416855156, + "longitude": -744420597 + }, + "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA" +}, { + "location": { + "latitude": 404663628, + "longitude": -744820157 + }, + "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA" +}, { + "location": { + "latitude": 407113723, + "longitude": -749746483 + }, + "name": "" +}, { + "location": { + "latitude": 402133926, + "longitude": -743613249 + }, + "name": "" +}, { + "location": { + "latitude": 400273442, + "longitude": -741220915 + }, + "name": "" +}, { + "location": { + "latitude": 411236786, + "longitude": -744070769 + }, + "name": "" +}, { + "location": { + "latitude": 411633782, + "longitude": -746784970 + }, + "name": "211-225 Plains Road, Augusta, NJ 07822, USA" +}, { + "location": { + "latitude": 415830701, + "longitude": -742952812 + }, + "name": "" +}, { + "location": { + "latitude": 413447164, + "longitude": -748712898 + }, + "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA" +}, { + "location": { + "latitude": 405047245, + "longitude": -749800722 + }, + "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA" +}, { + "location": { + "latitude": 418858923, + "longitude": -746156790 + }, + "name": "" +}, { + "location": { + "latitude": 417951888, + "longitude": -748484944 + }, + "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA" +}, { + "location": { + "latitude": 407033786, + "longitude": -743977337 + }, + "name": "26 East 3rd Street, New Providence, NJ 07974, USA" +}, { + "location": { + "latitude": 417548014, + "longitude": -740075041 + }, + "name": "" +}, { + "location": { + "latitude": 410395868, + "longitude": -744972325 + }, + "name": "" +}, { + "location": { + "latitude": 404615353, + "longitude": -745129803 + }, + "name": "" +}, { + "location": { + "latitude": 406589790, + "longitude": -743560121 + }, + "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA" +}, { + "location": { + "latitude": 414653148, + "longitude": -740477477 + }, + "name": "18 Lannis Avenue, New Windsor, NY 12553, USA" +}, { + "location": { + "latitude": 405957808, + "longitude": -743255336 + }, + "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA" +}, { + "location": { + "latitude": 411733589, + "longitude": -741648093 + }, + "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA" +}, { + "location": { + "latitude": 412676291, + "longitude": -742606606 + }, + "name": "1270 Lakes Road, Monroe, NY 10950, USA" +}, { + "location": { + "latitude": 409224445, + "longitude": -748286738 + }, + "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA" +}, { + "location": { + "latitude": 406523420, + "longitude": -742135517 + }, + "name": "652 Garden Street, Elizabeth, NJ 07202, USA" +}, { + "location": { + "latitude": 401827388, + "longitude": -740294537 + }, + "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA" +}, { + "location": { + "latitude": 410564152, + "longitude": -743685054 + }, + "name": "13-17 Stanley Street, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 408472324, + "longitude": -740726046 + }, + "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA" +}, { + "location": { + "latitude": 412452168, + "longitude": -740214052 + }, + "name": "5 White Oak Lane, Stony Point, NY 10980, USA" +}, { + "location": { + "latitude": 409146138, + "longitude": -746188906 + }, + "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" +}, { + "location": { + "latitude": 404701380, + "longitude": -744781745 + }, + "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 409642566, + "longitude": -746017679 + }, + "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA" +}, { + "location": { + "latitude": 408031728, + "longitude": -748645385 + }, + "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA" +}, { + "location": { + "latitude": 413700272, + "longitude": -742135189 + }, + "name": "367 Prospect Road, Chester, NY 10918, USA" +}, { + "location": { + "latitude": 404310607, + "longitude": -740282632 + }, + "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA" +}, { + "location": { + "latitude": 409319800, + "longitude": -746201391 + }, + "name": "11 Ward Street, Mount Arlington, NJ 07856, USA" +}, { + "location": { + "latitude": 406685311, + "longitude": -742108603 + }, + "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA" +}, { + "location": { + "latitude": 419018117, + "longitude": -749142781 + }, + "name": "43 Dreher Road, Roscoe, NY 12776, USA" +}, { + "location": { + "latitude": 412856162, + "longitude": -745148837 + }, + "name": "Swan Street, Pine Island, NY 10969, USA" +}, { + "location": { + "latitude": 416560744, + "longitude": -746721964 + }, + "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA" +}, { + "location": { + "latitude": 405314270, + "longitude": -749836354 + }, + "name": "" +}, { + "location": { + "latitude": 414219548, + "longitude": -743327440 + }, + "name": "" +}, { + "location": { + "latitude": 415534177, + "longitude": -742900616 + }, + "name": "565 Winding Hills Road, Montgomery, NY 12549, USA" +}, { + "location": { + "latitude": 406898530, + "longitude": -749127080 + }, + "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA" +}, { + "location": { + "latitude": 407586880, + "longitude": -741670168 + }, + "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA" +}, { + "location": { + "latitude": 400106455, + "longitude": -742870190 + }, + "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA" +}, { + "location": { + "latitude": 400066188, + "longitude": -746793294 + }, + "name": "" +}, { + "location": { + "latitude": 418803880, + "longitude": -744102673 + }, + "name": "40 Mountain Road, Napanoch, NY 12458, USA" +}, { + "location": { + "latitude": 414204288, + "longitude": -747895140 + }, + "name": "" +}, { + "location": { + "latitude": 414777405, + "longitude": -740615601 + }, + "name": "" +}, { + "location": { + "latitude": 415464475, + "longitude": -747175374 + }, + "name": "48 North Road, Forestburgh, NY 12777, USA" +}, { + "location": { + "latitude": 404062378, + "longitude": -746376177 + }, + "name": "" +}, { + "location": { + "latitude": 405688272, + "longitude": -749285130 + }, + "name": "" +}, { + "location": { + "latitude": 400342070, + "longitude": -748788996 + }, + "name": "" +}, { + "location": { + "latitude": 401809022, + "longitude": -744157964 + }, + "name": "" +}, { + "location": { + "latitude": 404226644, + "longitude": -740517141 + }, + "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA" +}, { + "location": { + "latitude": 410322033, + "longitude": -747871659 + }, + "name": "" +}, { + "location": { + "latitude": 407100674, + "longitude": -747742727 + }, + "name": "" +}, { + "location": { + "latitude": 418811433, + "longitude": -741718005 + }, + "name": "213 Bush Road, Stone Ridge, NY 12484, USA" +}, { + "location": { + "latitude": 415034302, + "longitude": -743850945 + }, + "name": "" +}, { + "location": { + "latitude": 411349992, + "longitude": -743694161 + }, + "name": "" +}, { + "location": { + "latitude": 404839914, + "longitude": -744759616 + }, + "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 414638017, + "longitude": -745957854 + }, + "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA" +}, { + "location": { + "latitude": 412127800, + "longitude": -740173578 + }, + "name": "" +}, { + "location": { + "latitude": 401263460, + "longitude": -747964303 + }, + "name": "" +}, { + "location": { + "latitude": 412843391, + "longitude": -749086026 + }, + "name": "" +}, { + "location": { + "latitude": 418512773, + "longitude": -743067823 + }, + "name": "" +}, { + "location": { + "latitude": 404318328, + "longitude": -740835638 + }, + "name": "42-102 Main Street, Belford, NJ 07718, USA" +}, { + "location": { + "latitude": 419020746, + "longitude": -741172328 + }, + "name": "" +}, { + "location": { + "latitude": 404080723, + "longitude": -746119569 + }, + "name": "" +}, { + "location": { + "latitude": 401012643, + "longitude": -744035134 + }, + "name": "" +}, { + "location": { + "latitude": 404306372, + "longitude": -741079661 + }, + "name": "" +}, { + "location": { + "latitude": 403966326, + "longitude": -748519297 + }, + "name": "" +}, { + "location": { + "latitude": 405002031, + "longitude": -748407866 + }, + "name": "" +}, { + "location": { + "latitude": 409532885, + "longitude": -742200683 + }, + "name": "" +}, { + "location": { + "latitude": 416851321, + "longitude": -742674555 + }, + "name": "" +}, { + "location": { + "latitude": 406411633, + "longitude": -741722051 + }, + "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA" +}, { + "location": { + "latitude": 413069058, + "longitude": -744597778 + }, + "name": "261 Van Sickle Road, Goshen, NY 10924, USA" +}, { + "location": { + "latitude": 418465462, + "longitude": -746859398 + }, + "name": "" +}, { + "location": { + "latitude": 411733222, + "longitude": -744228360 + }, + "name": "" +}, { + "location": { + "latitude": 410248224, + "longitude": -747127767 + }, + "name": "3 Hasta Way, Newton, NJ 07860, USA" +}] diff --git a/examples/routeguide/dynamic_codegen/route_guide_server.js b/examples/routeguide/dynamic_codegen/route_guide_server.js new file mode 100644 index 000000000..a303b825b --- /dev/null +++ b/examples/routeguide/dynamic_codegen/route_guide_server.js @@ -0,0 +1,245 @@ +/* + * + * Copyright 2015 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. + * + */ + +var PROTO_PATH = __dirname + '/../../protos/route_guide.proto'; + +var fs = require('fs'); +var parseArgs = require('minimist'); +var path = require('path'); +var _ = require('lodash'); +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var routeguide = grpc.loadPackageDefinition(packageDefinition).routeguide; + +var COORD_FACTOR = 1e7; + +/** + * For simplicity, a point is a record type that looks like + * {latitude: number, longitude: number}, and a feature is a record type that + * looks like {name: string, location: point}. feature objects with name==='' + * are points with no feature. + */ + +/** + * List of feature objects at points that have been requested so far. + */ +var feature_list = []; + +/** + * Get a feature object at the given point, or creates one if it does not exist. + * @param {point} point The point to check + * @return {feature} The feature object at the point. Note that an empty name + * indicates no feature + */ +function checkFeature(point) { + var feature; + // Check if there is already a feature object for the given point + for (var i = 0; i < feature_list.length; i++) { + feature = feature_list[i]; + if (feature.location.latitude === point.latitude && + feature.location.longitude === point.longitude) { + return feature; + } + } + var name = ''; + feature = { + name: name, + location: point + }; + return feature; +} + +/** + * getFeature request handler. Gets a request with a point, and responds with a + * feature object indicating whether there is a feature at that point. + * @param {EventEmitter} call Call object for the handler to process + * @param {function(Error, feature)} callback Response callback + */ +function getFeature(call, callback) { + callback(null, checkFeature(call.request)); +} + +/** + * listFeatures request handler. Gets a request with two points, and responds + * with a stream of all features in the bounding box defined by those points. + * @param {Writable} call Writable stream for responses with an additional + * request property for the request value. + */ +function listFeatures(call) { + var lo = call.request.lo; + var hi = call.request.hi; + var left = _.min([lo.longitude, hi.longitude]); + var right = _.max([lo.longitude, hi.longitude]); + var top = _.max([lo.latitude, hi.latitude]); + var bottom = _.min([lo.latitude, hi.latitude]); + // For each feature, check if it is in the given bounding box + _.each(feature_list, function(feature) { + if (feature.name === '') { + return; + } + if (feature.location.longitude >= left && + feature.location.longitude <= right && + feature.location.latitude >= bottom && + feature.location.latitude <= top) { + call.write(feature); + } + }); + call.end(); +} + +/** + * Calculate the distance between two points using the "haversine" formula. + * The formula is based on http://mathforum.org/library/drmath/view/51879.html. + * @param start The starting point + * @param end The end point + * @return The distance between the points in meters + */ +function getDistance(start, end) { + function toRadians(num) { + return num * Math.PI / 180; + } + var R = 6371000; // earth radius in metres + var lat1 = toRadians(start.latitude / COORD_FACTOR); + var lat2 = toRadians(end.latitude / COORD_FACTOR); + var lon1 = toRadians(start.longitude / COORD_FACTOR); + var lon2 = toRadians(end.longitude / COORD_FACTOR); + + var deltalat = lat2-lat1; + var deltalon = lon2-lon1; + var a = Math.sin(deltalat/2) * Math.sin(deltalat/2) + + Math.cos(lat1) * Math.cos(lat2) * + Math.sin(deltalon/2) * Math.sin(deltalon/2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + return R * c; +} + +/** + * recordRoute handler. Gets a stream of points, and responds with statistics + * about the "trip": number of points, number of known features visited, total + * distance traveled, and total time spent. + * @param {Readable} call The request point stream. + * @param {function(Error, routeSummary)} callback The callback to pass the + * response to + */ +function recordRoute(call, callback) { + var point_count = 0; + var feature_count = 0; + var distance = 0; + var previous = null; + // Start a timer + var start_time = process.hrtime(); + call.on('data', function(point) { + point_count += 1; + if (checkFeature(point).name !== '') { + feature_count += 1; + } + /* For each point after the first, add the incremental distance from the + * previous point to the total distance value */ + if (previous != null) { + distance += getDistance(previous, point); + } + previous = point; + }); + call.on('end', function() { + callback(null, { + point_count: point_count, + feature_count: feature_count, + // Cast the distance to an integer + distance: distance|0, + // End the timer + elapsed_time: process.hrtime(start_time)[0] + }); + }); +} + +var route_notes = {}; + +/** + * Turn the point into a dictionary key. + * @param {point} point The point to use + * @return {string} The key for an object + */ +function pointKey(point) { + return point.latitude + ' ' + point.longitude; +} + +/** + * routeChat handler. Receives a stream of message/location pairs, and responds + * with a stream of all previous messages at each of those locations. + * @param {Duplex} call The stream for incoming and outgoing messages + */ +function routeChat(call) { + call.on('data', function(note) { + var key = pointKey(note.location); + /* For each note sent, respond with all previous notes that correspond to + * the same point */ + if (route_notes.hasOwnProperty(key)) { + _.each(route_notes[key], function(note) { + call.write(note); + }); + } else { + route_notes[key] = []; + } + // Then add the new note to the list + route_notes[key].push(JSON.parse(JSON.stringify(note))); + }); + call.on('end', function() { + call.end(); + }); +} + +/** + * Get a new server with the handler functions in this file bound to the methods + * it serves. + * @return {Server} The new server object + */ +function getServer() { + var server = new grpc.Server(); + server.addService(routeguide.RouteGuide.service, { + getFeature: getFeature, + listFeatures: listFeatures, + recordRoute: recordRoute, + routeChat: routeChat + }); + return server; +} + +if (require.main === module) { + // If this is run as a script, start a server on an unused port + var routeServer = getServer(); + routeServer.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { + var argv = parseArgs(process.argv, { + string: 'db_path' + }); + fs.readFile(path.resolve(argv.db_path), function(err, data) { + if (err) throw err; + feature_list = JSON.parse(data); + routeServer.start(); + }); + }); +} + +exports.getServer = getServer; diff --git a/examples/routeguide/static_codegen/README.md b/examples/routeguide/static_codegen/README.md new file mode 100644 index 000000000..f154f7e46 --- /dev/null +++ b/examples/routeguide/static_codegen/README.md @@ -0,0 +1,7 @@ +This is the static code generation variant of the Route Guide example. Code in these examples is pre-generated using protoc and the Node gRPC protoc plugin, and the generated code can be found in various `*_pb.js` files. The command line sequence for generating those files is as follows (assuming that `protoc` and `grpc_node_plugin` are present, and starting in the directory which contains this README.md file): + +```sh +cd ../protos +npm install -g grpc-tools +grpc_tools_node_protoc --js_out=import_style=commonjs,binary:../routeguide/static_codegen/ --grpc_out=grpc_js:../routeguide/static_codegen/ route_guide.proto +``` diff --git a/examples/routeguide/static_codegen/route_guide_client.js b/examples/routeguide/static_codegen/route_guide_client.js new file mode 100644 index 000000000..0ab40a8a5 --- /dev/null +++ b/examples/routeguide/static_codegen/route_guide_client.js @@ -0,0 +1,237 @@ +/* + * + * Copyright 2015 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. + * + */ + +var messages = require('./route_guide_pb'); +var services = require('./route_guide_grpc_pb'); + +var async = require('async'); +var fs = require('fs'); +var parseArgs = require('minimist'); +var path = require('path'); +var _ = require('lodash'); +var grpc = require('@grpc/grpc-js'); + +var client = new services.RouteGuideClient('localhost:50051', + grpc.credentials.createInsecure()); + +var COORD_FACTOR = 1e7; + +/** + * Run the getFeature demo. Calls getFeature with a point known to have a + * feature and a point known not to have a feature. + * @param {function} callback Called when this demo is complete + */ +function runGetFeature(callback) { + var next = _.after(2, callback); + function featureCallback(error, feature) { + if (error) { + callback(error); + return; + } + var latitude = feature.getLocation().getLatitude(); + var longitude = feature.getLocation().getLongitude(); + if (feature.getName() === '') { + console.log('Found no feature at ' + + latitude/COORD_FACTOR + ', ' + longitude/COORD_FACTOR); + } else { + console.log('Found feature called "' + feature.getName() + '" at ' + + latitude/COORD_FACTOR + ', ' + longitude/COORD_FACTOR); + } + next(); + } + var point1 = new messages.Point(); + point1.setLatitude(409146138); + point1.setLongitude(-746188906); + var point2 = new messages.Point(); + point2.setLatitude(0); + point2.setLongitude(0); + client.getFeature(point1, featureCallback); + client.getFeature(point2, featureCallback); +} + +/** + * Run the listFeatures demo. Calls listFeatures with a rectangle containing all + * of the features in the pre-generated database. Prints each response as it + * comes in. + * @param {function} callback Called when this demo is complete + */ +function runListFeatures(callback) { + var rect = new messages.Rectangle(); + var lo = new messages.Point(); + lo.setLatitude(400000000); + lo.setLongitude(-750000000); + rect.setLo(lo); + var hi = new messages.Point(); + hi.setLatitude(420000000); + hi.setLongitude(-730000000); + rect.setHi(hi); + console.log('Looking for features between 40, -75 and 42, -73'); + var call = client.listFeatures(rect); + call.on('data', function(feature) { + console.log('Found feature called "' + feature.getName() + '" at ' + + feature.getLocation().getLatitude()/COORD_FACTOR + ', ' + + feature.getLocation().getLongitude()/COORD_FACTOR); + }); + call.on('end', callback); +} + +/** + * Run the recordRoute demo. Sends several randomly chosen points from the + * pre-generated feature database with a variable delay in between. Prints the + * statistics when they are sent from the server. + * @param {function} callback Called when this demo is complete + */ +function runRecordRoute(callback) { + var argv = parseArgs(process.argv, { + string: 'db_path' + }); + fs.readFile(path.resolve(argv.db_path), function(err, data) { + if (err) { + callback(err); + return; + } + // Transform the loaded features to Feature objects + var feature_list = _.map(JSON.parse(data), function(value) { + var feature = new messages.Feature(); + feature.setName(value.name); + var location = new messages.Point(); + location.setLatitude(value.location.latitude); + location.setLongitude(value.location.longitude); + feature.setLocation(location); + return feature; + }); + + var num_points = 10; + var call = client.recordRoute(function(error, stats) { + if (error) { + callback(error); + return; + } + console.log('Finished trip with', stats.getPointCount(), 'points'); + console.log('Passed', stats.getFeatureCount(), 'features'); + console.log('Travelled', stats.getDistance(), 'meters'); + console.log('It took', stats.getElapsedTime(), 'seconds'); + callback(); + }); + /** + * Constructs a function that asynchronously sends the given point and then + * delays sending its callback + * @param {messages.Point} location The point to send + * @return {function(function)} The function that sends the point + */ + function pointSender(location) { + /** + * Sends the point, then calls the callback after a delay + * @param {function} callback Called when complete + */ + return function(callback) { + console.log('Visiting point ' + location.getLatitude()/COORD_FACTOR + + ', ' + location.getLongitude()/COORD_FACTOR); + call.write(location); + _.delay(callback, _.random(500, 1500)); + }; + } + var point_senders = []; + for (var i = 0; i < num_points; i++) { + var rand_point = feature_list[_.random(0, feature_list.length - 1)]; + point_senders[i] = pointSender(rand_point.getLocation()); + } + async.series(point_senders, function() { + call.end(); + }); + }); +} + +/** + * Run the routeChat demo. Send some chat messages, and print any chat messages + * that are sent from the server. + * @param {function} callback Called when the demo is complete + */ +function runRouteChat(callback) { + var call = client.routeChat(); + call.on('data', function(note) { + console.log('Got message "' + note.getMessage() + '" at ' + + note.getLocation().getLatitude() + ', ' + + note.getLocation().getLongitude()); + }); + + call.on('end', callback); + + var notes = [{ + location: { + latitude: 0, + longitude: 0 + }, + message: 'First message' + }, { + location: { + latitude: 0, + longitude: 1 + }, + message: 'Second message' + }, { + location: { + latitude: 1, + longitude: 0 + }, + message: 'Third message' + }, { + location: { + latitude: 0, + longitude: 0 + }, + message: 'Fourth message' + }]; + for (var i = 0; i < notes.length; i++) { + var note = notes[i]; + console.log('Sending message "' + note.message + '" at ' + + note.location.latitude + ', ' + note.location.longitude); + var noteMsg = new messages.RouteNote(); + noteMsg.setMessage(note.message); + var location = new messages.Point(); + location.setLatitude(note.location.latitude); + location.setLongitude(note.location.longitude); + noteMsg.setLocation(location); + call.write(noteMsg); + } + call.end(); +} + +/** + * Run all of the demos in order + */ +function main() { + async.series([ + runGetFeature, + runListFeatures, + runRecordRoute, + runRouteChat + ]); +} + +if (require.main === module) { + main(); +} + +exports.runGetFeature = runGetFeature; + +exports.runListFeatures = runListFeatures; + +exports.runRecordRoute = runRecordRoute; + +exports.runRouteChat = runRouteChat; diff --git a/examples/routeguide/static_codegen/route_guide_db.json b/examples/routeguide/static_codegen/route_guide_db.json new file mode 100644 index 000000000..9d6a980ab --- /dev/null +++ b/examples/routeguide/static_codegen/route_guide_db.json @@ -0,0 +1,601 @@ +[{ + "location": { + "latitude": 407838351, + "longitude": -746143763 + }, + "name": "Patriots Path, Mendham, NJ 07945, USA" +}, { + "location": { + "latitude": 408122808, + "longitude": -743999179 + }, + "name": "101 New Jersey 10, Whippany, NJ 07981, USA" +}, { + "location": { + "latitude": 413628156, + "longitude": -749015468 + }, + "name": "U.S. 6, Shohola, PA 18458, USA" +}, { + "location": { + "latitude": 419999544, + "longitude": -740371136 + }, + "name": "5 Conners Road, Kingston, NY 12401, USA" +}, { + "location": { + "latitude": 414008389, + "longitude": -743951297 + }, + "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA" +}, { + "location": { + "latitude": 419611318, + "longitude": -746524769 + }, + "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA" +}, { + "location": { + "latitude": 406109563, + "longitude": -742186778 + }, + "name": "4001 Tremley Point Road, Linden, NJ 07036, USA" +}, { + "location": { + "latitude": 416802456, + "longitude": -742370183 + }, + "name": "352 South Mountain Road, Wallkill, NY 12589, USA" +}, { + "location": { + "latitude": 412950425, + "longitude": -741077389 + }, + "name": "Bailey Turn Road, Harriman, NY 10926, USA" +}, { + "location": { + "latitude": 412144655, + "longitude": -743949739 + }, + "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA" +}, { + "location": { + "latitude": 415736605, + "longitude": -742847522 + }, + "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA" +}, { + "location": { + "latitude": 413843930, + "longitude": -740501726 + }, + "name": "162 Merrill Road, Highland Mills, NY 10930, USA" +}, { + "location": { + "latitude": 410873075, + "longitude": -744459023 + }, + "name": "Clinton Road, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 412346009, + "longitude": -744026814 + }, + "name": "16 Old Brook Lane, Warwick, NY 10990, USA" +}, { + "location": { + "latitude": 402948455, + "longitude": -747903913 + }, + "name": "3 Drake Lane, Pennington, NJ 08534, USA" +}, { + "location": { + "latitude": 406337092, + "longitude": -740122226 + }, + "name": "6324 8th Avenue, Brooklyn, NY 11220, USA" +}, { + "location": { + "latitude": 406421967, + "longitude": -747727624 + }, + "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA" +}, { + "location": { + "latitude": 416318082, + "longitude": -749677716 + }, + "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA" +}, { + "location": { + "latitude": 415301720, + "longitude": -748416257 + }, + "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA" +}, { + "location": { + "latitude": 402647019, + "longitude": -747071791 + }, + "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA" +}, { + "location": { + "latitude": 412567807, + "longitude": -741058078 + }, + "name": "New York State Reference Route 987E, Southfields, NY 10975, USA" +}, { + "location": { + "latitude": 416855156, + "longitude": -744420597 + }, + "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA" +}, { + "location": { + "latitude": 404663628, + "longitude": -744820157 + }, + "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA" +}, { + "location": { + "latitude": 407113723, + "longitude": -749746483 + }, + "name": "" +}, { + "location": { + "latitude": 402133926, + "longitude": -743613249 + }, + "name": "" +}, { + "location": { + "latitude": 400273442, + "longitude": -741220915 + }, + "name": "" +}, { + "location": { + "latitude": 411236786, + "longitude": -744070769 + }, + "name": "" +}, { + "location": { + "latitude": 411633782, + "longitude": -746784970 + }, + "name": "211-225 Plains Road, Augusta, NJ 07822, USA" +}, { + "location": { + "latitude": 415830701, + "longitude": -742952812 + }, + "name": "" +}, { + "location": { + "latitude": 413447164, + "longitude": -748712898 + }, + "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA" +}, { + "location": { + "latitude": 405047245, + "longitude": -749800722 + }, + "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA" +}, { + "location": { + "latitude": 418858923, + "longitude": -746156790 + }, + "name": "" +}, { + "location": { + "latitude": 417951888, + "longitude": -748484944 + }, + "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA" +}, { + "location": { + "latitude": 407033786, + "longitude": -743977337 + }, + "name": "26 East 3rd Street, New Providence, NJ 07974, USA" +}, { + "location": { + "latitude": 417548014, + "longitude": -740075041 + }, + "name": "" +}, { + "location": { + "latitude": 410395868, + "longitude": -744972325 + }, + "name": "" +}, { + "location": { + "latitude": 404615353, + "longitude": -745129803 + }, + "name": "" +}, { + "location": { + "latitude": 406589790, + "longitude": -743560121 + }, + "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA" +}, { + "location": { + "latitude": 414653148, + "longitude": -740477477 + }, + "name": "18 Lannis Avenue, New Windsor, NY 12553, USA" +}, { + "location": { + "latitude": 405957808, + "longitude": -743255336 + }, + "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA" +}, { + "location": { + "latitude": 411733589, + "longitude": -741648093 + }, + "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA" +}, { + "location": { + "latitude": 412676291, + "longitude": -742606606 + }, + "name": "1270 Lakes Road, Monroe, NY 10950, USA" +}, { + "location": { + "latitude": 409224445, + "longitude": -748286738 + }, + "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA" +}, { + "location": { + "latitude": 406523420, + "longitude": -742135517 + }, + "name": "652 Garden Street, Elizabeth, NJ 07202, USA" +}, { + "location": { + "latitude": 401827388, + "longitude": -740294537 + }, + "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA" +}, { + "location": { + "latitude": 410564152, + "longitude": -743685054 + }, + "name": "13-17 Stanley Street, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 408472324, + "longitude": -740726046 + }, + "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA" +}, { + "location": { + "latitude": 412452168, + "longitude": -740214052 + }, + "name": "5 White Oak Lane, Stony Point, NY 10980, USA" +}, { + "location": { + "latitude": 409146138, + "longitude": -746188906 + }, + "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" +}, { + "location": { + "latitude": 404701380, + "longitude": -744781745 + }, + "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 409642566, + "longitude": -746017679 + }, + "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA" +}, { + "location": { + "latitude": 408031728, + "longitude": -748645385 + }, + "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA" +}, { + "location": { + "latitude": 413700272, + "longitude": -742135189 + }, + "name": "367 Prospect Road, Chester, NY 10918, USA" +}, { + "location": { + "latitude": 404310607, + "longitude": -740282632 + }, + "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA" +}, { + "location": { + "latitude": 409319800, + "longitude": -746201391 + }, + "name": "11 Ward Street, Mount Arlington, NJ 07856, USA" +}, { + "location": { + "latitude": 406685311, + "longitude": -742108603 + }, + "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA" +}, { + "location": { + "latitude": 419018117, + "longitude": -749142781 + }, + "name": "43 Dreher Road, Roscoe, NY 12776, USA" +}, { + "location": { + "latitude": 412856162, + "longitude": -745148837 + }, + "name": "Swan Street, Pine Island, NY 10969, USA" +}, { + "location": { + "latitude": 416560744, + "longitude": -746721964 + }, + "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA" +}, { + "location": { + "latitude": 405314270, + "longitude": -749836354 + }, + "name": "" +}, { + "location": { + "latitude": 414219548, + "longitude": -743327440 + }, + "name": "" +}, { + "location": { + "latitude": 415534177, + "longitude": -742900616 + }, + "name": "565 Winding Hills Road, Montgomery, NY 12549, USA" +}, { + "location": { + "latitude": 406898530, + "longitude": -749127080 + }, + "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA" +}, { + "location": { + "latitude": 407586880, + "longitude": -741670168 + }, + "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA" +}, { + "location": { + "latitude": 400106455, + "longitude": -742870190 + }, + "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA" +}, { + "location": { + "latitude": 400066188, + "longitude": -746793294 + }, + "name": "" +}, { + "location": { + "latitude": 418803880, + "longitude": -744102673 + }, + "name": "40 Mountain Road, Napanoch, NY 12458, USA" +}, { + "location": { + "latitude": 414204288, + "longitude": -747895140 + }, + "name": "" +}, { + "location": { + "latitude": 414777405, + "longitude": -740615601 + }, + "name": "" +}, { + "location": { + "latitude": 415464475, + "longitude": -747175374 + }, + "name": "48 North Road, Forestburgh, NY 12777, USA" +}, { + "location": { + "latitude": 404062378, + "longitude": -746376177 + }, + "name": "" +}, { + "location": { + "latitude": 405688272, + "longitude": -749285130 + }, + "name": "" +}, { + "location": { + "latitude": 400342070, + "longitude": -748788996 + }, + "name": "" +}, { + "location": { + "latitude": 401809022, + "longitude": -744157964 + }, + "name": "" +}, { + "location": { + "latitude": 404226644, + "longitude": -740517141 + }, + "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA" +}, { + "location": { + "latitude": 410322033, + "longitude": -747871659 + }, + "name": "" +}, { + "location": { + "latitude": 407100674, + "longitude": -747742727 + }, + "name": "" +}, { + "location": { + "latitude": 418811433, + "longitude": -741718005 + }, + "name": "213 Bush Road, Stone Ridge, NY 12484, USA" +}, { + "location": { + "latitude": 415034302, + "longitude": -743850945 + }, + "name": "" +}, { + "location": { + "latitude": 411349992, + "longitude": -743694161 + }, + "name": "" +}, { + "location": { + "latitude": 404839914, + "longitude": -744759616 + }, + "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 414638017, + "longitude": -745957854 + }, + "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA" +}, { + "location": { + "latitude": 412127800, + "longitude": -740173578 + }, + "name": "" +}, { + "location": { + "latitude": 401263460, + "longitude": -747964303 + }, + "name": "" +}, { + "location": { + "latitude": 412843391, + "longitude": -749086026 + }, + "name": "" +}, { + "location": { + "latitude": 418512773, + "longitude": -743067823 + }, + "name": "" +}, { + "location": { + "latitude": 404318328, + "longitude": -740835638 + }, + "name": "42-102 Main Street, Belford, NJ 07718, USA" +}, { + "location": { + "latitude": 419020746, + "longitude": -741172328 + }, + "name": "" +}, { + "location": { + "latitude": 404080723, + "longitude": -746119569 + }, + "name": "" +}, { + "location": { + "latitude": 401012643, + "longitude": -744035134 + }, + "name": "" +}, { + "location": { + "latitude": 404306372, + "longitude": -741079661 + }, + "name": "" +}, { + "location": { + "latitude": 403966326, + "longitude": -748519297 + }, + "name": "" +}, { + "location": { + "latitude": 405002031, + "longitude": -748407866 + }, + "name": "" +}, { + "location": { + "latitude": 409532885, + "longitude": -742200683 + }, + "name": "" +}, { + "location": { + "latitude": 416851321, + "longitude": -742674555 + }, + "name": "" +}, { + "location": { + "latitude": 406411633, + "longitude": -741722051 + }, + "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA" +}, { + "location": { + "latitude": 413069058, + "longitude": -744597778 + }, + "name": "261 Van Sickle Road, Goshen, NY 10924, USA" +}, { + "location": { + "latitude": 418465462, + "longitude": -746859398 + }, + "name": "" +}, { + "location": { + "latitude": 411733222, + "longitude": -744228360 + }, + "name": "" +}, { + "location": { + "latitude": 410248224, + "longitude": -747127767 + }, + "name": "3 Hasta Way, Newton, NJ 07860, USA" +}] diff --git a/examples/routeguide/static_codegen/route_guide_grpc_pb.js b/examples/routeguide/static_codegen/route_guide_grpc_pb.js new file mode 100644 index 000000000..83c839dc8 --- /dev/null +++ b/examples/routeguide/static_codegen/route_guide_grpc_pb.js @@ -0,0 +1,146 @@ +// GENERATED CODE -- DO NOT EDIT! + +// Original file comments: +// Copyright 2015 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. +// +'use strict'; +var grpc = require('@grpc/grpc-js'); +var route_guide_pb = require('./route_guide_pb.js'); + +function serialize_routeguide_Feature(arg) { + if (!(arg instanceof route_guide_pb.Feature)) { + throw new Error('Expected argument of type routeguide.Feature'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_routeguide_Feature(buffer_arg) { + return route_guide_pb.Feature.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_routeguide_Point(arg) { + if (!(arg instanceof route_guide_pb.Point)) { + throw new Error('Expected argument of type routeguide.Point'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_routeguide_Point(buffer_arg) { + return route_guide_pb.Point.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_routeguide_Rectangle(arg) { + if (!(arg instanceof route_guide_pb.Rectangle)) { + throw new Error('Expected argument of type routeguide.Rectangle'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_routeguide_Rectangle(buffer_arg) { + return route_guide_pb.Rectangle.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_routeguide_RouteNote(arg) { + if (!(arg instanceof route_guide_pb.RouteNote)) { + throw new Error('Expected argument of type routeguide.RouteNote'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_routeguide_RouteNote(buffer_arg) { + return route_guide_pb.RouteNote.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_routeguide_RouteSummary(arg) { + if (!(arg instanceof route_guide_pb.RouteSummary)) { + throw new Error('Expected argument of type routeguide.RouteSummary'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_routeguide_RouteSummary(buffer_arg) { + return route_guide_pb.RouteSummary.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +// Interface exported by the server. +var RouteGuideService = exports.RouteGuideService = { + // A simple RPC. +// +// Obtains the feature at a given position. +// +// A feature with an empty name is returned if there's no feature at the given +// position. +getFeature: { + path: '/routeguide.RouteGuide/GetFeature', + requestStream: false, + responseStream: false, + requestType: route_guide_pb.Point, + responseType: route_guide_pb.Feature, + requestSerialize: serialize_routeguide_Point, + requestDeserialize: deserialize_routeguide_Point, + responseSerialize: serialize_routeguide_Feature, + responseDeserialize: deserialize_routeguide_Feature, + }, + // A server-to-client streaming RPC. +// +// Obtains the Features available within the given Rectangle. Results are +// streamed rather than returned at once (e.g. in a response message with a +// repeated field), as the rectangle may cover a large area and contain a +// huge number of features. +listFeatures: { + path: '/routeguide.RouteGuide/ListFeatures', + requestStream: false, + responseStream: true, + requestType: route_guide_pb.Rectangle, + responseType: route_guide_pb.Feature, + requestSerialize: serialize_routeguide_Rectangle, + requestDeserialize: deserialize_routeguide_Rectangle, + responseSerialize: serialize_routeguide_Feature, + responseDeserialize: deserialize_routeguide_Feature, + }, + // A client-to-server streaming RPC. +// +// Accepts a stream of Points on a route being traversed, returning a +// RouteSummary when traversal is completed. +recordRoute: { + path: '/routeguide.RouteGuide/RecordRoute', + requestStream: true, + responseStream: false, + requestType: route_guide_pb.Point, + responseType: route_guide_pb.RouteSummary, + requestSerialize: serialize_routeguide_Point, + requestDeserialize: deserialize_routeguide_Point, + responseSerialize: serialize_routeguide_RouteSummary, + responseDeserialize: deserialize_routeguide_RouteSummary, + }, + // A Bidirectional streaming RPC. +// +// Accepts a stream of RouteNotes sent while a route is being traversed, +// while receiving other RouteNotes (e.g. from other users). +routeChat: { + path: '/routeguide.RouteGuide/RouteChat', + requestStream: true, + responseStream: true, + requestType: route_guide_pb.RouteNote, + responseType: route_guide_pb.RouteNote, + requestSerialize: serialize_routeguide_RouteNote, + requestDeserialize: deserialize_routeguide_RouteNote, + responseSerialize: serialize_routeguide_RouteNote, + responseDeserialize: deserialize_routeguide_RouteNote, + }, +}; + +exports.RouteGuideClient = grpc.makeGenericClientConstructor(RouteGuideService); diff --git a/examples/routeguide/static_codegen/route_guide_pb.js b/examples/routeguide/static_codegen/route_guide_pb.js new file mode 100644 index 000000000..a032bec4c --- /dev/null +++ b/examples/routeguide/static_codegen/route_guide_pb.js @@ -0,0 +1,1069 @@ +// source: route_guide.proto +/** + * @fileoverview + * @enhanceable + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = Function('return this')(); + +goog.exportSymbol('proto.routeguide.Feature', null, global); +goog.exportSymbol('proto.routeguide.Point', null, global); +goog.exportSymbol('proto.routeguide.Rectangle', null, global); +goog.exportSymbol('proto.routeguide.RouteNote', null, global); +goog.exportSymbol('proto.routeguide.RouteSummary', null, global); +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.routeguide.Point = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.routeguide.Point, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.routeguide.Point.displayName = 'proto.routeguide.Point'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.routeguide.Rectangle = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.routeguide.Rectangle, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.routeguide.Rectangle.displayName = 'proto.routeguide.Rectangle'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.routeguide.Feature = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.routeguide.Feature, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.routeguide.Feature.displayName = 'proto.routeguide.Feature'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.routeguide.RouteNote = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.routeguide.RouteNote, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.routeguide.RouteNote.displayName = 'proto.routeguide.RouteNote'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.routeguide.RouteSummary = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.routeguide.RouteSummary, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.routeguide.RouteSummary.displayName = 'proto.routeguide.RouteSummary'; +} + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.routeguide.Point.prototype.toObject = function(opt_includeInstance) { + return proto.routeguide.Point.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.routeguide.Point} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.Point.toObject = function(includeInstance, msg) { + var f, obj = { + latitude: jspb.Message.getFieldWithDefault(msg, 1, 0), + longitude: jspb.Message.getFieldWithDefault(msg, 2, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.routeguide.Point} + */ +proto.routeguide.Point.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.routeguide.Point; + return proto.routeguide.Point.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.routeguide.Point} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.routeguide.Point} + */ +proto.routeguide.Point.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readInt32()); + msg.setLatitude(value); + break; + case 2: + var value = /** @type {number} */ (reader.readInt32()); + msg.setLongitude(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.routeguide.Point.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.routeguide.Point.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.routeguide.Point} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.Point.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getLatitude(); + if (f !== 0) { + writer.writeInt32( + 1, + f + ); + } + f = message.getLongitude(); + if (f !== 0) { + writer.writeInt32( + 2, + f + ); + } +}; + + +/** + * optional int32 latitude = 1; + * @return {number} + */ +proto.routeguide.Point.prototype.getLatitude = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.routeguide.Point} returns this + */ +proto.routeguide.Point.prototype.setLatitude = function(value) { + return jspb.Message.setProto3IntField(this, 1, value); +}; + + +/** + * optional int32 longitude = 2; + * @return {number} + */ +proto.routeguide.Point.prototype.getLongitude = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.routeguide.Point} returns this + */ +proto.routeguide.Point.prototype.setLongitude = function(value) { + return jspb.Message.setProto3IntField(this, 2, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.routeguide.Rectangle.prototype.toObject = function(opt_includeInstance) { + return proto.routeguide.Rectangle.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.routeguide.Rectangle} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.Rectangle.toObject = function(includeInstance, msg) { + var f, obj = { + lo: (f = msg.getLo()) && proto.routeguide.Point.toObject(includeInstance, f), + hi: (f = msg.getHi()) && proto.routeguide.Point.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.routeguide.Rectangle} + */ +proto.routeguide.Rectangle.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.routeguide.Rectangle; + return proto.routeguide.Rectangle.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.routeguide.Rectangle} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.routeguide.Rectangle} + */ +proto.routeguide.Rectangle.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.routeguide.Point; + reader.readMessage(value,proto.routeguide.Point.deserializeBinaryFromReader); + msg.setLo(value); + break; + case 2: + var value = new proto.routeguide.Point; + reader.readMessage(value,proto.routeguide.Point.deserializeBinaryFromReader); + msg.setHi(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.routeguide.Rectangle.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.routeguide.Rectangle.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.routeguide.Rectangle} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.Rectangle.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getLo(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.routeguide.Point.serializeBinaryToWriter + ); + } + f = message.getHi(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.routeguide.Point.serializeBinaryToWriter + ); + } +}; + + +/** + * optional Point lo = 1; + * @return {?proto.routeguide.Point} + */ +proto.routeguide.Rectangle.prototype.getLo = function() { + return /** @type{?proto.routeguide.Point} */ ( + jspb.Message.getWrapperField(this, proto.routeguide.Point, 1)); +}; + + +/** + * @param {?proto.routeguide.Point|undefined} value + * @return {!proto.routeguide.Rectangle} returns this +*/ +proto.routeguide.Rectangle.prototype.setLo = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.routeguide.Rectangle} returns this + */ +proto.routeguide.Rectangle.prototype.clearLo = function() { + return this.setLo(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.routeguide.Rectangle.prototype.hasLo = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional Point hi = 2; + * @return {?proto.routeguide.Point} + */ +proto.routeguide.Rectangle.prototype.getHi = function() { + return /** @type{?proto.routeguide.Point} */ ( + jspb.Message.getWrapperField(this, proto.routeguide.Point, 2)); +}; + + +/** + * @param {?proto.routeguide.Point|undefined} value + * @return {!proto.routeguide.Rectangle} returns this +*/ +proto.routeguide.Rectangle.prototype.setHi = function(value) { + return jspb.Message.setWrapperField(this, 2, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.routeguide.Rectangle} returns this + */ +proto.routeguide.Rectangle.prototype.clearHi = function() { + return this.setHi(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.routeguide.Rectangle.prototype.hasHi = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.routeguide.Feature.prototype.toObject = function(opt_includeInstance) { + return proto.routeguide.Feature.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.routeguide.Feature} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.Feature.toObject = function(includeInstance, msg) { + var f, obj = { + name: jspb.Message.getFieldWithDefault(msg, 1, ""), + location: (f = msg.getLocation()) && proto.routeguide.Point.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.routeguide.Feature} + */ +proto.routeguide.Feature.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.routeguide.Feature; + return proto.routeguide.Feature.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.routeguide.Feature} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.routeguide.Feature} + */ +proto.routeguide.Feature.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setName(value); + break; + case 2: + var value = new proto.routeguide.Point; + reader.readMessage(value,proto.routeguide.Point.deserializeBinaryFromReader); + msg.setLocation(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.routeguide.Feature.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.routeguide.Feature.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.routeguide.Feature} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.Feature.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getName(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getLocation(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.routeguide.Point.serializeBinaryToWriter + ); + } +}; + + +/** + * optional string name = 1; + * @return {string} + */ +proto.routeguide.Feature.prototype.getName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.routeguide.Feature} returns this + */ +proto.routeguide.Feature.prototype.setName = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional Point location = 2; + * @return {?proto.routeguide.Point} + */ +proto.routeguide.Feature.prototype.getLocation = function() { + return /** @type{?proto.routeguide.Point} */ ( + jspb.Message.getWrapperField(this, proto.routeguide.Point, 2)); +}; + + +/** + * @param {?proto.routeguide.Point|undefined} value + * @return {!proto.routeguide.Feature} returns this +*/ +proto.routeguide.Feature.prototype.setLocation = function(value) { + return jspb.Message.setWrapperField(this, 2, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.routeguide.Feature} returns this + */ +proto.routeguide.Feature.prototype.clearLocation = function() { + return this.setLocation(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.routeguide.Feature.prototype.hasLocation = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.routeguide.RouteNote.prototype.toObject = function(opt_includeInstance) { + return proto.routeguide.RouteNote.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.routeguide.RouteNote} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.RouteNote.toObject = function(includeInstance, msg) { + var f, obj = { + location: (f = msg.getLocation()) && proto.routeguide.Point.toObject(includeInstance, f), + message: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.routeguide.RouteNote} + */ +proto.routeguide.RouteNote.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.routeguide.RouteNote; + return proto.routeguide.RouteNote.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.routeguide.RouteNote} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.routeguide.RouteNote} + */ +proto.routeguide.RouteNote.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.routeguide.Point; + reader.readMessage(value,proto.routeguide.Point.deserializeBinaryFromReader); + msg.setLocation(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setMessage(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.routeguide.RouteNote.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.routeguide.RouteNote.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.routeguide.RouteNote} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.RouteNote.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getLocation(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.routeguide.Point.serializeBinaryToWriter + ); + } + f = message.getMessage(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional Point location = 1; + * @return {?proto.routeguide.Point} + */ +proto.routeguide.RouteNote.prototype.getLocation = function() { + return /** @type{?proto.routeguide.Point} */ ( + jspb.Message.getWrapperField(this, proto.routeguide.Point, 1)); +}; + + +/** + * @param {?proto.routeguide.Point|undefined} value + * @return {!proto.routeguide.RouteNote} returns this +*/ +proto.routeguide.RouteNote.prototype.setLocation = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.routeguide.RouteNote} returns this + */ +proto.routeguide.RouteNote.prototype.clearLocation = function() { + return this.setLocation(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.routeguide.RouteNote.prototype.hasLocation = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string message = 2; + * @return {string} + */ +proto.routeguide.RouteNote.prototype.getMessage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.routeguide.RouteNote} returns this + */ +proto.routeguide.RouteNote.prototype.setMessage = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.routeguide.RouteSummary.prototype.toObject = function(opt_includeInstance) { + return proto.routeguide.RouteSummary.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.routeguide.RouteSummary} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.RouteSummary.toObject = function(includeInstance, msg) { + var f, obj = { + pointCount: jspb.Message.getFieldWithDefault(msg, 1, 0), + featureCount: jspb.Message.getFieldWithDefault(msg, 2, 0), + distance: jspb.Message.getFieldWithDefault(msg, 3, 0), + elapsedTime: jspb.Message.getFieldWithDefault(msg, 4, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.routeguide.RouteSummary} + */ +proto.routeguide.RouteSummary.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.routeguide.RouteSummary; + return proto.routeguide.RouteSummary.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.routeguide.RouteSummary} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.routeguide.RouteSummary} + */ +proto.routeguide.RouteSummary.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readInt32()); + msg.setPointCount(value); + break; + case 2: + var value = /** @type {number} */ (reader.readInt32()); + msg.setFeatureCount(value); + break; + case 3: + var value = /** @type {number} */ (reader.readInt32()); + msg.setDistance(value); + break; + case 4: + var value = /** @type {number} */ (reader.readInt32()); + msg.setElapsedTime(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.routeguide.RouteSummary.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.routeguide.RouteSummary.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.routeguide.RouteSummary} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.routeguide.RouteSummary.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getPointCount(); + if (f !== 0) { + writer.writeInt32( + 1, + f + ); + } + f = message.getFeatureCount(); + if (f !== 0) { + writer.writeInt32( + 2, + f + ); + } + f = message.getDistance(); + if (f !== 0) { + writer.writeInt32( + 3, + f + ); + } + f = message.getElapsedTime(); + if (f !== 0) { + writer.writeInt32( + 4, + f + ); + } +}; + + +/** + * optional int32 point_count = 1; + * @return {number} + */ +proto.routeguide.RouteSummary.prototype.getPointCount = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.routeguide.RouteSummary} returns this + */ +proto.routeguide.RouteSummary.prototype.setPointCount = function(value) { + return jspb.Message.setProto3IntField(this, 1, value); +}; + + +/** + * optional int32 feature_count = 2; + * @return {number} + */ +proto.routeguide.RouteSummary.prototype.getFeatureCount = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.routeguide.RouteSummary} returns this + */ +proto.routeguide.RouteSummary.prototype.setFeatureCount = function(value) { + return jspb.Message.setProto3IntField(this, 2, value); +}; + + +/** + * optional int32 distance = 3; + * @return {number} + */ +proto.routeguide.RouteSummary.prototype.getDistance = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.routeguide.RouteSummary} returns this + */ +proto.routeguide.RouteSummary.prototype.setDistance = function(value) { + return jspb.Message.setProto3IntField(this, 3, value); +}; + + +/** + * optional int32 elapsed_time = 4; + * @return {number} + */ +proto.routeguide.RouteSummary.prototype.getElapsedTime = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.routeguide.RouteSummary} returns this + */ +proto.routeguide.RouteSummary.prototype.setElapsedTime = function(value) { + return jspb.Message.setProto3IntField(this, 4, value); +}; + + +goog.object.extend(exports, proto.routeguide); diff --git a/examples/routeguide/static_codegen/route_guide_server.js b/examples/routeguide/static_codegen/route_guide_server.js new file mode 100644 index 000000000..eb1fd283d --- /dev/null +++ b/examples/routeguide/static_codegen/route_guide_server.js @@ -0,0 +1,244 @@ +/* + * + * Copyright 2015 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. + * + */ + +var messages = require('./route_guide_pb'); +var services = require('./route_guide_grpc_pb'); + +var fs = require('fs'); +var parseArgs = require('minimist'); +var path = require('path'); +var _ = require('lodash'); +var grpc = require('@grpc/grpc-js'); + +var COORD_FACTOR = 1e7; + +/** + * For simplicity, a point is a record type that looks like + * {latitude: number, longitude: number}, and a feature is a record type that + * looks like {name: string, location: point}. feature objects with name==='' + * are points with no feature. + */ + +/** + * List of feature objects at points that have been requested so far. + */ +var feature_list = []; + +/** + * Get a feature object at the given point, or creates one if it does not exist. + * @param {point} point The point to check + * @return {feature} The feature object at the point. Note that an empty name + * indicates no feature + */ +function checkFeature(point) { + var feature; + // Check if there is already a feature object for the given point + for (var i = 0; i < feature_list.length; i++) { + feature = feature_list[i]; + if (feature.getLocation().getLatitude() === point.getLatitude() && + feature.getLocation().getLongitude() === point.getLongitude()) { + return feature; + } + } + var name = ''; + feature = new messages.Feature(); + feature.setName(name); + feature.setLocation(point); + return feature; +} + +/** + * getFeature request handler. Gets a request with a point, and responds with a + * feature object indicating whether there is a feature at that point. + * @param {EventEmitter} call Call object for the handler to process + * @param {function(Error, feature)} callback Response callback + */ +function getFeature(call, callback) { + callback(null, checkFeature(call.request)); +} + +/** + * listFeatures request handler. Gets a request with two points, and responds + * with a stream of all features in the bounding box defined by those points. + * @param {Writable} call Writable stream for responses with an additional + * request property for the request value. + */ +function listFeatures(call) { + var lo = call.request.getLo(); + var hi = call.request.getHi(); + var left = _.min([lo.getLongitude(), hi.getLongitude()]); + var right = _.max([lo.getLongitude(), hi.getLongitude()]); + var top = _.max([lo.getLatitude(), hi.getLatitude()]); + var bottom = _.min([lo.getLatitude(), hi.getLatitude()]); + // For each feature, check if it is in the given bounding box + _.each(feature_list, function(feature) { + if (feature.getName() === '') { + return; + } + if (feature.getLocation().getLongitude() >= left && + feature.getLocation().getLongitude() <= right && + feature.getLocation().getLatitude() >= bottom && + feature.getLocation().getLatitude() <= top) { + call.write(feature); + } + }); + call.end(); +} + +/** + * Calculate the distance between two points using the "haversine" formula. + * The formula is based on http://mathforum.org/library/drmath/view/51879.html. + * @param start The starting point + * @param end The end point + * @return The distance between the points in meters + */ +function getDistance(start, end) { + function toRadians(num) { + return num * Math.PI / 180; + } + var R = 6371000; // earth radius in metres + var lat1 = toRadians(start.getLatitude() / COORD_FACTOR); + var lat2 = toRadians(end.getLatitude() / COORD_FACTOR); + var lon1 = toRadians(start.getLongitude() / COORD_FACTOR); + var lon2 = toRadians(end.getLongitude() / COORD_FACTOR); + + var deltalat = lat2-lat1; + var deltalon = lon2-lon1; + var a = Math.sin(deltalat/2) * Math.sin(deltalat/2) + + Math.cos(lat1) * Math.cos(lat2) * + Math.sin(deltalon/2) * Math.sin(deltalon/2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + return R * c; +} + +/** + * recordRoute handler. Gets a stream of points, and responds with statistics + * about the "trip": number of points, number of known features visited, total + * distance traveled, and total time spent. + * @param {Readable} call The request point stream. + * @param {function(Error, routeSummary)} callback The callback to pass the + * response to + */ +function recordRoute(call, callback) { + var point_count = 0; + var feature_count = 0; + var distance = 0; + var previous = null; + // Start a timer + var start_time = process.hrtime(); + call.on('data', function(point) { + point_count += 1; + if (checkFeature(point).name !== '') { + feature_count += 1; + } + /* For each point after the first, add the incremental distance from the + * previous point to the total distance value */ + if (previous != null) { + distance += getDistance(previous, point); + } + previous = point; + }); + call.on('end', function() { + var summary = new messages.RouteSummary(); + summary.setPointCount(point_count); + summary.setFeatureCount(feature_count); + // Cast the distance to an integer + summary.setDistance(distance|0); + // End the timer + summary.setElapsedTime(process.hrtime(start_time)[0]); + callback(null, summary); + }); +} + +var route_notes = {}; + +/** + * Turn the point into a dictionary key. + * @param {point} point The point to use + * @return {string} The key for an object + */ +function pointKey(point) { + return point.getLatitude() + ' ' + point.getLongitude(); +} + +/** + * routeChat handler. Receives a stream of message/location pairs, and responds + * with a stream of all previous messages at each of those locations. + * @param {Duplex} call The stream for incoming and outgoing messages + */ +function routeChat(call) { + call.on('data', function(note) { + var key = pointKey(note.getLocation()); + /* For each note sent, respond with all previous notes that correspond to + * the same point */ + if (route_notes.hasOwnProperty(key)) { + _.each(route_notes[key], function(note) { + call.write(note); + }); + } else { + route_notes[key] = []; + } + // Then add the new note to the list + route_notes[key].push(note); + }); + call.on('end', function() { + call.end(); + }); +} + +/** + * Get a new server with the handler functions in this file bound to the methods + * it serves. + * @return {Server} The new server object + */ +function getServer() { + var server = new grpc.Server(); + server.addService(services.RouteGuideService, { + getFeature: getFeature, + listFeatures: listFeatures, + recordRoute: recordRoute, + routeChat: routeChat + }); + return server; +} + +if (require.main === module) { + // If this is run as a script, start a server on an unused port + var routeServer = getServer(); + routeServer.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { + var argv = parseArgs(process.argv, { + string: 'db_path' + }); + fs.readFile(path.resolve(argv.db_path), function(err, data) { + if (err) throw err; + // Transform the loaded features to Feature objects + feature_list = _.map(JSON.parse(data), function(value) { + var feature = new messages.Feature(); + feature.setName(value.name); + var location = new messages.Point(); + location.setLatitude(value.location.latitude); + location.setLongitude(value.location.longitude); + feature.setLocation(location); + return feature; + }); + routeServer.start(); + }); + }); +} + +exports.getServer = getServer; diff --git a/examples/xds/greeter_client.js b/examples/xds/greeter_client.js new file mode 100644 index 000000000..17203742e --- /dev/null +++ b/examples/xds/greeter_client.js @@ -0,0 +1,62 @@ +/* + * + * Copyright 2015 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. + * + */ + +var PROTO_PATH = __dirname + '/../protos/helloworld.proto'; + +var parseArgs = require('minimist'); +var grpc = require('@grpc/grpc-js'); +var grpc_xds = require('@grpc/grpc-js-xds'); +grpc_xds.register(); + +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +function main() { + var argv = parseArgs(process.argv.slice(2), { + string: 'target' + }); + var target; + if (argv.target) { + target = argv.target; + } else { + target = 'localhost:50051'; + } + var client = new hello_proto.Greeter(target, + grpc.credentials.createInsecure()); + var user; + if (argv._.length > 0) { + user = argv._[0]; + } else { + user = 'world'; + } + client.sayHello({name: user}, function(err, response) { + if (err) throw err; + console.log('Greeting:', response.message); + client.close(); + }); +} + +main(); From cc89158e132a0c6dc2ab099a487731b0093df37a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 22 Jun 2023 16:52:53 -0700 Subject: [PATCH 511/694] grpc-js-xds: Use distroless Node image for interop Dockerfile --- packages/grpc-js-xds/interop/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 5c8e362f1..5f7305055 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -27,7 +27,7 @@ RUN npm install WORKDIR /node/src/grpc-node/packages/grpc-js-xds RUN npm install -FROM node:18-slim +FROM gcr.io/distroless/nodejs18-debian11:latest WORKDIR /node/src/grpc-node COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ From 9441de78f655ada34ada0dc1a8057122eb21f229 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 22 Jun 2023 16:52:53 -0700 Subject: [PATCH 512/694] grpc-js-xds: Use distroless Node image for interop Dockerfile --- packages/grpc-js-xds/interop/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 5987f1ee4..2986eb3e5 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -27,7 +27,7 @@ RUN npm install WORKDIR /node/src/grpc-node/packages/grpc-js-xds RUN npm install -FROM node:18-slim +FROM gcr.io/distroless/nodejs18-debian11:latest WORKDIR /node/src/grpc-node COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ From 9b7e5e66ab1f4d4d91b4763d0a5ccd509aa88dab Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 23 Jun 2023 09:34:29 -0700 Subject: [PATCH 513/694] Use entrypoint /nodejs/bin/node --- packages/grpc-js-xds/interop/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 5f7305055..63a321061 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -35,4 +35,4 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xd ENV GRPC_VERBOSITY="DEBUG" ENV GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_cluster_resolver,xds_cluster_impl,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection,server,server_call -ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] +ENTRYPOINT [ "/nodejs/bin/node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] From a62d2b027bf91e5084c9134305e88a645dc5f1c1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 23 Jun 2023 09:34:29 -0700 Subject: [PATCH 514/694] Use entrypoint /nodejs/bin/node --- packages/grpc-js-xds/interop/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 2986eb3e5..5ff12a433 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -35,4 +35,4 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xd ENV GRPC_VERBOSITY="DEBUG" ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection -ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] +ENTRYPOINT [ "/nodejs/bin/node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] From a6aa7ea43e1458a578cf9ab81ed492a760c43a78 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 23 Jun 2023 10:37:01 -0700 Subject: [PATCH 515/694] Merge pull request #2475 from XuanWang-Amos/file_multiple_url_map [PSM interop] Don't fail target if sub-target already failed --- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index 69126c72b..fc74718f2 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -156,7 +156,7 @@ main() { build_docker_images_if_needed # Run tests cd "${TEST_DRIVER_FULL_DIR}" - run_test url_map + run_test url_map || echo "Failed url_map test" } main "$@" From ed70a0b381144b387698f2d57001f5a7bc82cbe9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 27 Jun 2023 10:11:45 -0700 Subject: [PATCH 516/694] Fix handling of OD policy with no child --- packages/grpc-js/src/load-balancer-outlier-detection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 885c4feb9..ce8668f18 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -113,7 +113,7 @@ export class OutlierDetectionLoadBalancingConfig implements LoadBalancingConfig failurePercentageEjection: Partial | null, private readonly childPolicy: LoadBalancingConfig[] ) { - if (childPolicy[0].getLoadBalancerName() === 'pick_first') { + if (childPolicy.length > 0 && childPolicy[0].getLoadBalancerName() === 'pick_first') { throw new Error('outlier_detection LB policy cannot have a pick_first child policy'); } this.intervalMs = intervalMs ?? 10_000; From 7c934310fda92a4d04147edeb38ec350d60dc622 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 26 Jun 2023 15:23:13 -0700 Subject: [PATCH 517/694] Add metadata example --- examples/metadata/README.md | 15 ++ examples/metadata/client.js | 267 ++++++++++++++++++++++++++++++++++++ examples/metadata/server.js | 152 ++++++++++++++++++++ examples/protos/echo.proto | 45 ++++++ 4 files changed, 479 insertions(+) create mode 100644 examples/metadata/README.md create mode 100644 examples/metadata/client.js create mode 100644 examples/metadata/server.js create mode 100644 examples/protos/echo.proto diff --git a/examples/metadata/README.md b/examples/metadata/README.md new file mode 100644 index 000000000..f8b55de2c --- /dev/null +++ b/examples/metadata/README.md @@ -0,0 +1,15 @@ +# Metadata example + +This example shows how to set and read metadata in RPC headers and trailers. + +## Start the server + +``` +node server.js +``` + +## Run the client + +``` +node client.js +``` diff --git a/examples/metadata/client.js b/examples/metadata/client.js new file mode 100644 index 000000000..832019438 --- /dev/null +++ b/examples/metadata/client.js @@ -0,0 +1,267 @@ +/* + * + * 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. + * + */ + +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const parseArgs = require('minimist'); + +const PROTO_PATH = __dirname + '/../protos/echo.proto'; + +const packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo; + +const STREAMING_COUNT = 10; + +function unaryCallWithMetadata(client, message) { + return new Promise((resolve, reject) => { + console.log('--- unary ---'); + const requestMetadata = new grpc.Metadata(); + requestMetadata.set('timestamp', new Date().toISOString()); + const call = client.unaryEcho({message}, requestMetadata, (error, value) => { + if (error) { + console.log(`Received error ${error}`); + return; + } + console.log('Response:'); + console.log(`- ${JSON.stringify(value)}`); + }); + call.on('metadata', metadata => { + const timestamps = metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from header:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in header"); + } + const locations = metadata.get('location'); + if (locations.length > 0) { + console.log('location from header:'); + for (const [index, value] of locations.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("location expected but doesn't exist in header"); + } + }); + call.on('status', status => { + const timestamps = status.metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from trailer:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in trailer"); + } + resolve(); + }); + }); +} + +function serverStreamingWithMetadata(client, message) { + return new Promise((resolve, reject) => { + console.log('--- server streaming ---'); + const requestMetadata = new grpc.Metadata(); + requestMetadata.set('timestamp', new Date().toISOString()); + const call = client.serverStreamingEcho({message}, requestMetadata); + call.on('metadata', metadata => { + const timestamps = metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from header:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in header"); + } + const locations = metadata.get('location'); + if (locations.length > 0) { + console.log('location from header:'); + for (const [index, value] of locations.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("location expected but doesn't exist in header"); + } + }); + call.on('data', value => { + console.log(`Received response ${JSON.stringify(value)}`); + }); + call.on('status', status => { + const timestamps = status.metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from trailer:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in trailer"); + } + resolve(); + }); + call.on('error', error => { + console.log(`Received error ${error}`); + }); + }); +} + +function clientStreamingWithMetadata(client, message) { + return new Promise((resolve, reject) => { + console.log('--- client streaming ---'); + const requestMetadata = new grpc.Metadata(); + requestMetadata.set('timestamp', new Date().toISOString()); + const call = client.clientStreamingEcho(requestMetadata, (error, value) => { + if (error) { + console.log(`Received error ${error}`); + return; + } + console.log('Response:'); + console.log(`- ${JSON.stringify(value)}`); + }); + call.on('metadata', metadata => { + const timestamps = metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from header:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in header"); + } + const locations = metadata.get('location'); + if (locations.length > 0) { + console.log('location from header:'); + for (const [index, value] of locations.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("location expected but doesn't exist in header"); + } + }); + call.on('status', status => { + const timestamps = status.metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from trailer:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in trailer"); + } + resolve(); + }); + for (let i = 0; i < STREAMING_COUNT; i++) { + call.write({message}); + } + call.end(); + }); +} + +function bidirectionalWithMetadata(client, message) { + return new Promise((resolve, reject) => { + console.log('--- bidirectional ---'); + const requestMetadata = new grpc.Metadata(); + requestMetadata.set('timestamp', new Date().toISOString()); + const call = client.bidirectionalStreamingEcho(requestMetadata); + call.on('metadata', metadata => { + const timestamps = metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from header:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in header"); + } + const locations = metadata.get('location'); + if (locations.length > 0) { + console.log('location from header:'); + for (const [index, value] of locations.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("location expected but doesn't exist in header"); + } + }); + call.on('data', value => { + console.log(`Received response ${JSON.stringify(value)}`); + }); + call.on('status', status => { + const timestamps = status.metadata.get('timestamp'); + if (timestamps.length > 0) { + console.log('timestamp from trailer:'); + for (const [index, value] of timestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } else { + console.error("timestamp expected but doesn't exist in trailer"); + } + resolve(); + }); + call.on('error', error => { + console.log(`Received error ${error}`); + }); + for (let i = 0; i < STREAMING_COUNT; i++) { + call.write({message}); + } + call.end(); + }); +} + +function asyncWait(ms) { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms); + }); +} + +const message = 'this is examples/metadata'; + +async function main() { + let argv = parseArgs(process.argv.slice(2), { + string: 'target' + }); + let target; + if (argv.target) { + target = argv.target; + } else { + target = 'localhost:50051'; + } + const client = new echoProto.Echo(target, grpc.credentials.createInsecure()); + await unaryCallWithMetadata(client, message); + await asyncWait(1000); + + await serverStreamingWithMetadata(client, message); + await asyncWait(1000); + + await clientStreamingWithMetadata(client, message); + await asyncWait(1000); + + await bidirectionalWithMetadata(client, message); + client.close(); +} + +main(); diff --git a/examples/metadata/server.js b/examples/metadata/server.js new file mode 100644 index 000000000..821302524 --- /dev/null +++ b/examples/metadata/server.js @@ -0,0 +1,152 @@ +/* + * + * 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. + * + */ + +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); + +const PROTO_PATH = __dirname + '/../protos/echo.proto'; + +const packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo; + +const STREAMING_COUNT = 10; + +function unaryEcho(call, callback) { + console.log('--- UnaryEcho ---'); + const incomingTimestamps = call.metadata.get('timestamp'); + if (incomingTimestamps.length > 0) { + console.log('Timestamp from metadata:'); + for (const [index, value] of incomingTimestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } + + const outgoingHeaders = new grpc.Metadata(); + outgoingHeaders.set('location', 'MTV'); + outgoingHeaders.set('timestamp', new Date().toISOString()); + call.sendMetadata(outgoingHeaders); + + const outgoingTrailers = new grpc.Metadata(); + outgoingTrailers.set('timestamp', new Date().toISOString()); + + console.log(`Request received ${JSON.stringify(call.request)}, sending echo`); + callback(null, call.request, outgoingTrailers); +} + +function serverStreamingEcho(call) { + console.log('--- ServerStreamingEcho ---'); + const incomingTimestamps = call.metadata.get('timestamp'); + if (incomingTimestamps.length > 0) { + console.log('Timestamp from metadata:'); + for (const [index, value] of incomingTimestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } + + const outgoingHeaders = new grpc.Metadata(); + outgoingHeaders.set('location', 'MTV'); + outgoingHeaders.set('timestamp', new Date().toISOString()); + call.sendMetadata(outgoingHeaders); + + console.log(`Request received ${JSON.stringify(call.request)}`); + for (let i = 0; i < STREAMING_COUNT; i++) { + console.log(`Echo message ${JSON.stringify(call.request)}`); + call.write(call.request); + } + + const outgoingTrailers = new grpc.Metadata(); + outgoingTrailers.set('timestamp', new Date().toISOString()); + call.end(outgoingTrailers); +} + +function clientStreamingEcho(call, callback) { + console.log('--- ClientStreamingEcho ---'); + const incomingTimestamps = call.metadata.get('timestamp'); + if (incomingTimestamps.length > 0) { + console.log('Timestamp from metadata:'); + for (const [index, value] of incomingTimestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } + + const outgoingHeaders = new grpc.Metadata(); + outgoingHeaders.set('location', 'MTV'); + outgoingHeaders.set('timestamp', new Date().toISOString()); + call.sendMetadata(outgoingHeaders); + + let lastReceivedMessage = ''; + call.on('data', value => { + console.log(`Received request ${JSON.stringify(value)}`); + lastReceivedMessage = value.message; + }); + call.on('end', () => { + const outgoingTrailers = new grpc.Metadata(); + outgoingTrailers.set('timestamp', new Date().toISOString()); + callback(null, {message: lastReceivedMessage}, outgoingTrailers); + }); +} + +function bidirectionalStreamingEcho(call) { + console.log('--- BidirectionalStreamingEcho ---'); + const incomingTimestamps = call.metadata.get('timestamp'); + if (incomingTimestamps.length > 0) { + console.log('Timestamp from metadata:'); + for (const [index, value] of incomingTimestamps.entries()) { + console.log(` ${index}. ${value}`); + } + } + + const outgoingHeaders = new grpc.Metadata(); + outgoingHeaders.set('location', 'MTV'); + outgoingHeaders.set('timestamp', new Date().toISOString()); + call.sendMetadata(outgoingHeaders); + + call.on('data', value => { + console.log(`Request received ${JSON.stringify(value)}, sending echo`); + call.write(value); + }); + call.on('end', () => { + const outgoingTrailers = new grpc.Metadata(); + outgoingTrailers.set('timestamp', new Date().toISOString()); + call.end(outgoingTrailers); + }); +} + +const serviceImplementation = { + unaryEcho, + serverStreamingEcho, + clientStreamingEcho, + bidirectionalStreamingEcho +}; + +function main() { + const server = new grpc.Server(); + server.addService(echoProto.Echo.service, serviceImplementation); + server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { + server.start(); + }); +} + +main(); diff --git a/examples/protos/echo.proto b/examples/protos/echo.proto new file mode 100644 index 000000000..2dde5633e --- /dev/null +++ b/examples/protos/echo.proto @@ -0,0 +1,45 @@ +/* + * + * Copyright 2018 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. + * + */ + +syntax = "proto3"; + +option go_package = "google.golang.org/grpc/examples/features/proto/echo"; + +package grpc.examples.echo; + +// EchoRequest is the request for echo. +message EchoRequest { + string message = 1; +} + +// EchoResponse is the response for echo. +message EchoResponse { + string message = 1; +} + +// Echo is the echo service. +service Echo { + // UnaryEcho is unary echo. + rpc UnaryEcho(EchoRequest) returns (EchoResponse) {} + // ServerStreamingEcho is server side streaming. + rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {} + // ClientStreamingEcho is client side streaming. + rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {} + // BidirectionalStreamingEcho is bidi streaming. + rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {} +} From d2a3ef45c0aba6101de380e4f4e00640b27bb647 Mon Sep 17 00:00:00 2001 From: Sergii Tkachenko Date: Wed, 28 Jun 2023 13:55:27 -0700 Subject: [PATCH 518/694] grpc-js-xds: Bump the canonical server from v1.46.x to v1.56.0 Similar to https://github.com/grpc/grpc/pull/33542. Note that there's a ticket to automatically use the one specified in the `--server_image_canonical` flag, but for now we just hardcode. --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 4a83d44d5..729fb9293 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -19,7 +19,7 @@ set -eo pipefail readonly GITHUB_REPOSITORY_NAME="grpc-node" readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" ## xDS test client Docker images -readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:v1.46.x" +readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:558b5b0bfac8e21755c223063274a779b3898afe" readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" From b1b63be6cf03fb5735f71a37eeba80ba876d02c3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 28 Jun 2023 14:49:52 -0700 Subject: [PATCH 519/694] Add deadline examples --- examples/deadline/client.js | 92 ++++++++++++++++++++++++++++++ examples/deadline/server.js | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 examples/deadline/client.js create mode 100644 examples/deadline/server.js diff --git a/examples/deadline/client.js b/examples/deadline/client.js new file mode 100644 index 000000000..84143ac02 --- /dev/null +++ b/examples/deadline/client.js @@ -0,0 +1,92 @@ +/* + * + * 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. + * + */ + +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const parseArgs = require('minimist'); + +const PROTO_PATH = __dirname + '/../protos/echo.proto'; + +const packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo; + +function unaryCall(client, requestId, message, expectedCode) { + return new Promise((resolve, reject) => { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + client.unaryEcho({message: message}, {deadline}, (error, value) => { + let code; + if (error) { + code = error.code; + } else { + code = grpc.status.OK; + } + console.log(`[${requestId}] wanted = ${grpc.status[expectedCode]} got = ${grpc.status[code]}`); + resolve(); + }); + }); +} + +function streamingCall(client, requestId, message, expectedCode) { + return new Promise((resolve, reject) => { + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + const call = client.bidirectionalStreamingEcho({deadline}); + call.on('data', () => { + // Consume all response messages + }); + call.on('status', status => { + console.log(`[${requestId}] wanted = ${grpc.status[expectedCode]} got = ${grpc.status[status.code]}`); + resolve(); + }); + call.on('error', () => { + // Ignore error event + }); + call.write({message}); + call.end(); + }); +} + +async function main() { + let argv = parseArgs(process.argv.slice(2), { + string: 'target', + default: {target: 'localhost:50052'} + }); + const client = new echoProto.Echo(argv.target, grpc.credentials.createInsecure()); + // A successful request + await unaryCall(client, 1, 'world', grpc.status.OK); + // Exceeds deadline + await unaryCall(client, 2, 'delay', grpc.status.DEADLINE_EXCEEDED); + // A successful request with propagated deadline + await unaryCall(client, 3, '[propagate me]world', grpc.status.OK); + // Exceeds propagated deadline + await unaryCall(client, 4, '[propagate me][propagate me]world', grpc.status.DEADLINE_EXCEEDED); + // Receives a response from the stream successfully + await streamingCall(client, 5, '[propagate me]world', grpc.status.OK); + // Exceeds propagated deadline before receiving a response + await streamingCall(client, 6, '[propagate me][propagate me]world', grpc.status.DEADLINE_EXCEEDED); +} + +main(); diff --git a/examples/deadline/server.js b/examples/deadline/server.js new file mode 100644 index 000000000..1618a9d5a --- /dev/null +++ b/examples/deadline/server.js @@ -0,0 +1,109 @@ +/* + * + * 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. + * + */ + +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const parseArgs = require('minimist'); + +const PROTO_PATH = __dirname + '/../protos/echo.proto'; + +const packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo; + +const PROPAGATE_PREFIX = '[propagate me]'; + +let client; + +function unaryEcho(call, callback) { + const message = call.request.message; + if (message.startsWith(PROPAGATE_PREFIX)) { + setTimeout(() => { + client.unaryEcho({message: message.slice(PROPAGATE_PREFIX.length)}, {parent: call}, callback); + }, 800); + return; + } else if (message === 'delay') { + setTimeout(() => { + callback(null, call.request); + }, 1500); + } else { + callback(null, call.request); + } +} + +function bidirectionalStreamingEcho(call) { + let lastMessage = null; + call.on('data', value => { + const message = value.message; + lastMessage = message; + call.pause(); + if (message.startsWith(PROPAGATE_PREFIX)) { + setTimeout(() => { + client.unaryEcho({message: message.slice(PROPAGATE_PREFIX.length)}, {parent: call}, (error, response) => { + call.resume(); + if (error) { + call.emit(error); + return; + } + call.write(response); + }); + }, 800); + return; + } else if (message === 'delay') { + setTimeout(() => { + call.write(value); + call.resume(); + }, 1500); + } else { + call.write(value); + call.resume(); + } + }); + call.on('end', () => { + if (lastMessage === null) { + call.emit('error', {code: grpc.status.INVALID_ARGUMENT, details: 'request message not received'}); + } + call.end(); + }); +} + +const serviceImplementation = { + unaryEcho, + bidirectionalStreamingEcho +} + +function main() { + const argv = parseArgs(process.argv.slice(2), { + string: 'port', + default: {port: '50052'} + }); + const server = new grpc.Server(); + server.addService(echoProto.Echo.service, serviceImplementation); + server.bindAsync(`0.0.0.0:${argv.port}`, grpc.ServerCredentials.createInsecure(), () => { + server.start(); + }); + client = new echoProto.Echo(`localhost:${argv.port}`, grpc.credentials.createInsecure()); +} + +main(); From 163597e84bf62c156d9d2db240a36ffff00a29d7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 28 Jun 2023 14:52:54 -0700 Subject: [PATCH 520/694] Use command line arguments more consistently --- examples/metadata/client.js | 11 +++-------- examples/metadata/server.js | 6 +++++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/metadata/client.js b/examples/metadata/client.js index 832019438..e8f6f53fa 100644 --- a/examples/metadata/client.js +++ b/examples/metadata/client.js @@ -242,15 +242,10 @@ const message = 'this is examples/metadata'; async function main() { let argv = parseArgs(process.argv.slice(2), { - string: 'target' + string: 'target', + default: {target: 'localhost:50052'} }); - let target; - if (argv.target) { - target = argv.target; - } else { - target = 'localhost:50051'; - } - const client = new echoProto.Echo(target, grpc.credentials.createInsecure()); + const client = new echoProto.Echo(argv.target, grpc.credentials.createInsecure()); await unaryCallWithMetadata(client, message); await asyncWait(1000); diff --git a/examples/metadata/server.js b/examples/metadata/server.js index 821302524..b061d20a2 100644 --- a/examples/metadata/server.js +++ b/examples/metadata/server.js @@ -142,9 +142,13 @@ const serviceImplementation = { }; function main() { + const argv = parseArgs(process.argv.slice(2), { + string: 'port', + default: {port: '50052'} + }); const server = new grpc.Server(); server.addService(echoProto.Echo.service, serviceImplementation); - server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => { + server.bindAsync(`0.0.0.0:${argv.port}`, grpc.ServerCredentials.createInsecure(), () => { server.start(); }); } From 3cef1ba5472d5127b7346ef1533ffb403e6dcef3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 28 Jun 2023 16:00:22 -0700 Subject: [PATCH 521/694] Merge pull request #2488 from grpc/psm-interop-server-bump grpc-js-xds: Bump the canonical server from v1.46.x to v1.56.0 --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 4a83d44d5..729fb9293 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -19,7 +19,7 @@ set -eo pipefail readonly GITHUB_REPOSITORY_NAME="grpc-node" readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" ## xDS test client Docker images -readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:v1.46.x" +readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:558b5b0bfac8e21755c223063274a779b3898afe" readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" From 31adc1dac1aef8927a0cbb0d6538d83437cbaa74 Mon Sep 17 00:00:00 2001 From: Lucio Martinez Date: Mon, 10 Jul 2023 22:03:01 +0000 Subject: [PATCH 522/694] Fixes security issue by upgrading `protobufjs` --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 254cb6295..1e9a4baaf 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -48,7 +48,7 @@ "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", "long": "^4.0.0", - "protobufjs": "^7.0.0", + "protobufjs": "^7.2.4", "yargs": "^17.7.2" }, "devDependencies": { From 513f72a4fce95e034b057eb9b0cabc9e1df66798 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 11 Jul 2023 10:54:15 -0700 Subject: [PATCH 523/694] proto-loader: Increment version to 0.7.8 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 1e9a4baaf..0ebe1ed32 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.7", + "version": "0.7.8", "author": "Google Inc.", "contributors": [ { From 14b18a4bba9632c05a0223060837b7dcb3b4f453 Mon Sep 17 00:00:00 2001 From: Cedric Kassen Date: Wed, 12 Jul 2023 13:32:35 +0200 Subject: [PATCH 524/694] promisify receiveUnaryMessage server-call --- packages/grpc-js/src/server-call.ts | 146 ++++++++++++++-------------- packages/grpc-js/src/server.ts | 81 +++++++-------- 2 files changed, 108 insertions(+), 119 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index c03258308..e4dc13690 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -553,102 +553,100 @@ export class Http2ServerCallStream< return metadata; } - receiveUnaryMessage( - encoding: string, - next: ( - err: Partial | null, - request?: RequestType - ) => void - ): void { - const { stream } = this; + receiveUnaryMessage(encoding: string): Promise { + return new Promise((resolve, reject) => { + const { stream } = this; + + let receivedLength = 0; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const call = this; + const body: Buffer[] = []; + const limit = this.maxReceiveMessageSize; - let receivedLength = 0; + this.stream.on('data', onData); + this.stream.on('end', onEnd); + this.stream.on('error', onEnd); - // eslint-disable-next-line @typescript-eslint/no-this-alias - const call = this; - const body: Buffer[] = []; - const limit = this.maxReceiveMessageSize; + async function onData(chunk: Buffer) { + receivedLength += chunk.byteLength; - stream.on('data', onData); - stream.on('end', onEnd); - stream.on('error', onEnd); + if (limit !== -1 && receivedLength > limit) { + stream.removeListener('data', onData); + stream.removeListener('end', onEnd); + stream.removeListener('error', onEnd); - function onData(chunk: Buffer) { - receivedLength += chunk.byteLength; + reject({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message larger than max (${receivedLength} vs. ${limit})`, + }); + return; + } + + body.push(chunk); + } - if (limit !== -1 && receivedLength > limit) { + async function onEnd(err?: Error) { stream.removeListener('data', onData); stream.removeListener('end', onEnd); stream.removeListener('error', onEnd); - next({ - code: Status.RESOURCE_EXHAUSTED, - details: `Received message larger than max (${receivedLength} vs. ${limit})`, - }); - return; - } - - body.push(chunk); - } - function onEnd(err?: Error) { - stream.removeListener('data', onData); - stream.removeListener('end', onEnd); - stream.removeListener('error', onEnd); + if (err !== undefined) { + reject({ code: Status.INTERNAL, details: err.message }); + return; + } - if (err !== undefined) { - next({ code: Status.INTERNAL, details: err.message }); - return; - } + if (receivedLength === 0) { + reject({ + code: Status.INTERNAL, + details: 'received empty unary message', + }); + return; + } - if (receivedLength === 0) { - next({ - code: Status.INTERNAL, - details: 'received empty unary message', - }); - return; - } + call.emit('receiveMessage'); - call.emit('receiveMessage'); + const requestBytes = Buffer.concat(body, receivedLength); + const compressed = requestBytes.readUInt8(0) === 1; + const compressedMessageEncoding = compressed ? encoding : 'identity'; + const decompressedMessage = call.getDecompressedMessage( + requestBytes, + compressedMessageEncoding + ); - const requestBytes = Buffer.concat(body, receivedLength); - const compressed = requestBytes.readUInt8(0) === 1; - const compressedMessageEncoding = compressed ? encoding : 'identity'; - const decompressedMessage = call.getDecompressedMessage( - requestBytes, - compressedMessageEncoding - ); + if (Buffer.isBuffer(decompressedMessage)) { + call.safeDeserializeMessage(decompressedMessage, resolve, reject); + return; + } - if (Buffer.isBuffer(decompressedMessage)) { - call.safeDeserializeMessage(decompressedMessage, next); - return; + decompressedMessage.then( + decompressed => + call.safeDeserializeMessage(decompressed, resolve, reject), + (err: any) => + reject( + err.code + ? err + : { + code: Status.INTERNAL, + details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, + } + ) + ); } - - decompressedMessage.then( - decompressed => call.safeDeserializeMessage(decompressed, next), - (err: any) => - next( - err.code - ? err - : { - code: Status.INTERNAL, - details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, - } - ) - ); - } + }); } private safeDeserializeMessage( buffer: Buffer, - next: ( - err: Partial | null, - request?: RequestType - ) => void + resolve: ( + value: void | RequestType | PromiseLike + ) => void, + reject: (reason: any) => void ) { try { - next(null, this.deserializeMessage(buffer)); + resolve(this.deserializeMessage(buffer)); } catch (err) { - next({ + reject({ details: getErrorMessage(err), code: Status.INTERNAL, }); diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index b9ad8096d..0b05a680d 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -96,6 +96,7 @@ function getUnimplementedStatusResponse( return { code: Status.UNIMPLEMENTED, details: `The server does not implement the method ${methodName}`, + metadata: new Metadata(), }; } @@ -1176,40 +1177,35 @@ export class Server { } } -function handleUnary( +async function handleUnary( call: Http2ServerCallStream, handler: UnaryHandler, metadata: Metadata, encoding: string -): void { - call.receiveUnaryMessage(encoding, (err, request) => { - if (err) { - call.sendError(err); - return; - } +): Promise { + const request = await call.receiveUnaryMessage(encoding); - if (request === undefined || call.cancelled) { - return; - } + if (request === undefined || call.cancelled) { + return; + } - const emitter = new ServerUnaryCallImpl( - call, - metadata, - request - ); + const emitter = new ServerUnaryCallImpl( + call, + metadata, + request + ); - handler.func( - emitter, - ( - err: ServerErrorResponse | ServerStatusResponse | null, - value?: ResponseType | null, - trailer?: Metadata, - flags?: number - ) => { - call.sendUnaryMessage(err, value, trailer, flags); - } - ); - }); + handler.func( + emitter, + ( + err: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number + ) => { + call.sendUnaryMessage(err, value, trailer, flags); + } + ); } function handleClientStreaming( @@ -1243,31 +1239,26 @@ function handleClientStreaming( handler.func(stream, respond); } -function handleServerStreaming( +async function handleServerStreaming( call: Http2ServerCallStream, handler: ServerStreamingHandler, metadata: Metadata, encoding: string -): void { - call.receiveUnaryMessage(encoding, (err, request) => { - if (err) { - call.sendError(err); - return; - } +): Promise { + const request = await call.receiveUnaryMessage(encoding); - if (request === undefined || call.cancelled) { - return; - } + if (request === undefined || call.cancelled) { + return; + } - const stream = new ServerWritableStreamImpl( - call, - metadata, - handler.serialize, - request - ); + const stream = new ServerWritableStreamImpl( + call, + metadata, + handler.serialize, + request + ); - handler.func(stream); - }); + handler.func(stream); } function handleBidiStreaming( From 555643dcc8d46190771f784345ca6b890d26998b Mon Sep 17 00:00:00 2001 From: Cedric Kassen Date: Wed, 12 Jul 2023 13:41:30 +0200 Subject: [PATCH 525/694] try catch promise rejection and sendError --- packages/grpc-js/src/server.ts | 70 +++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 0b05a680d..8aeacdd54 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -1183,29 +1183,33 @@ async function handleUnary( metadata: Metadata, encoding: string ): Promise { - const request = await call.receiveUnaryMessage(encoding); + try { + const request = await call.receiveUnaryMessage(encoding); - if (request === undefined || call.cancelled) { - return; - } + if (request === undefined || call.cancelled) { + return; + } - const emitter = new ServerUnaryCallImpl( - call, - metadata, - request - ); + const emitter = new ServerUnaryCallImpl( + call, + metadata, + request + ); - handler.func( - emitter, - ( - err: ServerErrorResponse | ServerStatusResponse | null, - value?: ResponseType | null, - trailer?: Metadata, - flags?: number - ) => { - call.sendUnaryMessage(err, value, trailer, flags); - } - ); + handler.func( + emitter, + ( + err: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number + ) => { + call.sendUnaryMessage(err, value, trailer, flags); + } + ); + } catch (err) { + call.sendError(err as ServerErrorResponse) + } } function handleClientStreaming( @@ -1245,20 +1249,24 @@ async function handleServerStreaming( metadata: Metadata, encoding: string ): Promise { - const request = await call.receiveUnaryMessage(encoding); + try { + const request = await call.receiveUnaryMessage(encoding); - if (request === undefined || call.cancelled) { - return; - } + if (request === undefined || call.cancelled) { + return; + } - const stream = new ServerWritableStreamImpl( - call, - metadata, - handler.serialize, - request - ); + const stream = new ServerWritableStreamImpl( + call, + metadata, + handler.serialize, + request + ); - handler.func(stream); + handler.func(stream); + } catch (err) { + call.sendError(err as ServerErrorResponse) + } } function handleBidiStreaming( From 45e277547f4ad535a6c83d887992f6707f7f816a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Jul 2023 14:55:49 -0700 Subject: [PATCH 526/694] grpc-js: Fix mistakenly committed testing changes --- packages/grpc-js/src/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index bdbdc6e97..7c856ee5b 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -347,13 +347,13 @@ export class Client { code: Status.INTERNAL, details: 'No message received', metadata: status.metadata - }, /*callerStack*/'')); + }, callerStack)); } else { callProperties.callback!(null, responseMessage); } } else { const callerStack = getErrorStackString(callerStackError!); - callProperties.callback!(callErrorFromStatus(status, /*callerStack*/'')); + callProperties.callback!(callErrorFromStatus(status, callerStack)); } /* Avoid retaining the callerStackError object in the call context of * the status event handler. */ From 713a2c9bd1f30ee7f5bab9aabbd5712b3578ee14 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Jul 2023 15:22:15 -0700 Subject: [PATCH 527/694] grpc-js: Enable the noUnusedLocals TypeScript compiler option --- packages/grpc-js/src/channel-credentials.ts | 12 +----- packages/grpc-js/src/client-interceptors.ts | 1 - packages/grpc-js/src/compression-filter.ts | 2 +- packages/grpc-js/src/index.ts | 2 - packages/grpc-js/src/internal-channel.ts | 19 ++++------ .../src/load-balancer-outlier-detection.ts | 4 +- packages/grpc-js/src/load-balancer.ts | 5 +-- packages/grpc-js/src/load-balancing-call.ts | 10 ++--- .../grpc-js/src/max-message-size-filter.ts | 2 +- packages/grpc-js/src/metadata.ts | 5 --- packages/grpc-js/src/object-stream.ts | 2 +- packages/grpc-js/src/resolver-ip.ts | 2 +- packages/grpc-js/src/resolving-call.ts | 4 +- .../grpc-js/src/resolving-load-balancer.ts | 5 +-- packages/grpc-js/src/server-call.ts | 7 +++- packages/grpc-js/src/server.ts | 28 ++++++-------- packages/grpc-js/src/subchannel-call.ts | 8 ---- packages/grpc-js/src/transport.ts | 38 +++++++++---------- .../grpc-js/test/test-call-propagation.ts | 8 ++-- .../grpc-js/test/test-channel-credentials.ts | 7 +--- packages/grpc-js/test/test-channelz.ts | 4 +- packages/grpc-js/test/test-deadline.ts | 8 +--- .../grpc-js/test/test-outlier-detection.ts | 1 - packages/grpc-js/test/test-server.ts | 4 +- packages/grpc-js/tsconfig.json | 3 +- 25 files changed, 70 insertions(+), 121 deletions(-) diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index fd9d7b571..64db3e9eb 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -38,14 +38,6 @@ export type CheckServerIdentityCallback = ( cert: PeerCertificate ) => Error | undefined; -function bufferOrNullEqual(buf1: Buffer | null, buf2: Buffer | null) { - if (buf1 === null && buf2 === null) { - return true; - } else { - return buf1 !== null && buf2 !== null && buf1.equals(buf2); - } -} - /** * Additional peer verification options that can be set when creating * SSL credentials. @@ -196,7 +188,7 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { private verifyOptions: VerifyOptions ) { super(); - this.connectionOptions = { + this.connectionOptions = { secureContext }; // Node asserts that this option is a function, so we cannot pass undefined @@ -225,7 +217,7 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { } if (other instanceof SecureChannelCredentialsImpl) { return ( - this.secureContext === other.secureContext && + this.secureContext === other.secureContext && this.verifyOptions.checkServerIdentity === other.verifyOptions.checkServerIdentity ); } else { diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index d95828550..277a14f59 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -32,7 +32,6 @@ import { import { Status } from './constants'; import { Channel } from './channel'; import { CallOptions } from './client'; -import { CallCredentials } from './call-credentials'; import { ClientMethodDefinition } from './make-client'; import { getErrorMessage } from './error'; diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index f87614114..b8c2e6d1e 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -282,7 +282,7 @@ export class CompressionFilter extends BaseFilter implements Filter { export class CompressionFilterFactory implements FilterFactory { private sharedFilterConfig: SharedCompressionFilterConfig = {}; - constructor(private readonly channel: Channel, private readonly options: ChannelOptions) {} + constructor(channel: Channel, private readonly options: ChannelOptions) {} createFilter(): CompressionFilter { return new CompressionFilter(this.options, this.sharedFilterConfig); } diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 51f394785..70178a89e 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -43,9 +43,7 @@ import { loadPackageDefinition, makeClientConstructor, MethodDefinition, - ProtobufTypeDefinition, Serialize, - ServiceClientConstructor, ServiceDefinition, } from './make-client'; import { Metadata, MetadataOptions, MetadataValue } from './metadata'; diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 14038bd3f..3d2624895 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -20,7 +20,7 @@ import { ChannelOptions } from './channel-options'; import { ResolvingLoadBalancer } from './resolving-load-balancer'; import { SubchannelPool, getSubchannelPool } from './subchannel-pool'; import { ChannelControlHelper } from './load-balancer'; -import { UnavailablePicker, Picker, PickResultType } from './picker'; +import { UnavailablePicker, Picker } from './picker'; import { Metadata } from './metadata'; import { Status, LogVerbosity, Propagate } from './constants'; import { FilterStackFactory } from './filter-stack'; @@ -31,22 +31,19 @@ import { getDefaultAuthority, mapUriDefaultScheme, } from './resolver'; -import { trace, log } from './logging'; +import { trace } from './logging'; import { SubchannelAddress } from './subchannel-address'; import { MaxMessageSizeFilterFactory } from './max-message-size-filter'; import { mapProxyName } from './http_proxy'; -import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; +import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; -import { Filter } from './filter'; import { ConnectivityState } from './connectivity-state'; import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; -import { Subchannel } from './subchannel'; import { LoadBalancingCall } from './load-balancing-call'; import { CallCredentials } from './call-credentials'; -import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from './call-interface'; -import { SubchannelCall } from './subchannel-call'; -import { Deadline, deadlineToString, getDeadlineTimeoutString } from './deadline'; +import { Call, CallStreamOptions, StatusObject } from './call-interface'; +import { Deadline, deadlineToString } from './deadline'; import { ResolvingCall } from './resolving-call'; import { getNextCallNumber } from './call-number'; import { restrictControlPlaneStatusCode } from './control-plane-status'; @@ -112,7 +109,7 @@ class ChannelSubchannelWrapper extends BaseSubchannelWrapper implements Subchann } export class InternalChannel { - + private resolvingLoadBalancer: ResolvingLoadBalancer; private subchannelPool: SubchannelPool; private connectivityState: ConnectivityState = ConnectivityState.IDLE; @@ -376,7 +373,7 @@ export class InternalChannel { trace( LogVerbosity.DEBUG, 'connectivity_state', - '(' + this.channelzRef.id + ') ' + + '(' + this.channelzRef.id + ') ' + uriToString(this.target) + ' ' + ConnectivityState[this.connectivityState] + @@ -601,7 +598,7 @@ export class InternalChannel { /** * Get the channelz reference object for this channel. The returned value is * garbage if channelz is disabled for this channel. - * @returns + * @returns */ getChannelzRef() { return this.channelzRef; diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index ce8668f18..7297000d0 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -20,11 +20,9 @@ import { ConnectivityState } from "./connectivity-state"; import { LogVerbosity, Status } from "./constants"; import { durationToMs, isDuration, msToDuration } from "./duration"; import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental"; -import { BaseFilter, Filter, FilterFactory } from "./filter"; import { getFirstUsableConfig, LoadBalancer, LoadBalancingConfig, validateLoadBalancingConfig } from "./load-balancer"; import { ChildLoadBalancerHandler } from "./load-balancer-child-handler"; -import { PickArgs, Picker, PickResult, PickResultType, QueuePicker, UnavailablePicker } from "./picker"; -import { Subchannel } from "./subchannel"; +import { PickArgs, Picker, PickResult, PickResultType } from "./picker"; import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address"; import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface"; import * as logging from './logging'; diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index 48930c7db..8d1c96981 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -16,7 +16,6 @@ */ import { ChannelOptions } from './channel-options'; -import { Subchannel } from './subchannel'; import { SubchannelAddress } from './subchannel-address'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; @@ -58,8 +57,8 @@ export interface ChannelControlHelper { * parent while letting others pass through to the parent unmodified. This * allows other code to create these children without needing to know about * all of the methods to be passed through. - * @param parent - * @param overrides + * @param parent + * @param overrides */ export function createChildChannelControlHelper(parent: ChannelControlHelper, overrides: Partial): ChannelControlHelper { return { diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index d88bdb809..e9144cb93 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -21,7 +21,6 @@ import { SubchannelCall } from "./subchannel-call"; import { ConnectivityState } from "./connectivity-state"; import { LogVerbosity, Status } from "./constants"; import { Deadline, getDeadlineTimeoutString } from "./deadline"; -import { FilterStack, FilterStackFactory } from "./filter-stack"; import { InternalChannel } from "./internal-channel"; import { Metadata } from "./metadata"; import { PickResultType } from "./picker"; @@ -48,7 +47,6 @@ export class LoadBalancingCall implements Call { private readPending = false; private pendingMessage: {context: MessageContext, message: Buffer} | null = null; private pendingHalfClose = false; - private pendingChildStatus: StatusObject | null = null; private ended = false; private serviceUrl: string; private metadata: Metadata | null = null; @@ -104,9 +102,9 @@ export class LoadBalancingCall implements Call { } this.trace('Pick called') const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation); - const subchannelString = pickResult.subchannel ? - '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : - '' + pickResult.subchannel; + const subchannelString = pickResult.subchannel ? + '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() : + '' + pickResult.subchannel; this.trace( 'Pick result: ' + PickResultType[pickResult.pickResultType] + @@ -280,4 +278,4 @@ export class LoadBalancingCall implements Call { getCallNumber(): number { return this.callNumber; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/max-message-size-filter.ts b/packages/grpc-js/src/max-message-size-filter.ts index 62d01077c..25e4fdc03 100644 --- a/packages/grpc-js/src/max-message-size-filter.ts +++ b/packages/grpc-js/src/max-message-size-filter.ts @@ -29,7 +29,7 @@ export class MaxMessageSizeFilter extends BaseFilter implements Filter { private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; constructor( - private readonly options: ChannelOptions + options: ChannelOptions ) { super(); if ('grpc.max_send_message_length' in options) { diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts index 0dddd9465..bfef51b0d 100644 --- a/packages/grpc-js/src/metadata.ts +++ b/packages/grpc-js/src/metadata.ts @@ -229,11 +229,6 @@ export class Metadata { return result; } - // For compatibility with the other Metadata implementation - private _getCoreRepresentation() { - return this.internalRepr; - } - /** * This modifies the behavior of JSON.stringify to show an object * representation of the metadata map. diff --git a/packages/grpc-js/src/object-stream.ts b/packages/grpc-js/src/object-stream.ts index 22ab8a41f..9289b9d84 100644 --- a/packages/grpc-js/src/object-stream.ts +++ b/packages/grpc-js/src/object-stream.ts @@ -15,7 +15,7 @@ * */ -import { Duplex, Readable, Writable } from 'stream'; +import { Readable, Writable } from 'stream'; import { EmitterAugmentation1 } from './events'; /* eslint-disable @typescript-eslint/no-explicit-any */ diff --git a/packages/grpc-js/src/resolver-ip.ts b/packages/grpc-js/src/resolver-ip.ts index efb0b8dcb..0704131e1 100644 --- a/packages/grpc-js/src/resolver-ip.ts +++ b/packages/grpc-js/src/resolver-ip.ts @@ -42,7 +42,7 @@ class IpResolver implements Resolver { private addresses: SubchannelAddress[] = []; private error: StatusObject | null = null; constructor( - private target: GrpcUri, + target: GrpcUri, private listener: ResolverListener, channelOptions: ChannelOptions ) { diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index f29fb7fd7..683fed5bc 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -18,7 +18,7 @@ import { CallCredentials } from "./call-credentials"; import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; import { LogVerbosity, Propagate, Status } from "./constants"; -import { Deadline, deadlineToString, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; +import { Deadline, deadlineToString, getRelativeTimeout, minDeadline } from "./deadline"; import { FilterStack, FilterStackFactory } from "./filter-stack"; import { InternalChannel } from "./internal-channel"; import { Metadata } from "./metadata"; @@ -276,4 +276,4 @@ export class ResolvingCall implements Call { getCallNumber(): number { return this.callNumber; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index a39606f2c..5194bef46 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -36,7 +36,6 @@ import { SubchannelAddress } from './subchannel-address'; import { GrpcUri, uriToString } from './uri-parser'; import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; import { ChannelOptions } from './channel-options'; -import { PickFirstLoadBalancingConfig } from './load-balancer-pick-first'; const TRACER_NAME = 'resolving_load_balancer'; @@ -44,8 +43,6 @@ function trace(text: string): void { logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); } -const DEFAULT_LOAD_BALANCER_NAME = 'pick_first'; - function getDefaultConfigSelector( serviceConfig: ServiceConfig | null ): ConfigSelector { @@ -137,7 +134,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { constructor( private readonly target: GrpcUri, private readonly channelControlHelper: ChannelControlHelper, - private readonly channelOptions: ChannelOptions, + channelOptions: ChannelOptions, private readonly onSuccessfulResolution: ResolutionCallback, private readonly onFailedResolution: ResolutionFailureCallback ) { diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 48186bc29..fc840bdf9 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -268,6 +268,9 @@ export class ServerDuplexStreamImpl implements ServerDuplexStream { cancelled: boolean; + /* This field appears to be unsued, but it is actually used in _final, which is assiged from + * ServerWritableStreamImpl.prototype._final below. */ + // @ts-ignore noUnusedLocals private trailingMetadata: Metadata; constructor( @@ -419,7 +422,7 @@ export class Http2ServerCallStream< constructor( private stream: http2.ServerHttp2Stream, private handler: Handler, - private options: ChannelOptions + options: ChannelOptions ) { super(); @@ -720,7 +723,7 @@ export class Http2ServerCallStream< [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), ...statusObj.metadata?.toHttp2Headers(), }; - + this.stream.sendTrailers(trailersToSend); this.statusSent = true; }); diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index d19186a75..1a01b30dc 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -141,10 +141,6 @@ interface ChannelzSessionInfo { lastMessageReceivedTimestamp: Date | null; } -interface ChannelzListenerInfo { - ref: SocketRef; -} - export class Server { private http2ServerList: { server: (http2.Http2Server | http2.Http2SecureServer), channelzRef: SocketRef }[] = []; @@ -242,7 +238,7 @@ export class Server { private trace(text: string): void { logging.trace(LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + text); } - + addProtoService(): never { throw new Error('Not implemented. Use addService() instead'); @@ -743,7 +739,7 @@ export class Server { /** * Get the channelz reference object for this server. The returned value is * garbage if channelz is disabled for this server. - * @returns + * @returns */ getChannelzRef() { return this.channelzRef; @@ -792,14 +788,14 @@ export class Server { return handler } - + private _respondWithError>( - err: T, - stream: http2.ServerHttp2Stream, + err: T, + stream: http2.ServerHttp2Stream, channelzSessionInfo: ChannelzSessionInfo | null = null ) { const call = new Http2ServerCallStream(stream, null!, this.options); - + if (err.code === undefined) { err.code = Status.INTERNAL; } @@ -814,7 +810,7 @@ export class Server { private _channelzHandler(stream: http2.ServerHttp2Stream, headers: http2.IncomingHttpHeaders) { const channelzSessionInfo = this.sessions.get(stream.session as http2.ServerHttp2Session); - + this.callTracker.addCallStarted(); channelzSessionInfo?.streamTracker.addCallStarted(); @@ -834,9 +830,9 @@ export class Server { }, stream, channelzSessionInfo) return } - + const call = new Http2ServerCallStream(stream, handler, this.options); - + call.once('callEnd', (code: Status) => { if (code === Status.OK) { this.callTracker.addCallSucceeded(); @@ -844,7 +840,7 @@ export class Server { this.callTracker.addCallFailed(); } }); - + if (channelzSessionInfo) { call.once('streamEnd', (success: boolean) => { if (success) { @@ -954,8 +950,8 @@ export class Server { } this.serverAddressString = serverAddressString - const handler = this.channelzEnabled - ? this._channelzHandler + const handler = this.channelzEnabled + ? this._channelzHandler : this._streamHandler http2Server.on('stream', handler.bind(this)) diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 969282e19..f9c24f6bd 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -23,19 +23,11 @@ import { Metadata } from './metadata'; import { StreamDecoder } from './stream-decoder'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { ServerSurfaceCall } from './server-call'; -import { Deadline } from './deadline'; import { InterceptingListener, MessageContext, StatusObject, WriteCallback } from './call-interface'; import { CallEventTracker, Transport } from './transport'; const TRACER_NAME = 'subchannel_call'; -const { - HTTP2_HEADER_STATUS, - HTTP2_HEADER_CONTENT_TYPE, - NGHTTP2_CANCEL, -} = http2.constants; - /** * https://nodejs.org/api/errors.html#errors_class_systemerror */ diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 2f89d8f28..36176d643 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -46,10 +46,6 @@ const { HTTP2_HEADER_USER_AGENT, } = http2.constants; -/* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't - * have a constant for the max signed 32 bit integer, so this is a simple way - * to calculate it */ -const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); const KEEPALIVE_TIMEOUT_MS = 20000; export interface CallEventTracker { @@ -108,11 +104,6 @@ class Http2Transport implements Transport { // Channelz info private channelzRef: SocketRef; private readonly channelzEnabled: boolean = true; - /** - * Name of the remote server, if it is not the same as the subchannel - * address, i.e. if connecting through an HTTP CONNECT proxy. - */ - private remoteName: string | null = null; private streamTracker = new ChannelzCallTracker(); private keepalivesSent = 0; private messagesSent = 0; @@ -123,7 +114,12 @@ class Http2Transport implements Transport { constructor( private session: http2.ClientHttp2Session, subchannelAddress: SubchannelAddress, - options: ChannelOptions + options: ChannelOptions, + /** + * Name of the remote server, if it is not the same as the subchannel + * address, i.e. if connecting through an HTTP CONNECT proxy. + */ + private remoteName: string | null ) { // Build user-agent string. this.userAgent = [ @@ -133,7 +129,7 @@ class Http2Transport implements Transport { ] .filter((e) => e) .join(' '); // remove falsey values first - + if ('grpc.keepalive_time_ms' in options) { this.keepaliveTimeMs = options['grpc.keepalive_time_ms']!; } @@ -271,7 +267,7 @@ class Http2Transport implements Transport { * @param tooManyPings If true, this was triggered by a GOAWAY with data * indicating that the session was closed becaues the client sent too many * pings. - * @returns + * @returns */ private reportDisconnectToOwner(tooManyPings: boolean) { if (this.disconnectHandled) { @@ -405,11 +401,11 @@ class Http2Transport implements Transport { this.session.state.remoteWindowSize ); this.internalsTrace( - 'session.closed=' + - this.session.closed + - ' session.destroyed=' + - this.session.destroyed + - ' session.socket.destroyed=' + + 'session.closed=' + + this.session.closed + + ' session.destroyed=' + + this.session.destroyed + + ' session.socket.destroyed=' + this.session.socket.destroyed); let eventTracker: CallEventTracker; let call: Http2SubchannelCall; @@ -565,12 +561,12 @@ export class Http2SubchannelConnector implements SubchannelConnector { } }; } - + connectionOptions = { ...connectionOptions, ...address, }; - + /* http2.connect uses the options here: * https://github.com/nodejs/node/blob/70c32a6d190e2b5d7b9ff9d5b6a459d14e8b7d59/lib/internal/http2/core.js#L3028-L3036 * The spread operator overides earlier values with later ones, so any port @@ -596,7 +592,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { session.unref(); session.once('connect', () => { session.removeAllListeners(); - resolve(new Http2Transport(session, address, options)); + resolve(new Http2Transport(session, address, options, remoteName)); this.session = null; }); session.once('close', () => { @@ -666,4 +662,4 @@ export class Http2SubchannelConnector implements SubchannelConnector { this.session?.close(); this.session = null; } -} \ No newline at end of file +} diff --git a/packages/grpc-js/test/test-call-propagation.ts b/packages/grpc-js/test/test-call-propagation.ts index 3ce57be17..4a2619f1f 100644 --- a/packages/grpc-js/test/test-call-propagation.ts +++ b/packages/grpc-js/test/test-call-propagation.ts @@ -165,7 +165,6 @@ describe('Call propagation', () => { describe('Deadlines', () => { it('should work with unary requests', (done) => { done = multiDone(done, 2); - let call: grpc.ClientUnaryCall; proxyServer.addService(Client.service, { unary: (parent: grpc.ServerUnaryCall, callback: grpc.sendUnaryData) => { client.unary(parent.request, {parent: parent, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { @@ -178,7 +177,7 @@ describe('Call propagation', () => { }); const deadline = new Date(); deadline.setMilliseconds(deadline.getMilliseconds() + 100); - call = proxyClient.unary({}, {deadline}, (error: grpc.ServiceError, value: unknown) => { + proxyClient.unary({}, {deadline}, (error: grpc.ServiceError, value: unknown) => { assert(error); assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); done(); @@ -186,7 +185,6 @@ describe('Call propagation', () => { }); it('Should work with client streaming requests', (done) => { done = multiDone(done, 2); - let call: grpc.ClientWritableStream; proxyServer.addService(Client.service, { clientStream: (parent: grpc.ServerReadableStream, callback: grpc.sendUnaryData) => { client.clientStream({parent: parent, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { @@ -199,7 +197,7 @@ describe('Call propagation', () => { }); const deadline = new Date(); deadline.setMilliseconds(deadline.getMilliseconds() + 100); - call = proxyClient.clientStream({deadline, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { + proxyClient.clientStream({deadline, propagate_flags: grpc.propagate.DEADLINE}, (error: grpc.ServiceError, value: unknown) => { assert(error); assert.strictEqual(error.code, grpc.status.DEADLINE_EXCEEDED); done(); @@ -250,4 +248,4 @@ describe('Call propagation', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-channel-credentials.ts b/packages/grpc-js/test/test-channel-credentials.ts index 2b537ac97..62e4c19a1 100644 --- a/packages/grpc-js/test/test-channel-credentials.ts +++ b/packages/grpc-js/test/test-channel-credentials.ts @@ -19,14 +19,11 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; -import * as protoLoader from '@grpc/proto-loader'; import { CallCredentials } from '../src/call-credentials'; import { ChannelCredentials } from '../src/channel-credentials'; import * as grpc from '../src'; import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; -import { TestServiceClient, TestServiceHandlers } from './generated/TestService'; -import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service'; import { assert2, loadProtoFile, mockFunction } from './common'; import { sendUnaryData, ServerUnaryCall, ServiceError } from '../src'; @@ -171,7 +168,7 @@ describe('ChannelCredentials usage', () => { callback(null, call.request); }, }); - + server.bindAsync( 'localhost:0', serverCreds, @@ -209,4 +206,4 @@ describe('ChannelCredentials usage', () => { })); assert2.afterMustCallsSatisfied(done); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-channelz.ts b/packages/grpc-js/test/test-channelz.ts index f14145c37..26c15d47d 100644 --- a/packages/grpc-js/test/test-channelz.ts +++ b/packages/grpc-js/test/test-channelz.ts @@ -21,8 +21,6 @@ import * as grpc from '../src'; import { ProtoGrpcType } from '../src/generated/channelz' import { ChannelzClient } from '../src/generated/grpc/channelz/v1/Channelz'; -import { Channel__Output } from '../src/generated/grpc/channelz/v1/Channel'; -import { Server__Output } from '../src/generated/grpc/channelz/v1/Server'; import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; import { loadProtoFile } from './common'; @@ -318,4 +316,4 @@ describe('Disabling channelz', () => { done(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-deadline.ts b/packages/grpc-js/test/test-deadline.ts index bb6b3ba9b..d117fc52a 100644 --- a/packages/grpc-js/test/test-deadline.ts +++ b/packages/grpc-js/test/test-deadline.ts @@ -19,14 +19,10 @@ import * as assert from 'assert'; import * as grpc from '../src'; import { experimental } from '../src'; -import { ServerCredentials } from '../src'; import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; import { loadProtoFile } from './common'; import ServiceConfig = experimental.ServiceConfig; -const clientInsecureCreds = grpc.credentials.createInsecure(); -const serverInsecureCreds = ServerCredentials.createInsecure(); - const TIMEOUT_SERVICE_CONFIG: ServiceConfig = { loadBalancingConfig: [], methodConfig: [{ @@ -44,7 +40,7 @@ describe('Client with configured timeout', () => { let server: grpc.Server; let Client: ServiceClientConstructor; let client: ServiceClient; - + before(done => { Client = loadProtoFile(__dirname + '/fixtures/test_service.proto').TestService as ServiceClientConstructor; server = new grpc.Server(); @@ -87,4 +83,4 @@ describe('Client with configured timeout', () => { done(); }); }); -}); \ No newline at end of file +}); diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts index d51ccf3fd..a91350fd7 100644 --- a/packages/grpc-js/test/test-outlier-detection.ts +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -20,7 +20,6 @@ import * as path from 'path'; import * as grpc from '../src'; import { loadProtoFile } from './common'; import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection' -import { ServiceClient } from '../src/make-client'; function multiDone(done: Mocha.Done, target: number) { let count = 0; diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index c67ebc4d6..d67307f61 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -686,7 +686,7 @@ describe('Compressed requests', () => { }, ServerStream(call) { - const { metadata, request } = call; + const { request } = call; for (let i = 0; i < 5; i++) { call.write({ count: request.message.length }); @@ -908,7 +908,7 @@ describe('Compressed requests', () => { done(); }) }) - + /* As of Node 16, Writable and Duplex streams validate the encoding * argument to write, and the flags values we are passing there are not * valid. We don't currently have an alternative way to pass that flag diff --git a/packages/grpc-js/tsconfig.json b/packages/grpc-js/tsconfig.json index 310b633c7..5f955a982 100644 --- a/packages/grpc-js/tsconfig.json +++ b/packages/grpc-js/tsconfig.json @@ -7,7 +7,8 @@ "module": "commonjs", "resolveJsonModule": true, "incremental": true, - "types": ["mocha"] + "types": ["mocha"], + "noUnusedLocals": true }, "include": [ "src/**/*.ts", From 493cbaaf45cf6bd47a1b877253c47e9148551058 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 12 Jul 2023 15:23:34 -0700 Subject: [PATCH 528/694] grpc-js: Increment version to 1.8.18 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index d4b822600..a001e617f 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.17", + "version": "1.8.18", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From c5bdd9c398d0d2a5c44be5ca2c4384304ed1a048 Mon Sep 17 00:00:00 2001 From: Cedric Kassen Date: Wed, 12 Jul 2023 21:27:23 +0200 Subject: [PATCH 529/694] remove oversight asyncs and replace safeDeserializeMessage --- packages/grpc-js/src/server-call.ts | 28 ++++++++++++++-------------- packages/grpc-js/src/server.ts | 1 - 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index e4dc13690..fc060de51 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -568,7 +568,7 @@ export class Http2ServerCallStream< this.stream.on('end', onEnd); this.stream.on('error', onEnd); - async function onData(chunk: Buffer) { + function onData(chunk: Buffer) { receivedLength += chunk.byteLength; if (limit !== -1 && receivedLength > limit) { @@ -586,7 +586,7 @@ export class Http2ServerCallStream< body.push(chunk); } - async function onEnd(err?: Error) { + function onEnd(err?: Error) { stream.removeListener('data', onData); stream.removeListener('end', onEnd); stream.removeListener('error', onEnd); @@ -615,13 +615,19 @@ export class Http2ServerCallStream< ); if (Buffer.isBuffer(decompressedMessage)) { - call.safeDeserializeMessage(decompressedMessage, resolve, reject); + call + .deserializeMessageWithInternalError(decompressedMessage) + .then(resolve) + .catch(reject); return; } decompressedMessage.then( decompressed => - call.safeDeserializeMessage(decompressed, resolve, reject), + call + .deserializeMessageWithInternalError(decompressed) + .then(resolve) + .catch(reject), (err: any) => reject( err.code @@ -636,20 +642,14 @@ export class Http2ServerCallStream< }); } - private safeDeserializeMessage( - buffer: Buffer, - resolve: ( - value: void | RequestType | PromiseLike - ) => void, - reject: (reason: any) => void - ) { + private async deserializeMessageWithInternalError(buffer: Buffer) { try { - resolve(this.deserializeMessage(buffer)); + return this.deserializeMessage(buffer); } catch (err) { - reject({ + throw { details: getErrorMessage(err), code: Status.INTERNAL, - }); + }; } } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 8aeacdd54..d1859c395 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -96,7 +96,6 @@ function getUnimplementedStatusResponse( return { code: Status.UNIMPLEMENTED, details: `The server does not implement the method ${methodName}`, - metadata: new Metadata(), }; } From 9c3640f958b6e8ffd86dc02e4e7cba1918f583bf Mon Sep 17 00:00:00 2001 From: Andrew Haines Date: Thu, 13 Jul 2023 10:17:16 +0100 Subject: [PATCH 530/694] proto-loader: Update long dependency to match protobufjs Signed-off-by: Andrew Haines --- packages/proto-loader/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 0ebe1ed32..66c93e96e 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -45,9 +45,8 @@ "proto-loader-gen-types": "./build/bin/proto-loader-gen-types.js" }, "dependencies": { - "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", + "long": "^5.0.0", "protobufjs": "^7.2.4", "yargs": "^17.7.2" }, From 8ed0a50c58414863d6209c27c6c75671cab620db Mon Sep 17 00:00:00 2001 From: Cedric Kassen Date: Thu, 13 Jul 2023 20:59:04 +0200 Subject: [PATCH 531/694] directly pass deserializeMessageWithInternalError to resolve --- packages/grpc-js/src/server-call.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index fc060de51..0e1f94d20 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -615,19 +615,15 @@ export class Http2ServerCallStream< ); if (Buffer.isBuffer(decompressedMessage)) { - call - .deserializeMessageWithInternalError(decompressedMessage) - .then(resolve) - .catch(reject); + resolve( + call.deserializeMessageWithInternalError(decompressedMessage) + ); return; } decompressedMessage.then( decompressed => - call - .deserializeMessageWithInternalError(decompressed) - .then(resolve) - .catch(reject), + resolve(call.deserializeMessageWithInternalError(decompressed)), (err: any) => reject( err.code From 66bcc7a2ccbee34c3d4676fb1c945f866fa2bac9 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 13 Jul 2023 14:20:54 -0700 Subject: [PATCH 532/694] grpc-js: Reformat files and fix lint errors --- packages/grpc-js/src/channel-credentials.ts | 7 +++-- packages/grpc-js/src/client.ts | 30 ++++++++++++------- packages/grpc-js/src/compression-filter.ts | 5 ++-- packages/grpc-js/src/internal-channel.ts | 11 ++++++- .../src/load-balancer-outlier-detection.ts | 16 +++++----- packages/grpc-js/src/load-balancing-call.ts | 20 ++++++++++--- .../grpc-js/src/max-message-size-filter.ts | 4 +-- packages/grpc-js/src/resolver-dns.ts | 4 ++- packages/grpc-js/src/server-call.ts | 1 + packages/grpc-js/src/server.ts | 4 +-- packages/grpc-js/src/transport.ts | 6 +++- .../grpc-js/test/test-channel-credentials.ts | 27 +++++++---------- .../grpc-js/test/test-outlier-detection.ts | 4 +-- 13 files changed, 86 insertions(+), 53 deletions(-) diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index 5c18bb4a8..72b19d65e 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -191,7 +191,7 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { ) { super(); this.connectionOptions = { - secureContext + secureContext, }; // Node asserts that this option is a function, so we cannot pass undefined if (verifyOptions?.checkServerIdentity) { @@ -220,8 +220,9 @@ class SecureChannelCredentialsImpl extends ChannelCredentials { if (other instanceof SecureChannelCredentialsImpl) { return ( this.secureContext === other.secureContext && - this.verifyOptions.checkServerIdentity === other.verifyOptions.checkServerIdentity - ); + this.verifyOptions.checkServerIdentity === + other.verifyOptions.checkServerIdentity + ); } else { return false; } diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index 312ed55ac..e122f6cf4 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -342,11 +342,16 @@ export class Client { if (status.code === Status.OK) { if (responseMessage === null) { const callerStack = getErrorStackString(callerStackError!); - callProperties.callback!(callErrorFromStatus({ - code: Status.INTERNAL, - details: 'No message received', - metadata: status.metadata - }, callerStack)); + callProperties.callback!( + callErrorFromStatus( + { + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata, + }, + callerStack + ) + ); } else { callProperties.callback!(null, responseMessage); } @@ -470,11 +475,16 @@ export class Client { if (status.code === Status.OK) { if (responseMessage === null) { const callerStack = getErrorStackString(callerStackError!); - callProperties.callback!(callErrorFromStatus({ - code: Status.INTERNAL, - details: 'No message received', - metadata: status.metadata - }, callerStack)); + callProperties.callback!( + callErrorFromStatus( + { + code: Status.INTERNAL, + details: 'No message received', + metadata: status.metadata, + }, + callerStack + ) + ); } else { callProperties.callback!(null, responseMessage); } diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 87c9db996..136311ad5 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -305,8 +305,9 @@ export class CompressionFilter extends BaseFilter implements Filter { } export class CompressionFilterFactory - implements FilterFactory { - private sharedFilterConfig: SharedCompressionFilterConfig = {}; + implements FilterFactory +{ + private sharedFilterConfig: SharedCompressionFilterConfig = {}; constructor(channel: Channel, private readonly options: ChannelOptions) {} createFilter(): CompressionFilter { return new CompressionFilter(this.options, this.sharedFilterConfig); diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 678c62242..88dd34741 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -39,7 +39,16 @@ import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; import { ConnectivityState } from './connectivity-state'; -import { ChannelInfo, ChannelRef, ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzChannel, SubchannelRef, unregisterChannelzRef } from './channelz'; +import { + ChannelInfo, + ChannelRef, + ChannelzCallTracker, + ChannelzChildrenTracker, + ChannelzTrace, + registerChannelzChannel, + SubchannelRef, + unregisterChannelzRef, +} from './channelz'; import { LoadBalancingCall } from './load-balancing-call'; import { CallCredentials } from './call-credentials'; import { Call, CallStreamOptions, StatusObject } from './call-interface'; diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 5a67855f6..4abbd0843 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -31,12 +31,7 @@ import { validateLoadBalancingConfig, } from './load-balancer'; import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; -import { - PickArgs, - Picker, - PickResult, - PickResultType, -} from './picker'; +import { PickArgs, Picker, PickResult, PickResultType } from './picker'; import { SubchannelAddress, subchannelAddressToString, @@ -170,8 +165,13 @@ export class OutlierDetectionLoadBalancingConfig failurePercentageEjection: Partial | null, private readonly childPolicy: LoadBalancingConfig[] ) { - if (childPolicy.length > 0 && childPolicy[0].getLoadBalancerName() === 'pick_first') { - throw new Error('outlier_detection LB policy cannot have a pick_first child policy'); + if ( + childPolicy.length > 0 && + childPolicy[0].getLoadBalancerName() === 'pick_first' + ) { + throw new Error( + 'outlier_detection LB policy cannot have a pick_first child policy' + ); } this.intervalMs = intervalMs ?? 10_000; this.baseEjectionTimeMs = baseEjectionTimeMs ?? 30_000; diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index b345241c1..6e9718a7a 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -256,18 +256,30 @@ export class LoadBalancingCall implements Call { ); break; case PickResultType.DROP: - const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); + const { code, details } = restrictControlPlaneStatusCode( + pickResult.status!.code, + pickResult.status!.details + ); setImmediate(() => { - this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'DROP'); + this.outputStatus( + { code, details, metadata: pickResult.status!.metadata }, + 'DROP' + ); }); break; case PickResultType.TRANSIENT_FAILURE: if (this.metadata.getOptions().waitForReady) { this.channel.queueCallForPick(this); } else { - const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details); + const { code, details } = restrictControlPlaneStatusCode( + pickResult.status!.code, + pickResult.status!.details + ); setImmediate(() => { - this.outputStatus({code, details, metadata: pickResult.status!.metadata}, 'PROCESSED'); + this.outputStatus( + { code, details, metadata: pickResult.status!.metadata }, + 'PROCESSED' + ); }); } break; diff --git a/packages/grpc-js/src/max-message-size-filter.ts b/packages/grpc-js/src/max-message-size-filter.ts index 9df9de8b6..b6df374b2 100644 --- a/packages/grpc-js/src/max-message-size-filter.ts +++ b/packages/grpc-js/src/max-message-size-filter.ts @@ -28,9 +28,7 @@ import { Metadata } from './metadata'; export class MaxMessageSizeFilter extends BaseFilter implements Filter { private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; - constructor( - options: ChannelOptions - ) { + constructor(options: ChannelOptions) { super(); if ('grpc.max_send_message_length' in options) { this.maxSendMessageSize = options['grpc.max_send_message_length']!; diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 6b866e947..b55278525 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -286,7 +286,9 @@ class DnsResolver implements Resolver { } catch (err) { this.latestServiceConfigError = { code: Status.UNAVAILABLE, - details: `Parsing service config failed with error ${(err as Error).message}`, + details: `Parsing service config failed with error ${ + (err as Error).message + }`, metadata: new Metadata(), }; } diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 934c58825..894d0f76e 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -270,6 +270,7 @@ export class ServerDuplexStreamImpl cancelled: boolean; /* This field appears to be unsued, but it is actually used in _final, which is assiged from * ServerWritableStreamImpl.prototype._final below. */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore noUnusedLocals private trailingMetadata: Metadata; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 018ec1715..f7d580b92 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -1203,7 +1203,7 @@ async function handleUnary( } ); } catch (err) { - call.sendError(err as ServerErrorResponse) + call.sendError(err as ServerErrorResponse); } } @@ -1260,7 +1260,7 @@ async function handleServerStreaming( handler.func(stream); } catch (err) { - call.sendError(err as ServerErrorResponse) + call.sendError(err as ServerErrorResponse); } } diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 9d784cbdd..a213448b7 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -581,7 +581,11 @@ export class Http2SubchannelConnector implements SubchannelConnector { private isShutdown = false; constructor(private channelTarget: GrpcUri) {} private trace(text: string) { - logging.trace(LogVerbosity.DEBUG, TRACER_NAME, this.channelTarget + ' ' + text); + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + this.channelTarget + ' ' + text + ); } private createSession( address: SubchannelAddress, diff --git a/packages/grpc-js/test/test-channel-credentials.ts b/packages/grpc-js/test/test-channel-credentials.ts index 614c8951e..b05b0d048 100644 --- a/packages/grpc-js/test/test-channel-credentials.ts +++ b/packages/grpc-js/test/test-channel-credentials.ts @@ -173,23 +173,18 @@ describe('ChannelCredentials usage', () => { }, }); - server.bindAsync( - 'localhost:0', - serverCreds, - (err, port) => { - if (err) { - reject(err); - return; - } - client = new echoService( - `localhost:${port}`, - combinedCreds, - {'grpc.ssl_target_name_override': 'foo.test.google.fr', 'grpc.default_authority': 'foo.test.google.fr'} - ); - server.start(); - resolve(); + server.bindAsync('localhost:0', serverCreds, (err, port) => { + if (err) { + reject(err); + return; } - ); + client = new echoService(`localhost:${port}`, combinedCreds, { + 'grpc.ssl_target_name_override': 'foo.test.google.fr', + 'grpc.default_authority': 'foo.test.google.fr', + }); + server.start(); + resolve(); + }); }); }); after(() => { diff --git a/packages/grpc-js/test/test-outlier-detection.ts b/packages/grpc-js/test/test-outlier-detection.ts index a9629a562..78b972303 100644 --- a/packages/grpc-js/test/test-outlier-detection.ts +++ b/packages/grpc-js/test/test-outlier-detection.ts @@ -19,7 +19,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as grpc from '../src'; import { loadProtoFile } from './common'; -import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection' +import { OutlierDetectionLoadBalancingConfig } from '../src/load-balancer-outlier-detection'; function multiDone(done: Mocha.Done, target: number) { let count = 0; @@ -374,7 +374,7 @@ describe('Outlier detection config validation', () => { describe('child_policy', () => { it('Should reject a pick_first child_policy', () => { const loadBalancingConfig = { - child_policy: [{pick_first: {}}] + child_policy: [{ pick_first: {} }], }; assert.throws(() => { OutlierDetectionLoadBalancingConfig.createFromJson(loadBalancingConfig); From cb11e66c59dd6b362c3d3be0935465e2d22f2cff Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Jul 2023 10:45:20 -0700 Subject: [PATCH 533/694] grpc-js: Add channel option to enable TLS tracing --- packages/grpc-js/src/channel-options.ts | 5 +++++ packages/grpc-js/src/server.ts | 2 ++ packages/grpc-js/src/transport.ts | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index 1120ab848..f2bb8bcf7 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -57,6 +57,10 @@ export interface ChannelOptions { 'grpc-node.max_session_memory'?: number; 'grpc.service_config_disable_resolution'?: number; 'grpc.client_idle_timeout_ms'?: number; + /** + * Set the enableTrace option in TLS clients and servers + */ + 'grpc-node.tls_enable_trace'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } @@ -91,6 +95,7 @@ export const recognizedOptions = { 'grpc-node.max_session_memory': true, 'grpc.service_config_disable_resolution': true, 'grpc.client_idle_timeout_ms': true, + 'grpc-node.tls_enable_trace': true, }; export function channelOptionsEqual( diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index f7d580b92..c5bab949e 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -427,6 +427,8 @@ export class Server { serverOptions, creds._getSettings()! ); + secureServerOptions.enableTrace = + this.options['grpc-node.tls_enable_trace'] === 1; http2Server = http2.createSecureServer(secureServerOptions); http2Server.on('secureConnection', (socket: TLSSocket) => { /* These errors need to be handled by the user of Http2SecureServer, diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index a213448b7..5ff5257fa 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -677,6 +677,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { connectionOptions = { ...connectionOptions, ...address, + enableTrace: options['grpc-node.tls_enable_trace'] === 1, }; /* http2.connect uses the options here: @@ -760,6 +761,9 @@ export class Http2SubchannelConnector implements SubchannelConnector { connectionOptions.servername = hostPort?.host ?? targetPath; } } + if (options['grpc-node.tls_enable_trace']) { + connectionOptions.enableTrace = true; + } } return getProxiedConnection(address, options, connectionOptions).then( From 7c3a5fe70cce056b077aea3c7ec5206cdcfa15ac Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Jul 2023 10:58:53 -0700 Subject: [PATCH 534/694] grpc-js: Cancel deadline timer on server when call is cancelled --- packages/grpc-js/src/server-call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 894d0f76e..8c4f0d727 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -452,6 +452,7 @@ export class Http2ServerCallStream< details: 'Cancelled by client', metadata: null, }); + if (this.deadlineTimer) clearTimeout(this.deadlineTimer); } }); From 54409d00f3a7175067596342bb76b9557c55fb4e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 14 Jul 2023 14:15:44 -0700 Subject: [PATCH 535/694] grpc-js: Fix transport trace message formatting --- packages/grpc-js/src/transport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index a213448b7..d5df295bb 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -584,7 +584,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { logging.trace( LogVerbosity.DEBUG, TRACER_NAME, - this.channelTarget + ' ' + text + uriToString(this.channelTarget) + ' ' + text ); } private createSession( From 698d1427c6eec9850d2526f3444eef3251700d7b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 21 Jul 2023 09:45:34 -0700 Subject: [PATCH 536/694] grpc-js: Implement pick_first sticky TF and address list shuffling --- .../grpc-js/src/load-balancer-pick-first.ts | 437 ++++++------- packages/grpc-js/src/subchannel-interface.ts | 9 + packages/grpc-js/src/subchannel.ts | 9 +- packages/grpc-js/test/common.ts | 70 +- packages/grpc-js/test/test-pick-first.ts | 603 ++++++++++++++++++ 5 files changed, 882 insertions(+), 246 deletions(-) create mode 100644 packages/grpc-js/test/test-pick-first.ts diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index e91bb3c5a..0805e5fb2 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -31,11 +31,7 @@ import { PickResultType, UnavailablePicker, } from './picker'; -import { - SubchannelAddress, - subchannelAddressEqual, - subchannelAddressToString, -} from './subchannel-address'; +import { SubchannelAddress } from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; import { @@ -58,21 +54,35 @@ const TYPE_NAME = 'pick_first'; const CONNECTION_DELAY_INTERVAL_MS = 250; export class PickFirstLoadBalancingConfig implements LoadBalancingConfig { + constructor(private readonly shuffleAddressList: boolean) {} + getLoadBalancerName(): string { return TYPE_NAME; } - constructor() {} - toJsonObject(): object { return { - [TYPE_NAME]: {}, + [TYPE_NAME]: { + shuffleAddressList: this.shuffleAddressList, + }, }; } + getShuffleAddressList() { + return this.shuffleAddressList; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any static createFromJson(obj: any) { - return new PickFirstLoadBalancingConfig(); + if ( + 'shuffleAddressList' in obj && + !(typeof obj.shuffleAddressList === 'boolean') + ) { + throw new Error( + 'pick_first config field shuffleAddressList must be a boolean if provided' + ); + } + return new PickFirstLoadBalancingConfig(obj.shuffleAddressList === true); } } @@ -94,24 +104,33 @@ class PickFirstPicker implements Picker { } } -interface ConnectivityStateCounts { - [ConnectivityState.CONNECTING]: number; - [ConnectivityState.IDLE]: number; - [ConnectivityState.READY]: number; - [ConnectivityState.SHUTDOWN]: number; - [ConnectivityState.TRANSIENT_FAILURE]: number; +interface SubchannelChild { + subchannel: SubchannelInterface; + hasReportedTransientFailure: boolean; +} + +/** + * Return a new array with the elements of the input array in a random order + * @param list The input array + * @returns A shuffled array of the elements of list + */ +export function shuffled(list: T[]): T[] { + const result = list.slice(); + for (let i = result.length - 1; i > 1; i--) { + const j = Math.floor(Math.random() * (i + 1)); + const temp = result[i]; + result[i] = result[j]; + result[j] = temp; + } + return result; } export class PickFirstLoadBalancer implements LoadBalancer { - /** - * The list of backend addresses most recently passed to `updateAddressList`. - */ - private latestAddressList: SubchannelAddress[] = []; /** * The list of subchannels this load balancer is currently attempting to * connect to. */ - private subchannels: SubchannelInterface[] = []; + private children: SubchannelChild[] = []; /** * The current connectivity state of the load balancer. */ @@ -121,8 +140,6 @@ export class PickFirstLoadBalancer implements LoadBalancer { * recently started connection attempt. */ private currentSubchannelIndex = 0; - - private subchannelStateCounts: ConnectivityStateCounts; /** * The currently picked subchannel used for making calls. Populated if * and only if the load balancer's current state is READY. In that case, @@ -133,11 +150,13 @@ export class PickFirstLoadBalancer implements LoadBalancer { * Listener callback attached to each subchannel in the `subchannels` list * while establishing a connection. */ - private subchannelStateListener: ConnectivityStateListener; - /** - * Listener callback attached to the current picked subchannel. - */ - private pickedSubchannelStateListener: ConnectivityStateListener; + private subchannelStateListener: ConnectivityStateListener = ( + subchannel, + previousState, + newState + ) => { + this.onSubchannelStateUpdate(subchannel, previousState, newState); + }; /** * Timer reference for the timer tracking when to start */ @@ -145,6 +164,14 @@ export class PickFirstLoadBalancer implements LoadBalancer { private triedAllSubchannels = false; + /** + * The LB policy enters sticky TRANSIENT_FAILURE mode when all + * subchannels have failed to connect at least once, and it stays in that + * mode until a connection attempt is successful. While in sticky TF mode, + * the LB policy continuously attempts to connect to all of its subchannels. + */ + private stickyTransientFailureMode = false; + /** * Load balancer that attempts to connect to each backend in the address list * in order, and picks the first one that connects, using it for every @@ -153,136 +180,88 @@ export class PickFirstLoadBalancer implements LoadBalancer { * this load balancer's owner. */ constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.subchannelStateCounts = { - [ConnectivityState.CONNECTING]: 0, - [ConnectivityState.IDLE]: 0, - [ConnectivityState.READY]: 0, - [ConnectivityState.SHUTDOWN]: 0, - [ConnectivityState.TRANSIENT_FAILURE]: 0, - }; - this.subchannelStateListener = ( - subchannel: SubchannelInterface, - previousState: ConnectivityState, - newState: ConnectivityState - ) => { - this.subchannelStateCounts[previousState] -= 1; - this.subchannelStateCounts[newState] += 1; - /* If the subchannel we most recently attempted to start connecting - * to goes into TRANSIENT_FAILURE, immediately try to start - * connecting to the next one instead of waiting for the connection - * delay timer. */ - if ( - subchannel.getRealSubchannel() === - this.subchannels[this.currentSubchannelIndex].getRealSubchannel() && - newState === ConnectivityState.TRANSIENT_FAILURE - ) { - this.startNextSubchannelConnecting(); - } - if (newState === ConnectivityState.READY) { - this.pickSubchannel(subchannel); - return; + this.connectionDelayTimeout = setTimeout(() => {}, 0); + clearTimeout(this.connectionDelayTimeout); + } + + private allChildrenHaveReportedTF(): boolean { + return this.children.every(child => child.hasReportedTransientFailure); + } + + private calculateAndReportNewState() { + if (this.currentPick) { + this.updateState( + ConnectivityState.READY, + new PickFirstPicker(this.currentPick) + ); + } else if (this.children.length === 0) { + this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); + } else { + if (this.stickyTransientFailureMode) { + this.updateState( + ConnectivityState.TRANSIENT_FAILURE, + new UnavailablePicker() + ); } else { - if ( - this.triedAllSubchannels && - this.subchannelStateCounts[ConnectivityState.IDLE] === - this.subchannels.length - ) { - /* If all of the subchannels are IDLE we should go back to a - * basic IDLE state where there is no subchannel list to avoid - * holding unused resources. We do not reset triedAllSubchannels - * because that is a reminder to request reresolution the next time - * this LB policy needs to connect. */ - this.resetSubchannelList(false); - this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); - return; - } - if (this.currentPick === null) { - if (this.triedAllSubchannels) { - let newLBState: ConnectivityState; - if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) { - newLBState = ConnectivityState.CONNECTING; - } else if ( - this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > - 0 - ) { - newLBState = ConnectivityState.TRANSIENT_FAILURE; - } else { - newLBState = ConnectivityState.IDLE; - } - if (newLBState !== this.currentState) { - if (newLBState === ConnectivityState.TRANSIENT_FAILURE) { - this.updateState(newLBState, new UnavailablePicker()); - } else { - this.updateState(newLBState, new QueuePicker(this)); - } - } - } else { - this.updateState( - ConnectivityState.CONNECTING, - new QueuePicker(this) - ); - } - } + this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); } - }; - this.pickedSubchannelStateListener = ( - subchannel: SubchannelInterface, - previousState: ConnectivityState, - newState: ConnectivityState - ) => { + } + } + + private maybeEnterStickyTransientFailureMode() { + if (this.stickyTransientFailureMode) { + return; + } + if (!this.allChildrenHaveReportedTF()) { + return; + } + this.stickyTransientFailureMode = true; + this.channelControlHelper.requestReresolution(); + for (const { subchannel } of this.children) { + subchannel.startConnecting(); + } + this.calculateAndReportNewState(); + } + + private onSubchannelStateUpdate( + subchannel: SubchannelInterface, + previousState: ConnectivityState, + newState: ConnectivityState + ) { + if (this.currentPick?.realSubchannelEquals(subchannel)) { if (newState !== ConnectivityState.READY) { this.currentPick = null; - subchannel.unref(); - subchannel.removeConnectivityStateListener( - this.pickedSubchannelStateListener - ); - this.channelControlHelper.removeChannelzChild( - subchannel.getChannelzRef() - ); - if (this.subchannels.length > 0) { - if (this.triedAllSubchannels) { - let newLBState: ConnectivityState; - if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) { - newLBState = ConnectivityState.CONNECTING; - } else if ( - this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > - 0 - ) { - newLBState = ConnectivityState.TRANSIENT_FAILURE; - } else { - newLBState = ConnectivityState.IDLE; - } - if (newLBState === ConnectivityState.TRANSIENT_FAILURE) { - this.updateState(newLBState, new UnavailablePicker()); - } else { - this.updateState(newLBState, new QueuePicker(this)); - } - } else { - this.updateState( - ConnectivityState.CONNECTING, - new QueuePicker(this) - ); + this.calculateAndReportNewState(); + this.channelControlHelper.requestReresolution(); + } + return; + } + for (const [index, child] of this.children.entries()) { + if (subchannel.realSubchannelEquals(child.subchannel)) { + if (newState === ConnectivityState.READY) { + this.pickSubchannel(child.subchannel); + } + if (newState === ConnectivityState.TRANSIENT_FAILURE) { + child.hasReportedTransientFailure = true; + this.maybeEnterStickyTransientFailureMode(); + if (index === this.currentSubchannelIndex) { + this.startNextSubchannelConnecting(index + 1); } - } else { - /* We don't need to backoff here because this only happens if a - * subchannel successfully connects then disconnects, so it will not - * create a loop of attempting to connect to an unreachable backend - */ - this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); } + child.subchannel.startConnecting(); + return; } - }; - this.connectionDelayTimeout = setTimeout(() => {}, 0); - clearTimeout(this.connectionDelayTimeout); + } } - private startNextSubchannelConnecting() { - if (this.triedAllSubchannels) { + private startNextSubchannelConnecting(startIndex: number) { + clearTimeout(this.connectionDelayTimeout); + if (this.triedAllSubchannels || this.stickyTransientFailureMode) { return; } - for (const [index, subchannel] of this.subchannels.entries()) { - if (index > this.currentSubchannelIndex) { - const subchannelState = subchannel.getConnectivityState(); + for (const [index, child] of this.children.entries()) { + if (index >= startIndex) { + const subchannelState = child.subchannel.getConnectivityState(); if ( subchannelState === ConnectivityState.IDLE || subchannelState === ConnectivityState.CONNECTING @@ -293,6 +272,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { } } this.triedAllSubchannels = true; + this.maybeEnterStickyTransientFailureMode(); } /** @@ -303,37 +283,43 @@ export class PickFirstLoadBalancer implements LoadBalancer { clearTimeout(this.connectionDelayTimeout); this.currentSubchannelIndex = subchannelIndex; if ( - this.subchannels[subchannelIndex].getConnectivityState() === + this.children[subchannelIndex].subchannel.getConnectivityState() === ConnectivityState.IDLE ) { trace( 'Start connecting to subchannel with address ' + - this.subchannels[subchannelIndex].getAddress() + this.children[subchannelIndex].subchannel.getAddress() ); process.nextTick(() => { - this.subchannels[subchannelIndex].startConnecting(); + this.children[subchannelIndex].subchannel.startConnecting(); }); } this.connectionDelayTimeout = setTimeout(() => { - this.startNextSubchannelConnecting(); - }, CONNECTION_DELAY_INTERVAL_MS); + this.startNextSubchannelConnecting(subchannelIndex + 1); + }, CONNECTION_DELAY_INTERVAL_MS).unref?.(); } private pickSubchannel(subchannel: SubchannelInterface) { + if (subchannel === this.currentPick) { + return; + } trace('Pick subchannel with address ' + subchannel.getAddress()); + this.stickyTransientFailureMode = false; if (this.currentPick !== null) { this.currentPick.unref(); + this.channelControlHelper.removeChannelzChild( + this.currentPick.getChannelzRef() + ); this.currentPick.removeConnectivityStateListener( - this.pickedSubchannelStateListener + this.subchannelStateListener ); } this.currentPick = subchannel; - subchannel.addConnectivityStateListener(this.pickedSubchannelStateListener); subchannel.ref(); this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); this.resetSubchannelList(); clearTimeout(this.connectionDelayTimeout); - this.updateState(ConnectivityState.READY, new PickFirstPicker(subchannel)); + this.calculateAndReportNewState(); } private updateState(newState: ConnectivityState, picker: Picker) { @@ -346,115 +332,80 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.channelControlHelper.updateState(newState, picker); } - private resetSubchannelList(resetTriedAllSubchannels = true) { - for (const subchannel of this.subchannels) { - subchannel.removeConnectivityStateListener(this.subchannelStateListener); - subchannel.unref(); + private resetSubchannelList() { + for (const child of this.children) { + if (child.subchannel !== this.currentPick) { + /* The connectivity state listener is the same whether the subchannel + * is in the list of children or it is the currentPick, so if it is in + * both, removing it here would cause problems. In particular, that + * always happens immediately after the subchannel is picked. */ + child.subchannel.removeConnectivityStateListener( + this.subchannelStateListener + ); + } + /* Refs are counted independently for the children list and the + * currentPick, so we call unref whether or not the child is the + * currentPick. Channelz child references are also refcounted, so + * removeChannelzChild can be handled the same way. */ + child.subchannel.unref(); this.channelControlHelper.removeChannelzChild( - subchannel.getChannelzRef() + child.subchannel.getChannelzRef() ); } this.currentSubchannelIndex = 0; - this.subchannelStateCounts = { - [ConnectivityState.CONNECTING]: 0, - [ConnectivityState.IDLE]: 0, - [ConnectivityState.READY]: 0, - [ConnectivityState.SHUTDOWN]: 0, - [ConnectivityState.TRANSIENT_FAILURE]: 0, - }; - this.subchannels = []; - if (resetTriedAllSubchannels) { - this.triedAllSubchannels = false; - } + this.children = []; + this.triedAllSubchannels = false; } - /** - * Start connecting to the address list most recently passed to - * `updateAddressList`. - */ - private connectToAddressList(): void { - this.resetSubchannelList(); - trace( - 'Connect to address list ' + - this.latestAddressList.map(address => - subchannelAddressToString(address) - ) - ); - this.subchannels = this.latestAddressList.map(address => - this.channelControlHelper.createSubchannel(address, {}) - ); - for (const subchannel of this.subchannels) { + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig + ): void { + if (!(lbConfig instanceof PickFirstLoadBalancingConfig)) { + return; + } + /* Previously, an update would be discarded if it was identical to the + * previous update, to minimize churn. Now the DNS resolver is + * rate-limited, so that is less of a concern. */ + if (lbConfig.getShuffleAddressList()) { + addressList = shuffled(addressList); + } + const newChildrenList = addressList.map(address => ({ + subchannel: this.channelControlHelper.createSubchannel(address, {}), + hasReportedTransientFailure: false, + })); + /* Ref each subchannel before resetting the list, to ensure that + * subchannels shared between the list don't drop to 0 refs during the + * transition. */ + for (const { subchannel } of newChildrenList) { subchannel.ref(); this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); } - for (const subchannel of this.subchannels) { + this.resetSubchannelList(); + this.children = newChildrenList; + for (const { subchannel } of this.children) { subchannel.addConnectivityStateListener(this.subchannelStateListener); - this.subchannelStateCounts[subchannel.getConnectivityState()] += 1; if (subchannel.getConnectivityState() === ConnectivityState.READY) { this.pickSubchannel(subchannel); - this.resetSubchannelList(); return; } } - for (const [index, subchannel] of this.subchannels.entries()) { - const subchannelState = subchannel.getConnectivityState(); + for (const child of this.children) { if ( - subchannelState === ConnectivityState.IDLE || - subchannelState === ConnectivityState.CONNECTING + child.subchannel.getConnectivityState() === + ConnectivityState.TRANSIENT_FAILURE ) { - this.startConnecting(index); - if (this.currentPick === null) { - this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); - } - return; + child.hasReportedTransientFailure = true; } } - // If the code reaches this point, every subchannel must be in TRANSIENT_FAILURE - if (this.currentPick === null) { - this.updateState( - ConnectivityState.TRANSIENT_FAILURE, - new UnavailablePicker() - ); - } - } - - updateAddressList( - addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig - ): void { - // lbConfig has no useful information for pick first load balancing - /* To avoid unnecessary churn, we only do something with this address list - * if we're not currently trying to establish a connection, or if the new - * address list is different from the existing one */ - if ( - this.subchannels.length === 0 || - this.latestAddressList.length !== addressList.length || - !this.latestAddressList.every( - (value, index) => - addressList[index] && - subchannelAddressEqual(addressList[index], value) - ) - ) { - this.latestAddressList = addressList; - this.connectToAddressList(); - } + this.startNextSubchannelConnecting(0); + this.calculateAndReportNewState(); } exitIdle() { - if ( - this.currentState === ConnectivityState.IDLE || - this.triedAllSubchannels - ) { - this.channelControlHelper.requestReresolution(); - } - for (const subchannel of this.subchannels) { - subchannel.startConnecting(); - } - if (this.currentState === ConnectivityState.IDLE) { - if (this.latestAddressList.length > 0) { - this.connectToAddressList(); - } - } + /* The pick_first LB policy is only in the IDLE state if it has no + * addresses to try to connect to and it has no picked subchannel. + * In that case, there is no meaningful action that can be taken here. */ } resetBackoff() { @@ -470,9 +421,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { * does not impact this function. */ const currentPick = this.currentPick; currentPick.unref(); - currentPick.removeConnectivityStateListener( - this.pickedSubchannelStateListener - ); + currentPick.removeConnectivityStateListener(this.subchannelStateListener); this.channelControlHelper.removeChannelzChild( currentPick.getChannelzRef() ); diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts index 557d62870..9b947ad32 100644 --- a/packages/grpc-js/src/subchannel-interface.ts +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -49,6 +49,12 @@ export interface SubchannelInterface { * If this is a wrapper, return the wrapped subchannel, otherwise return this */ getRealSubchannel(): Subchannel; + /** + * Returns true if this and other both proxy the same underlying subchannel. + * Can be used instead of directly accessing getRealSubchannel to allow mocks + * to avoid implementing getRealSubchannel + */ + realSubchannelEquals(other: SubchannelInterface): boolean; } export abstract class BaseSubchannelWrapper implements SubchannelInterface { @@ -84,4 +90,7 @@ export abstract class BaseSubchannelWrapper implements SubchannelInterface { getRealSubchannel(): Subchannel { return this.child.getRealSubchannel(); } + realSubchannelEquals(other: SubchannelInterface): boolean { + return this.getRealSubchannel() === other.getRealSubchannel(); + } } diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 480314e4a..6fad9500a 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -36,7 +36,10 @@ import { ChannelzCallTracker, unregisterChannelzRef, } from './channelz'; -import { ConnectivityStateListener } from './subchannel-interface'; +import { + ConnectivityStateListener, + SubchannelInterface, +} from './subchannel-interface'; import { SubchannelCallInterceptingListener } from './subchannel-call'; import { SubchannelCall } from './subchannel-call'; import { CallEventTracker, SubchannelConnector, Transport } from './transport'; @@ -462,6 +465,10 @@ export class Subchannel { return this; } + realSubchannelEquals(other: SubchannelInterface): boolean { + return other.getRealSubchannel() === this; + } + throttleKeepalive(newKeepaliveTime: number) { if (newKeepaliveTime > this.keepaliveTime) { this.keepaliveTime = newKeepaliveTime; diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index 26192d3eb..d15a9d5ed 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -27,6 +27,10 @@ import { loadPackageDefinition, } from '../src/make-client'; import { readFileSync } from 'fs'; +import { SubchannelInterface } from '../src/subchannel-interface'; +import { SubchannelRef } from '../src/channelz'; +import { Subchannel } from '../src/subchannel'; +import { ConnectivityState } from '../src/connectivity-state'; const protoLoaderOptions = { keepCase: true, @@ -119,7 +123,7 @@ export class TestClient { this.client.waitForReady(deadline, callback); } - sendRequest(callback: (error: grpc.ServiceError) => void) { + sendRequest(callback: (error?: grpc.ServiceError) => void) { this.client.echo({}, callback); } @@ -132,4 +136,68 @@ export class TestClient { } } +/** + * A mock subchannel that transitions between states on command, to test LB + * policy behavior + */ +export class MockSubchannel implements SubchannelInterface { + private state: grpc.connectivityState; + private listeners: Set = + new Set(); + constructor( + private readonly address: string, + initialState: grpc.connectivityState = grpc.connectivityState.IDLE + ) { + this.state = initialState; + } + getConnectivityState(): grpc.connectivityState { + return this.state; + } + addConnectivityStateListener( + listener: grpc.experimental.ConnectivityStateListener + ): void { + this.listeners.add(listener); + } + removeConnectivityStateListener( + listener: grpc.experimental.ConnectivityStateListener + ): void { + this.listeners.delete(listener); + } + transitionToState(nextState: grpc.connectivityState) { + grpc.experimental.trace( + grpc.logVerbosity.DEBUG, + 'subchannel', + this.address + + ' ' + + ConnectivityState[this.state] + + ' -> ' + + ConnectivityState[nextState] + ); + for (const listener of this.listeners) { + listener(this, this.state, nextState, 0); + } + this.state = nextState; + } + startConnecting(): void {} + getAddress(): string { + return this.address; + } + throttleKeepalive(newKeepaliveTime: number): void {} + ref(): void {} + unref(): void {} + getChannelzRef(): SubchannelRef { + return { + kind: 'subchannel', + id: -1, + name: this.address, + }; + } + getRealSubchannel(): Subchannel { + throw new Error('Method not implemented.'); + } + realSubchannelEquals(other: grpc.experimental.SubchannelInterface): boolean { + return this === other; + } +} + export { assert2 }; diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts new file mode 100644 index 000000000..e9e9e5601 --- /dev/null +++ b/packages/grpc-js/test/test-pick-first.ts @@ -0,0 +1,603 @@ +/* + * 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. + * + */ + +import * as assert from 'assert'; + +import { ConnectivityState } from '../src/connectivity-state'; +import { + ChannelControlHelper, + createChildChannelControlHelper, +} from '../src/load-balancer'; +import { + PickFirstLoadBalancer, + PickFirstLoadBalancingConfig, + shuffled, +} from '../src/load-balancer-pick-first'; +import { Metadata } from '../src/metadata'; +import { Picker } from '../src/picker'; +import { + SubchannelAddress, + subchannelAddressToString, +} from '../src/subchannel-address'; +import { MockSubchannel, TestClient, TestServer } from './common'; + +function updateStateCallBackForExpectedStateSequence( + expectedStateSequence: ConnectivityState[], + done: Mocha.Done +) { + const actualStateSequence: ConnectivityState[] = []; + let lastPicker: Picker | null = null; + return (connectivityState: ConnectivityState, picker: Picker) => { + // Ignore duplicate state transitions + if ( + connectivityState === actualStateSequence[actualStateSequence.length - 1] + ) { + // Ignore READY duplicate state transitions if the picked subchannel is the same + if ( + connectivityState !== ConnectivityState.READY || + lastPicker?.pick({ extraPickInfo: {}, metadata: new Metadata() }) + ?.subchannel === + picker.pick({ extraPickInfo: {}, metadata: new Metadata() }) + .subchannel + ) { + return; + } + } + if ( + expectedStateSequence[actualStateSequence.length] !== connectivityState + ) { + done( + new Error( + `Unexpected state ${ + ConnectivityState[connectivityState] + } after [${actualStateSequence.map( + value => ConnectivityState[value] + )}]` + ) + ); + } + actualStateSequence.push(connectivityState); + lastPicker = picker; + if (actualStateSequence.length === expectedStateSequence.length) { + done(); + } + }; +} + +describe('Shuffler', () => { + it('Should maintain the multiset of elements from the original array', () => { + const originalArray = [1, 2, 2, 3, 3, 3, 4, 4, 5]; + for (let i = 0; i < 100; i++) { + assert.deepStrictEqual( + shuffled(originalArray).sort((a, b) => a - b), + originalArray + ); + } + }); +}); + +describe('pick_first load balancing policy', () => { + const config = new PickFirstLoadBalancingConfig(false); + let subchannels: MockSubchannel[] = []; + const baseChannelControlHelper: ChannelControlHelper = { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress) + ); + subchannels.push(subchannel); + return subchannel; + }, + addChannelzChild: () => {}, + removeChannelzChild: () => {}, + requestReresolution: () => {}, + updateState: () => {}, + }; + beforeEach(() => { + subchannels = []; + }); + it('Should report READY when a subchannel connects', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.CONNECTING, ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.READY); + }); + }); + it('Should report READY when updated with a subchannel that is already READY', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + ConnectivityState.READY + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + }); + it('Should stay CONNECTING if only some subchannels fail to connect', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.CONNECTING], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + config + ); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + }); + }); + it('Should enter TRANSIENT_FAILURE when subchannels fail to connect', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.CONNECTING, ConnectivityState.TRANSIENT_FAILURE], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + config + ); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + }); + process.nextTick(() => { + subchannels[1].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + }); + }); + it('Should stay in TRANSIENT_FAILURE if subchannels go back to CONNECTING', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.CONNECTING, ConnectivityState.TRANSIENT_FAILURE], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + config + ); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + process.nextTick(() => { + subchannels[1].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.CONNECTING); + process.nextTick(() => { + subchannels[1].transitionToState(ConnectivityState.CONNECTING); + }); + }); + }); + }); + }); + it('Should immediately enter TRANSIENT_FAILURE if subchannels start in TRANSIENT_FAILURE', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + ConnectivityState.TRANSIENT_FAILURE + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.TRANSIENT_FAILURE], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + config + ); + }); + it('Should enter READY if a subchannel connects after entering TRANSIENT_FAILURE mode', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + ConnectivityState.TRANSIENT_FAILURE + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.TRANSIENT_FAILURE, ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + config + ); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.READY); + }); + }); + it('Should stay in TRANSIENT_FAILURE after an address update with non-READY subchannels', done => { + let currentStartState = ConnectivityState.TRANSIENT_FAILURE; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.TRANSIENT_FAILURE], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + config + ); + process.nextTick(() => { + currentStartState = ConnectivityState.CONNECTING; + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 3 }, + { host: 'localhost', port: 4 }, + ], + config + ); + }); + }); + it('Should transition from TRANSIENT_FAILURE to READY after an address update with a READY subchannel', done => { + let currentStartState = ConnectivityState.TRANSIENT_FAILURE; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.TRANSIENT_FAILURE, ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + config + ); + process.nextTick(() => { + currentStartState = ConnectivityState.READY; + pickFirst.updateAddressList([{ host: 'localhost', port: 3 }], config); + }); + }); + it('Should transition from READY to IDLE if the connected subchannel disconnects', done => { + const currentStartState = ConnectivityState.READY; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.READY, ConnectivityState.IDLE], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.IDLE); + }); + }); + it('Should transition from READY to CONNECTING if the connected subchannel disconnects after an update', done => { + let currentStartState = ConnectivityState.READY; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.READY, ConnectivityState.CONNECTING], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + currentStartState = ConnectivityState.IDLE; + pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.IDLE); + }); + }); + }); + it('Should transition from READY to TRANSIENT_FAILURE if the connected subchannel disconnects and the update fails', done => { + let currentStartState = ConnectivityState.READY; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.READY, ConnectivityState.TRANSIENT_FAILURE], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + currentStartState = ConnectivityState.TRANSIENT_FAILURE; + pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.IDLE); + }); + }); + }); + it('Should transition from READY to READY if a subchannel is connected and an update has a connected subchannel', done => { + const currentStartState = ConnectivityState.READY; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.READY, ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.IDLE); + }); + }); + }); + describe('Address list randomization', () => { + const shuffleConfig = new PickFirstLoadBalancingConfig(true); + it('Should pick different subchannels after multiple updates', done => { + const pickedSubchannels: Set = new Set(); + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + ConnectivityState.READY + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: (connectivityState, picker) => { + if (connectivityState === ConnectivityState.READY) { + const pickedSubchannel = picker.pick({ + extraPickInfo: {}, + metadata: new Metadata(), + }).subchannel; + if (pickedSubchannel) { + pickedSubchannels.add(pickedSubchannel.getAddress()); + } + } + }, + } + ); + const addresses: SubchannelAddress[] = []; + for (let i = 0; i < 10; i++) { + addresses.push({ host: 'localhost', port: i + 1 }); + } + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + /* Pick from 10 subchannels 5 times, with address randomization enabled, + * and verify that at least two different subchannels are picked. The + * probability choosing the same address every time is 1/10,000, which + * I am considering an acceptable flake rate */ + pickFirst.updateAddressList(addresses, shuffleConfig); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, shuffleConfig); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, shuffleConfig); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, shuffleConfig); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, shuffleConfig); + process.nextTick(() => { + assert(pickedSubchannels.size > 1); + done(); + }); + }); + }); + }); + }); + }); + it('Should pick the same subchannel if address randomization is disabled', done => { + /* This is the same test as the previous one, except using the config + * that does not enable address randomization. In this case, false + * positive probability is 1/10,000. */ + const pickedSubchannels: Set = new Set(); + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + ConnectivityState.READY + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: (connectivityState, picker) => { + if (connectivityState === ConnectivityState.READY) { + const pickedSubchannel = picker.pick({ + extraPickInfo: {}, + metadata: new Metadata(), + }).subchannel; + if (pickedSubchannel) { + pickedSubchannels.add(pickedSubchannel.getAddress()); + } + } + }, + } + ); + const addresses: SubchannelAddress[] = []; + for (let i = 0; i < 10; i++) { + addresses.push({ host: 'localhost', port: i + 1 }); + } + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList(addresses, config); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, config); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, config); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, config); + process.nextTick(() => { + pickFirst.updateAddressList(addresses, config); + process.nextTick(() => { + assert(pickedSubchannels.size === 1); + done(); + }); + }); + }); + }); + }); + }); + describe('End-to-end functionality', () => { + const serviceConfig = { + methodConfig: [], + loadBalancingConfig: [ + { + pick_first: { + shuffleAddressList: true, + }, + }, + ], + }; + let server: TestServer; + let client: TestClient; + before(async () => { + server = new TestServer(false); + await server.start(); + client = new TestClient(server.port!, false, { + 'grpc.service_config': JSON.stringify(serviceConfig), + }); + }); + after(() => { + client.close(); + server.shutdown(); + }); + it('Should still work with shuffleAddressList set', done => { + client.sendRequest(error => { + done(error); + }); + }); + }); + }); +}); From 2e9060385c6578a8e69687c917015b9eaf475f25 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Jul 2023 11:20:00 -0700 Subject: [PATCH 537/694] grpc-js: Fix keepalive ping timing after inactivity --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/transport.ts | 77 +++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index a001e617f..33096012c 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.18", + "version": "1.8.19", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 36176d643..a3408d836 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -81,7 +81,12 @@ class Http2Transport implements Transport { /** * Timer reference for timeout that indicates when to send the next ping */ - private keepaliveIntervalId: NodeJS.Timer; + private keepaliveTimerId: NodeJS.Timer | null = null; + /** + * Indicates that the keepalive timer ran out while there were no active + * calls, and a ping should be sent the next time a call starts. + */ + private pendingSendKeepalivePing = false; /** * Timer reference tracking when the most recent ping will be considered lost */ @@ -142,10 +147,8 @@ class Http2Transport implements Transport { } else { this.keepaliveWithoutCalls = false; } - this.keepaliveIntervalId = setTimeout(() => {}, 0); - clearTimeout(this.keepaliveIntervalId); if (this.keepaliveWithoutCalls) { - this.startKeepalivePings(); + this.maybeStartKeepalivePingTimer(); } this.subchannelAddressString = subchannelAddressToString(subchannelAddress); @@ -295,6 +298,14 @@ class Http2Transport implements Transport { this.disconnectListeners.push(listener); } + private clearKeepaliveTimer() { + if (!this.keepaliveTimerId) { + return; + } + clearTimeout(this.keepaliveTimerId); + this.keepaliveTimerId = null; + } + private clearKeepaliveTimeout() { if (!this.keepaliveTimeoutId) { return; @@ -303,7 +314,16 @@ class Http2Transport implements Transport { this.keepaliveTimeoutId = null; } - private sendPing() { + private canSendPing() { + return this.keepaliveTimeMs > 0 && (this.keepaliveWithoutCalls || this.activeCalls.size > 0); + } + + private maybeSendPing() { + this.clearKeepaliveTimer(); + if (!this.canSendPing()) { + this.pendingSendKeepalivePing = true; + return; + } if (this.channelzEnabled) { this.keepalivesSent += 1; } @@ -320,6 +340,7 @@ class Http2Transport implements Transport { (err: Error | null, duration: number, payload: Buffer) => { this.keepaliveTrace('Received ping response'); this.clearKeepaliveTimeout(); + this.maybeStartKeepalivePingTimer(); } ); } catch (e) { @@ -329,25 +350,34 @@ class Http2Transport implements Transport { } } - private startKeepalivePings() { - if (this.keepaliveTimeMs < 0) { + /** + * Starts the keepalive ping timer if appropriate. If the timer already ran + * out while there were no active requests, instead send a ping immediately. + * If the ping timer is already running or a ping is currently in flight, + * instead do nothing and wait for them to resolve. + */ + private maybeStartKeepalivePingTimer() { + if (!this.canSendPing()) { return; } - this.keepaliveIntervalId = setInterval(() => { - this.sendPing(); - }, this.keepaliveTimeMs); - this.keepaliveIntervalId.unref?.(); - /* Don't send a ping immediately because whatever caused us to start - * sending pings should also involve some network activity. */ + if (this.pendingSendKeepalivePing) { + this.pendingSendKeepalivePing = false; + this.maybeSendPing(); + } else if (!this.keepaliveTimerId && !this.keepaliveTimeoutId) { + this.keepaliveTrace('Starting keepalive timer for ' + this.keepaliveTimeMs + 'ms'); + this.keepaliveTimerId = setTimeout(() => { + this.maybeSendPing(); + }, this.keepaliveTimeMs).unref?.(); + } + /* Otherwise, there is already either a keepalive timer or a ping pending, + * wait for those to resolve. */ } - /** - * Stop keepalive pings when terminating a connection. This discards the - * outstanding ping timeout, so it should not be called if the same - * connection will still be used. - */ private stopKeepalivePings() { - clearInterval(this.keepaliveIntervalId); + if (this.keepaliveTimerId) { + clearTimeout(this.keepaliveTimerId); + this.keepaliveTimerId = null; + } this.clearKeepaliveTimeout(); } @@ -355,20 +385,17 @@ class Http2Transport implements Transport { this.activeCalls.delete(call); if (this.activeCalls.size === 0) { this.session.unref(); - if (!this.keepaliveWithoutCalls) { - this.stopKeepalivePings(); - } } } private addActiveCall(call: Http2SubchannelCall) { - if (this.activeCalls.size === 0) { + this.activeCalls.add(call); + if (this.activeCalls.size === 1) { this.session.ref(); if (!this.keepaliveWithoutCalls) { - this.startKeepalivePings(); + this.maybeStartKeepalivePingTimer(); } } - this.activeCalls.add(call); } createCall(metadata: Metadata, host: string, method: string, listener: SubchannelCallInterceptingListener, subchannelCallStatsTracker: Partial): Http2SubchannelCall { From 42a02749eb4a067dbdb84fc111ab10ef09bc2ec4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Jul 2023 13:08:55 -0700 Subject: [PATCH 538/694] grpc-js: Fix compilation error from new @types/node version --- packages/grpc-js/src/server-call.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index fc840bdf9..ff0a10336 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -954,8 +954,8 @@ export class Http2ServerCallStream< } getPeer(): string { - const socket = this.stream.session.socket; - if (socket.remoteAddress) { + const socket = this.stream.session?.socket; + if (socket?.remoteAddress) { if (socket.remotePort) { return `${socket.remoteAddress}:${socket.remotePort}`; } else { From 71d035b5bf1a6fa299f1362a8e73caf446df16db Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Jul 2023 13:54:30 -0700 Subject: [PATCH 539/694] Fix formatting --- packages/grpc-js/src/transport.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 16f889c33..aa17161fd 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -395,7 +395,10 @@ class Http2Transport implements Transport { } private canSendPing() { - return this.keepaliveTimeMs > 0 && (this.keepaliveWithoutCalls || this.activeCalls.size > 0); + return ( + this.keepaliveTimeMs > 0 && + (this.keepaliveWithoutCalls || this.activeCalls.size > 0) + ); } private maybeSendPing() { @@ -446,7 +449,9 @@ class Http2Transport implements Transport { this.pendingSendKeepalivePing = false; this.maybeSendPing(); } else if (!this.keepaliveTimerId && !this.keepaliveTimeoutId) { - this.keepaliveTrace('Starting keepalive timer for ' + this.keepaliveTimeMs + 'ms'); + this.keepaliveTrace( + 'Starting keepalive timer for ' + this.keepaliveTimeMs + 'ms' + ); this.keepaliveTimerId = setTimeout(() => { this.maybeSendPing(); }, this.keepaliveTimeMs).unref?.(); From 66cd8519bd05097faa20a2deb72309c9431b0f4b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 24 Jul 2023 16:00:13 -0700 Subject: [PATCH 540/694] grpc-js: pick_first: Properly dispose of current pick when it disconnects --- .../grpc-js/src/load-balancer-pick-first.ts | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 0805e5fb2..08971980b 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -223,6 +223,21 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.calculateAndReportNewState(); } + private removeCurrentPick() { + if (this.currentPick !== null) { + /* Unref can cause a state change, which can cause a change in the value + * of this.currentPick, so we hold a local reference to make sure that + * does not impact this function. */ + const currentPick = this.currentPick; + this.currentPick = null; + currentPick.unref(); + currentPick.removeConnectivityStateListener(this.subchannelStateListener); + this.channelControlHelper.removeChannelzChild( + currentPick.getChannelzRef() + ); + } + } + private onSubchannelStateUpdate( subchannel: SubchannelInterface, previousState: ConnectivityState, @@ -230,7 +245,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { ) { if (this.currentPick?.realSubchannelEquals(subchannel)) { if (newState !== ConnectivityState.READY) { - this.currentPick = null; + this.removeCurrentPick(); this.calculateAndReportNewState(); this.channelControlHelper.requestReresolution(); } @@ -415,17 +430,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { destroy() { this.resetSubchannelList(); - if (this.currentPick !== null) { - /* Unref can cause a state change, which can cause a change in the value - * of this.currentPick, so we hold a local reference to make sure that - * does not impact this function. */ - const currentPick = this.currentPick; - currentPick.unref(); - currentPick.removeConnectivityStateListener(this.subchannelStateListener); - this.channelControlHelper.removeChannelzChild( - currentPick.getChannelzRef() - ); - } + this.removeCurrentPick(); } getTypeName(): string { From 6d979565492916de8106b6d7f6bfbc1f1fb63cf4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 25 Jul 2023 09:36:58 -0700 Subject: [PATCH 541/694] grpc-js: Fix a crash when grpc.keepalive_permit_without_calls is set --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/transport.ts | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 33096012c..93b2f7826 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.19", + "version": "1.8.20", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index a3408d836..4f26e96e2 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -126,6 +126,14 @@ class Http2Transport implements Transport { */ private remoteName: string | null ) { + /* Populate subchannelAddressString and channelzRef before doing anything + * else, because they are used in the trace methods. */ + this.subchannelAddressString = subchannelAddressToString(subchannelAddress); + + if (options['grpc.enable_channelz'] === 0) { + this.channelzEnabled = false; + } + this.channelzRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); // Build user-agent string. this.userAgent = [ options['grpc.primary_user_agent'], @@ -147,16 +155,6 @@ class Http2Transport implements Transport { } else { this.keepaliveWithoutCalls = false; } - if (this.keepaliveWithoutCalls) { - this.maybeStartKeepalivePingTimer(); - } - - this.subchannelAddressString = subchannelAddressToString(subchannelAddress); - - if (options['grpc.enable_channelz'] === 0) { - this.channelzEnabled = false; - } - this.channelzRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); session.once('close', () => { this.trace('session closed'); @@ -205,6 +203,11 @@ class Http2Transport implements Transport { ); }); } + /* Start the keepalive timer last, because this can trigger trace logs, + * which should only happen after everything else is set up. */ + if (this.keepaliveWithoutCalls) { + this.maybeStartKeepalivePingTimer(); + } } private getChannelzInfo(): SocketInfo { From e43fa716192132df2eb9f1b167a9f04777699ceb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 25 Jul 2023 10:11:45 -0700 Subject: [PATCH 542/694] Fix formatting --- packages/grpc-js/src/transport.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 0aca0c73c..c55a170b8 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -160,7 +160,11 @@ class Http2Transport implements Transport { if (options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; } - this.channelzRef = registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled); + this.channelzRef = registerChannelzSocket( + this.subchannelAddressString, + () => this.getChannelzInfo(), + this.channelzEnabled + ); // Build user-agent string. this.userAgent = [ options['grpc.primary_user_agent'], From 5759b70639e4e8ca74e529c7b7bbde8c2cf02318 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 27 Jul 2023 11:11:10 -0700 Subject: [PATCH 543/694] Fix some issues with the benchmark code --- test/any_grpc.js | 2 +- test/package.json | 4 +- test/performance/benchmark_server.js | 3 +- test/performance/driver.js | 82 ++++++++++++++++++++++++++++ test/performance/worker.js | 4 +- 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 test/performance/driver.js diff --git a/test/any_grpc.js b/test/any_grpc.js index 2f161ff7a..ec0b2b743 100644 --- a/test/any_grpc.js +++ b/test/any_grpc.js @@ -25,7 +25,7 @@ function getImplementation(globalField) { const impl = global[globalField]; if (impl === 'js') { - return require(`../packages/grpc-${impl}`); + return require('../packages/grpc-js'); } else if (impl === 'native') { return require('grpc'); } diff --git a/test/package.json b/test/package.json index 4f42f7bf7..963093bc0 100644 --- a/test/package.json +++ b/test/package.json @@ -16,8 +16,10 @@ "dependencies": { "express": "^4.16.3", "google-auth-library": "^6.1.0", - "grpc": "^1.24.2", "lodash": "^4.17.4", "poisson-process": "^1.0.0" + }, + "optionalDependencies": { + "grpc": "^1.24.2" } } diff --git a/test/performance/benchmark_server.js b/test/performance/benchmark_server.js index 64128b9d0..0d6c7178c 100644 --- a/test/performance/benchmark_server.js +++ b/test/performance/benchmark_server.js @@ -154,8 +154,9 @@ util.inherits(BenchmarkServer, EventEmitter); * Start the benchmark server. */ BenchmarkServer.prototype.start = function() { - this.server.bindAsync(this.host + ':' + this.port, this.creds, (err) => { + this.server.bindAsync(this.host + ':' + this.port, this.creds, (err, port) => { assert.ifError(err); + this.port = port; this.server.start(); this.last_wall_time = process.hrtime(); this.last_usage = process.cpuUsage(); diff --git a/test/performance/driver.js b/test/performance/driver.js new file mode 100644 index 000000000..fa0e87560 --- /dev/null +++ b/test/performance/driver.js @@ -0,0 +1,82 @@ +const grpc = require('../any_grpc').server; +const protoLoader = require('../../packages/proto-loader'); +const protoPackage = protoLoader.loadSync( + 'src/proto/grpc/testing/worker_service.proto', + {keepCase: true, + defaults: true, + enums: String, + oneofs: true, + includeDirs: [__dirname + '/../proto/']}); +const serviceProto = grpc.loadPackageDefinition(protoPackage).grpc.testing; + +function main() { + const parseArgs = require('minimist'); + const argv = parseArgs(process.argv, { + string: ['client_worker_port', 'server_worker_port'] + }); + const clientWorker = new serviceProto.WorkerService(`localhost:${argv.client_worker_port}`, grpc.credentials.createInsecure()); + const serverWorker = new serviceProto.WorkerService(`localhost:${argv.server_worker_port}`, grpc.credentials.createInsecure()); + const serverWorkerStream = serverWorker.runServer(); + const clientWorkerStream = clientWorker.runClient(); + let firstServerResponseReceived = false; + let markCount = 0; + serverWorkerStream.on('data', (response) => { + console.log('Server stats:', response.stats); + if (!firstServerResponseReceived) { + firstServerResponseReceived = true; + clientWorkerStream.write({ + setup: { + server_targets: [`localhost:${response.port}`], + client_channels: 1, + outstanding_rpcs_per_channel: 1, + histogram_params: { + resolution: 0.01, + max_possible:60000000000 + }, + payload_config: { + bytebuf_params: { + req_size: 10, + resp_size: 10 + } + }, + load_params: { + closed_loop: {} + } + } + }); + clientWorkerStream.on('status', (status) => { + console.log('Received client worker status ' + JSON.stringify(status)); + serverWorkerStream.end(); + }); + const markInterval = setInterval(() => { + if (markCount >= 5) { + clientWorkerStream.end(); + clearInterval(markInterval); + } else { + clientWorkerStream.write({ + mark: {} + }); + serverWorkerStream.write({ + mark: {} + }); + } + markCount += 1; + }, 1000); + } + }); + clientWorkerStream.on('data', (response) => { + console.log('Client stats:', response.stats); + }); + serverWorkerStream.write({ + setup: { + port: 0 + } + }); + serverWorkerStream.on('status', (status) => { + console.log('Received server worker status ' + JSON.stringify(status)); + }); +} + +if (require.main === module) { + main(); +} diff --git a/test/performance/worker.js b/test/performance/worker.js index 86f17df2a..786d70560 100644 --- a/test/performance/worker.js +++ b/test/performance/worker.js @@ -39,13 +39,13 @@ function runServer(port, benchmark_impl, callback) { server.addService(serviceProto.WorkerService.service, new WorkerServiceImpl(benchmark_impl, server)); var address = '0.0.0.0:' + port; - server.bindAsync(address, server_creds, (err) => { + server.bindAsync(address, server_creds, (err, port) => { if (err) { return callback(err); } server.start(); - console.log('running QPS worker on %s', address); + console.log('running QPS worker on 0.0.0.0:%s', port); callback(null, server); }); } From 247af2c8c02d7425ca6594d100ee3dcdd229930e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 27 Jul 2023 16:56:02 -0700 Subject: [PATCH 544/694] Default to pure JS implementation in tests --- test/any_grpc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/any_grpc.js b/test/any_grpc.js index ec0b2b743..5dcbf0111 100644 --- a/test/any_grpc.js +++ b/test/any_grpc.js @@ -22,7 +22,7 @@ const _ = require('lodash'); function getImplementation(globalField) { - const impl = global[globalField]; + const impl = global[globalField] ?? 'js'; if (impl === 'js') { return require('../packages/grpc-js'); From aee1789145d5285405811966fee5a5edf72c2f06 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 28 Jul 2023 11:49:30 -0700 Subject: [PATCH 545/694] proto-loader: Increment version to prerelease version --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 66c93e96e..69038b74e 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.8", + "version": "0.7.9-pre.1", "author": "Google Inc.", "contributors": [ { From 4e111e77921096b1190873298f8f44cab1bd47a0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 28 Jul 2023 14:21:19 -0700 Subject: [PATCH 546/694] grpc-js: Fix propagation of UNIMPLEMENTED error messages --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/server.ts | 38 +++++------- packages/grpc-js/test/test-server.ts | 90 ++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 24 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 93b2f7826..29c2a00ee 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.20", + "version": "1.8.21", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 1a01b30dc..9853ddf56 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -61,7 +61,6 @@ import { import { parseUri } from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; -import { getErrorCode, getErrorMessage } from './error'; const UNLIMITED_CONNECTION_AGE_MS = ~(1<<31); const KEEPALIVE_MAX_TIME_MS = ~(1<<31); @@ -765,9 +764,7 @@ export class Server { return true } - private _retrieveHandler(headers: http2.IncomingHttpHeaders): Handler { - const path = headers[HTTP2_HEADER_PATH] as string - + private _retrieveHandler(path: string): Handler | null { this.trace( 'Received call to method ' + path + @@ -783,7 +780,7 @@ export class Server { path + '. Sending UNIMPLEMENTED status.' ); - throw getUnimplementedStatusResponse(path); + return null; } return handler @@ -820,15 +817,12 @@ export class Server { return } - let handler: Handler - try { - handler = this._retrieveHandler(headers) - } catch (err) { - this._respondWithError({ - details: getErrorMessage(err), - code: getErrorCode(err) ?? undefined - }, stream, channelzSessionInfo) - return + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError(getUnimplementedStatusResponse(path), stream, channelzSessionInfo); + return; } const call = new Http2ServerCallStream(stream, handler, this.options); @@ -875,15 +869,13 @@ export class Server { return } - let handler: Handler - try { - handler = this._retrieveHandler(headers) - } catch (err) { - this._respondWithError({ - details: getErrorMessage(err), - code: getErrorCode(err) ?? undefined - }, stream, null) - return + + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError(getUnimplementedStatusResponse(path), stream, null); + return; } const call = new Http2ServerCallStream(stream, handler, this.options) diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index d67307f61..68890df55 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -408,6 +408,7 @@ describe('Server', () => { (error: ServiceError, response: any) => { assert(error); assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Div/); done(); } ); @@ -417,6 +418,7 @@ describe('Server', () => { const call = client.sum((error: ServiceError, response: any) => { assert(error); assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Sum/); done(); }); @@ -433,6 +435,7 @@ describe('Server', () => { call.on('error', (err: ServiceError) => { assert(err); assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*Fib/); done(); }); }); @@ -447,6 +450,93 @@ describe('Server', () => { call.on('error', (err: ServiceError) => { assert(err); assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*DivMany/); + done(); + }); + + call.end(); + }); + }); + + describe('Unregistered service', () => { + let server: Server; + let client: ServiceClient; + + const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto'); + const mathClient = (loadProtoFile(mathProtoFile).math as any).Math; + + before(done => { + server = new Server(); + // Don't register a service at all + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + client = new mathClient( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server.start(); + done(); + } + ); + }); + + after(done => { + client.close(); + server.tryShutdown(done); + }); + + it('should respond to a unary call with UNIMPLEMENTED', done => { + client.div( + { divisor: 4, dividend: 3 }, + (error: ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Div/); + done(); + } + ); + }); + + it('should respond to a client stream with UNIMPLEMENTED', done => { + const call = client.sum((error: ServiceError, response: any) => { + assert(error); + assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED); + assert.match(error.details, /does not implement the method.*Sum/); + done(); + }); + + call.end(); + }); + + it('should respond to a server stream with UNIMPLEMENTED', done => { + const call = client.fib({ limit: 5 }); + + call.on('data', (value: any) => { + assert.fail('No messages expected'); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*Fib/); + done(); + }); + }); + + it('should respond to a bidi call with UNIMPLEMENTED', done => { + const call = client.divMany(); + + call.on('data', (value: any) => { + assert.fail('No messages expected'); + }); + + call.on('error', (err: ServiceError) => { + assert(err); + assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED); + assert.match(err.details, /does not implement the method.*DivMany/); done(); }); From 1f08883e2aded2276a877edfb16ba3daa4b339bf Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 31 Jul 2023 10:50:28 -0700 Subject: [PATCH 547/694] benchmark: Delay shutdown in quitWorker --- test/performance/driver.js | 14 ++++++++++++++ test/performance/worker_service_impl.js | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/test/performance/driver.js b/test/performance/driver.js index fa0e87560..9ecc4bc21 100644 --- a/test/performance/driver.js +++ b/test/performance/driver.js @@ -74,6 +74,20 @@ function main() { }); serverWorkerStream.on('status', (status) => { console.log('Received server worker status ' + JSON.stringify(status)); + clientWorker.quitWorker({}, (error, response) => { + if (error) { + console.log('Received error on clientWorker.quitWorker:', error); + } else { + console.log('Received response from clientWorker.quitWorker'); + } + }); + serverWorker.quitWorker({}, (error, response) => { + if (error) { + console.log('Received error on serverWorker.quitWorker:', error); + } else { + console.log('Received response from serverWorker.quitWorker'); + } + }); }); } diff --git a/test/performance/worker_service_impl.js b/test/performance/worker_service_impl.js index a73d77efc..30016aa71 100644 --- a/test/performance/worker_service_impl.js +++ b/test/performance/worker_service_impl.js @@ -41,7 +41,12 @@ module.exports = function WorkerServiceImpl(benchmark_impl, server) { this.quitWorker = function quitWorker(call, callback) { callback(null, {}); - server.tryShutdown(function() {}); + /* Due to https://github.com/nodejs/node/issues/42713, tryShutdown acts + * like forceShutdown on some Node versions. So, delay calling tryShutdown + * until after done handling this request. */ + setTimeout(() => { + server.tryShutdown(function() {}); + }, 10); }; this.runClient = function runClient(call) { From 49b629ffb0c294b8beed234ce50fc8f21de503bb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 31 Jul 2023 16:54:26 -0700 Subject: [PATCH 548/694] grpc-js/grpc-js-xds: Update to 1.9.0, and update READMEs --- packages/grpc-js-xds/README.md | 6 ++++-- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js/README.md | 1 + packages/grpc-js/package.json | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index 793e0c0d7..c1db440cf 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -1,6 +1,6 @@ # @grpc/grpc-js xDS plugin -This package provides support for the `xds://` URL scheme to the `@grpc/grpc-js` library. The latest version of this package is compatible with `@grpc/grpc-js` version 1.2.x. +This package provides support for the `xds://` URL scheme to the `@grpc/grpc-js` library. The latest version of this package is compatible with `@grpc/grpc-js` version 1.9.x. ## Installation @@ -29,4 +29,6 @@ const client = new MyServiceClient('xds:///example.com:123'); - [xDS Client-Side Fault Injection](https://github.com/grpc/proposal/blob/master/A33-Fault-Injection.md) - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) - - [xDS Retry Support](https://github.com/grpc/proposal/blob/master/A44-xds-retry.md) \ No newline at end of file + - [xDS Retry Support](https://github.com/grpc/proposal/blob/master/A44-xds-retry.md) + - [xDS Aggregate and Logical DNS Clusters](https://github.com/grpc/proposal/blob/master/A37-xds-aggregate-and-logical-dns-clusters.md)' + - [xDS Federation](https://github.com/grpc/proposal/blob/master/A47-xds-federation.md) (Currently experimental, enabled by environment variable `GRPC_EXPERIMENTAL_XDS_FEDERATION`) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 7fd7e700d..a55c631cf 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.8.2", + "version": "1.9.0", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -49,7 +49,7 @@ "vscode-uri": "^3.0.7" }, "peerDependencies": { - "@grpc/grpc-js": "~1.8.0" + "@grpc/grpc-js": "~1.9.0" }, "engines": { "node": ">=10.10.0" diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index 112b99932..eb04ece2f 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -65,6 +65,7 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.service_config_disable_resolution` - `grpc.client_idle_timeout_ms` - `grpc-node.max_session_memory` + - `grpc-node.tls_enable_trace` - `channelOverride` - `channelFactoryOverride` diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index f9f5629d5..97ff965a2 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.8.21", + "version": "1.9.0", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From bb2942197efa2698b37d8ab688578f9b6c4e5dde Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 2 Aug 2023 16:42:29 -0700 Subject: [PATCH 549/694] grpc-js: Improve formatting of channelz logs for grpcdebug --- packages/grpc-js/src/internal-channel.ts | 4 +--- packages/grpc-js/src/subchannel.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 88dd34741..f183729e8 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -477,9 +477,7 @@ export class InternalChannel { if (this.channelzEnabled) { this.channelzTrace.addTrace( 'CT_INFO', - ConnectivityState[this.connectivityState] + - ' -> ' + - ConnectivityState[newState] + 'Connectivity state change to ' + ConnectivityState[newState] ); } this.connectivityState = newState; diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 6fad9500a..91455f7c1 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -277,9 +277,7 @@ export class Subchannel { if (this.channelzEnabled) { this.channelzTrace.addTrace( 'CT_INFO', - ConnectivityState[this.connectivityState] + - ' -> ' + - ConnectivityState[newState] + 'Connectivity state change to ' + ConnectivityState[newState] ); } const previousState = this.connectivityState; From 30bc44f4ce54811022b84e0623d3757bea71736b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 2 Aug 2023 16:48:57 -0700 Subject: [PATCH 550/694] grpc-js: Handle race between call cancellation and auth metadata generation --- packages/grpc-js/src/load-balancing-call.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 6e9718a7a..b6c990946 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -140,6 +140,12 @@ export class LoadBalancingCall implements Call { .generateMetadata({ service_url: this.serviceUrl }) .then( credsMetadata => { + /* If this call was cancelled (e.g. by the deadline) before + * metadata generation finished, we shouldn't do anything with + * it. */ + if (this.ended) { + return; + } const finalMetadata = this.metadata!.clone(); finalMetadata.merge(credsMetadata); if (finalMetadata.get('authorization').length > 1) { From 01749a8d4199180eab3feda9976993fa642afd8f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 3 Aug 2023 09:24:24 -0700 Subject: [PATCH 551/694] Explicitly log credentials/cancellation races --- packages/grpc-js/src/load-balancing-call.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index b6c990946..2721e96a4 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -144,6 +144,7 @@ export class LoadBalancingCall implements Call { * metadata generation finished, we shouldn't do anything with * it. */ if (this.ended) { + this.trace('Credentials metadata generation finished after call ended'); return; } const finalMetadata = this.metadata!.clone(); From d28b9e8c378d4f81911056d1a9d27e36adc94f32 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 7 Aug 2023 17:23:57 -0700 Subject: [PATCH 552/694] grpc-js: Return LB policy configs from resolvers in JSON form --- packages/grpc-js/src/experimental.ts | 9 +- packages/grpc-js/src/index.ts | 2 + .../src/load-balancer-child-handler.ts | 10 +-- .../src/load-balancer-outlier-detection.ts | 60 +++++++------- .../grpc-js/src/load-balancer-pick-first.ts | 6 +- .../grpc-js/src/load-balancer-round-robin.ts | 6 +- packages/grpc-js/src/load-balancer.ts | 83 ++++++++++--------- .../grpc-js/src/resolving-load-balancer.ts | 8 +- packages/grpc-js/src/service-config.ts | 25 +++++- packages/grpc-js/test/test-deadline.ts | 4 +- 10 files changed, 119 insertions(+), 94 deletions(-) diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 9e4bbf45b..e4bd164ec 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -8,16 +8,15 @@ export { } from './resolver'; export { GrpcUri, uriToString } from './uri-parser'; export { Duration, durationToMs } from './duration'; -export { ServiceConfig, MethodConfig, RetryPolicy } from './service-config'; export { BackoffTimeout } from './backoff-timeout'; export { LoadBalancer, - LoadBalancingConfig, + TypedLoadBalancingConfig, ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType, - getFirstUsableConfig, - validateLoadBalancingConfig, + selectLbConfigFromList, + parseLoadBalancingConfig } from './load-balancer'; export { SubchannelAddress, @@ -42,7 +41,7 @@ export { ConnectivityStateListener, } from './subchannel-interface'; export { - OutlierDetectionLoadBalancingConfig, + OutlierDetectionRawConfig, SuccessRateEjectionConfig, FailurePercentageEjectionConfig, } from './load-balancer-outlier-detection'; diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index adacae08f..d44a2dc6e 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -261,6 +261,8 @@ export { getChannelzServiceDefinition, getChannelzHandlers } from './channelz'; export { addAdminServicesToServer } from './admin'; +export { ServiceConfig, LoadBalancingConfig, MethodConfig, RetryPolicy } from './service-config'; + import * as experimental from './experimental'; export { experimental }; diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index a4dc90c4f..b23f19263 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -18,7 +18,7 @@ import { LoadBalancer, ChannelControlHelper, - LoadBalancingConfig, + TypedLoadBalancingConfig, createLoadBalancer, } from './load-balancer'; import { SubchannelAddress } from './subchannel-address'; @@ -33,7 +33,7 @@ const TYPE_NAME = 'child_load_balancer_helper'; export class ChildLoadBalancerHandler implements LoadBalancer { private currentChild: LoadBalancer | null = null; private pendingChild: LoadBalancer | null = null; - private latestConfig: LoadBalancingConfig | null = null; + private latestConfig: TypedLoadBalancingConfig | null = null; private ChildPolicyHelper = class { private child: LoadBalancer | null = null; @@ -87,8 +87,8 @@ export class ChildLoadBalancerHandler implements LoadBalancer { constructor(private readonly channelControlHelper: ChannelControlHelper) {} protected configUpdateRequiresNewPolicyInstance( - oldConfig: LoadBalancingConfig, - newConfig: LoadBalancingConfig + oldConfig: TypedLoadBalancingConfig, + newConfig: TypedLoadBalancingConfig ): boolean { return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName(); } @@ -101,7 +101,7 @@ export class ChildLoadBalancerHandler implements LoadBalancer { */ updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { let childToUpdate: LoadBalancer; diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 4abbd0843..00d0e0a53 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -18,17 +18,16 @@ import { ChannelOptions } from './channel-options'; import { ConnectivityState } from './connectivity-state'; import { LogVerbosity, Status } from './constants'; -import { durationToMs, isDuration, msToDuration } from './duration'; +import { Duration, durationToMs, isDuration, msToDuration } from './duration'; import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType, } from './experimental'; import { - getFirstUsableConfig, + selectLbConfigFromList, LoadBalancer, - LoadBalancingConfig, - validateLoadBalancingConfig, + TypedLoadBalancingConfig, } from './load-balancer'; import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; import { PickArgs, Picker, PickResult, PickResultType } from './picker'; @@ -42,6 +41,7 @@ import { SubchannelInterface, } from './subchannel-interface'; import * as logging from './logging'; +import { LoadBalancingConfig } from './service-config'; const TRACER_NAME = 'outlier_detection'; @@ -68,6 +68,16 @@ export interface FailurePercentageEjectionConfig { readonly request_volume: number; } +export interface OutlierDetectionRawConfig { + interval?: Duration; + base_ejection_time?: Duration; + max_ejection_time?: Duration; + max_ejection_percent?: number; + success_rate_ejection?: Partial; + failure_percentage_ejection?: Partial; + child_policy: LoadBalancingConfig[]; +} + const defaultSuccessRateEjectionConfig: SuccessRateEjectionConfig = { stdev_factor: 1900, enforcement_percentage: 100, @@ -147,7 +157,7 @@ function validatePercentage(obj: any, fieldName: string, objectName?: string) { } export class OutlierDetectionLoadBalancingConfig - implements LoadBalancingConfig + implements TypedLoadBalancingConfig { private readonly intervalMs: number; private readonly baseEjectionTimeMs: number; @@ -163,11 +173,10 @@ export class OutlierDetectionLoadBalancingConfig maxEjectionPercent: number | null, successRateEjection: Partial | null, failurePercentageEjection: Partial | null, - private readonly childPolicy: LoadBalancingConfig[] + private readonly childPolicy: TypedLoadBalancingConfig ) { if ( - childPolicy.length > 0 && - childPolicy[0].getLoadBalancerName() === 'pick_first' + childPolicy.getLoadBalancerName() === 'pick_first' ) { throw new Error( 'outlier_detection LB policy cannot have a pick_first child policy' @@ -198,7 +207,7 @@ export class OutlierDetectionLoadBalancingConfig max_ejection_percent: this.maxEjectionPercent, success_rate_ejection: this.successRateEjection, failure_percentage_ejection: this.failurePercentageEjection, - child_policy: this.childPolicy.map(policy => policy.toJsonObject()), + child_policy: [this.childPolicy.toJsonObject()] }; } @@ -220,24 +229,10 @@ export class OutlierDetectionLoadBalancingConfig getFailurePercentageEjectionConfig(): FailurePercentageEjectionConfig | null { return this.failurePercentageEjection; } - getChildPolicy(): LoadBalancingConfig[] { + getChildPolicy(): TypedLoadBalancingConfig { return this.childPolicy; } - copyWithChildPolicy( - childPolicy: LoadBalancingConfig[] - ): OutlierDetectionLoadBalancingConfig { - return new OutlierDetectionLoadBalancingConfig( - this.intervalMs, - this.baseEjectionTimeMs, - this.maxEjectionTimeMs, - this.maxEjectionPercent, - this.successRateEjection, - this.failurePercentageEjection, - childPolicy - ); - } - static createFromJson(obj: any): OutlierDetectionLoadBalancingConfig { validatePositiveDuration(obj, 'interval'); validatePositiveDuration(obj, 'base_ejection_time'); @@ -303,6 +298,14 @@ export class OutlierDetectionLoadBalancingConfig ); } + if (!('child_policy' in obj) || !Array.isArray(obj.child_policy)) { + throw new Error('outlier detection config child_policy must be an array'); + } + const childPolicy = selectLbConfigFromList(obj.child_policy); + if (!childPolicy) { + throw new Error('outlier detection config child_policy: no valid recognized policy found'); + } + return new OutlierDetectionLoadBalancingConfig( obj.interval ? durationToMs(obj.interval) : null, obj.base_ejection_time ? durationToMs(obj.base_ejection_time) : null, @@ -310,7 +313,7 @@ export class OutlierDetectionLoadBalancingConfig obj.max_ejection_percent ?? null, obj.success_rate_ejection, obj.failure_percentage_ejection, - obj.child_policy.map(validateLoadBalancingConfig) + childPolicy ); } } @@ -794,7 +797,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) { @@ -821,10 +824,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { this.addressMap.delete(key); } } - const childPolicy: LoadBalancingConfig = getFirstUsableConfig( - lbConfig.getChildPolicy(), - true - ); + const childPolicy = lbConfig.getChildPolicy(); this.childBalancer.updateAddressList(addressList, childPolicy, attributes); if ( diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 08971980b..8635482ce 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -18,7 +18,7 @@ import { LoadBalancer, ChannelControlHelper, - LoadBalancingConfig, + TypedLoadBalancingConfig, registerDefaultLoadBalancerType, registerLoadBalancerType, } from './load-balancer'; @@ -53,7 +53,7 @@ const TYPE_NAME = 'pick_first'; */ const CONNECTION_DELAY_INTERVAL_MS = 250; -export class PickFirstLoadBalancingConfig implements LoadBalancingConfig { +export class PickFirstLoadBalancingConfig implements TypedLoadBalancingConfig { constructor(private readonly shuffleAddressList: boolean) {} getLoadBalancerName(): string { @@ -374,7 +374,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig + lbConfig: TypedLoadBalancingConfig ): void { if (!(lbConfig instanceof PickFirstLoadBalancingConfig)) { return; diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index f389fefc0..a611cfd64 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -18,7 +18,7 @@ import { LoadBalancer, ChannelControlHelper, - LoadBalancingConfig, + TypedLoadBalancingConfig, registerLoadBalancerType, } from './load-balancer'; import { ConnectivityState } from './connectivity-state'; @@ -49,7 +49,7 @@ function trace(text: string): void { const TYPE_NAME = 'round_robin'; -class RoundRobinLoadBalancingConfig implements LoadBalancingConfig { +class RoundRobinLoadBalancingConfig implements TypedLoadBalancingConfig { getLoadBalancerName(): string { return TYPE_NAME; } @@ -192,7 +192,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig + lbConfig: TypedLoadBalancingConfig ): void { this.resetSubchannelList(); trace( diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index f18638788..d5d69543f 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -21,6 +21,9 @@ import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; import { ChannelRef, SubchannelRef } from './channelz'; import { SubchannelInterface } from './subchannel-interface'; +import { LoadBalancingConfig } from './service-config'; +import { log } from './logging'; +import { LogVerbosity } from './constants'; /** * A collection of functions associated with a channel that a load balancer @@ -98,7 +101,7 @@ export interface LoadBalancer { */ updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void; /** @@ -128,22 +131,22 @@ export interface LoadBalancerConstructor { new (channelControlHelper: ChannelControlHelper): LoadBalancer; } -export interface LoadBalancingConfig { +export interface TypedLoadBalancingConfig { getLoadBalancerName(): string; toJsonObject(): object; } -export interface LoadBalancingConfigConstructor { +export interface TypedLoadBalancingConfigConstructor { // eslint-disable-next-line @typescript-eslint/no-explicit-any - new (...args: any): LoadBalancingConfig; + new (...args: any): TypedLoadBalancingConfig; // eslint-disable-next-line @typescript-eslint/no-explicit-any - createFromJson(obj: any): LoadBalancingConfig; + createFromJson(obj: any): TypedLoadBalancingConfig; } const registeredLoadBalancerTypes: { [name: string]: { LoadBalancer: LoadBalancerConstructor; - LoadBalancingConfig: LoadBalancingConfigConstructor; + LoadBalancingConfig: TypedLoadBalancingConfigConstructor; }; } = {}; @@ -152,7 +155,7 @@ let defaultLoadBalancerType: string | null = null; export function registerLoadBalancerType( typeName: string, loadBalancerType: LoadBalancerConstructor, - loadBalancingConfigType: LoadBalancingConfigConstructor + loadBalancingConfigType: TypedLoadBalancingConfigConstructor ) { registeredLoadBalancerTypes[typeName] = { LoadBalancer: loadBalancerType, @@ -165,7 +168,7 @@ export function registerDefaultLoadBalancerType(typeName: string) { } export function createLoadBalancer( - config: LoadBalancingConfig, + config: TypedLoadBalancingConfig, channelControlHelper: ChannelControlHelper ): LoadBalancer | null { const typeName = config.getLoadBalancerName(); @@ -182,17 +185,44 @@ export function isLoadBalancerNameRegistered(typeName: string): boolean { return typeName in registeredLoadBalancerTypes; } -export function getFirstUsableConfig( - configs: LoadBalancingConfig[], - fallbackTodefault?: true -): LoadBalancingConfig; -export function getFirstUsableConfig( +export function parseLoadBalancingConfig(rawConfig: LoadBalancingConfig): TypedLoadBalancingConfig { + const keys = Object.keys(rawConfig); + if (keys.length !== 1) { + throw new Error( + 'Provided load balancing config has multiple conflicting entries' + ); + } + const typeName = keys[0]; + if (typeName in registeredLoadBalancerTypes) { + try { + return registeredLoadBalancerTypes[ + typeName + ].LoadBalancingConfig.createFromJson(rawConfig[typeName]); + } catch (e) { + throw new Error(`${typeName}: ${(e as Error).message}`); + } + } else { + throw new Error(`Unrecognized load balancing config name ${typeName}`); + } +} + +export function getDefaultConfig() { + if (!defaultLoadBalancerType) { + throw new Error('No default load balancer type registered'); + } + return new registeredLoadBalancerTypes[defaultLoadBalancerType]!.LoadBalancingConfig(); +} + +export function selectLbConfigFromList( configs: LoadBalancingConfig[], fallbackTodefault = false -): LoadBalancingConfig | null { +): TypedLoadBalancingConfig | null { for (const config of configs) { - if (config.getLoadBalancerName() in registeredLoadBalancerTypes) { - return config; + try { + return parseLoadBalancingConfig(config); + } catch (e) { + log(LogVerbosity.DEBUG, 'Config parsing failed with error', (e as Error).message); + continue; } } if (fallbackTodefault) { @@ -207,24 +237,3 @@ export function getFirstUsableConfig( return null; } } - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function validateLoadBalancingConfig(obj: any): LoadBalancingConfig { - if (!(obj !== null && typeof obj === 'object')) { - throw new Error('Load balancing config must be an object'); - } - const keys = Object.keys(obj); - if (keys.length !== 1) { - throw new Error( - 'Provided load balancing config has multiple conflicting entries' - ); - } - const typeName = keys[0]; - if (typeName in registeredLoadBalancerTypes) { - return registeredLoadBalancerTypes[ - typeName - ].LoadBalancingConfig.createFromJson(obj[typeName]); - } else { - throw new Error(`Unrecognized load balancing config name ${typeName}`); - } -} diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index d49609ff2..e2b2c1fa5 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -18,8 +18,8 @@ import { ChannelControlHelper, LoadBalancer, - LoadBalancingConfig, - getFirstUsableConfig, + TypedLoadBalancingConfig, + selectLbConfigFromList, } from './load-balancer'; import { ServiceConfig, validateServiceConfig } from './service-config'; import { ConnectivityState } from './connectivity-state'; @@ -211,7 +211,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { } const workingConfigList = workingServiceConfig?.loadBalancingConfig ?? []; - const loadBalancingConfig = getFirstUsableConfig( + const loadBalancingConfig = selectLbConfigFromList( workingConfigList, true ); @@ -308,7 +308,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig | null + lbConfig: TypedLoadBalancingConfig | null ): never { throw new Error('updateAddressList not supported on ResolvingLoadBalancer'); } diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index 91bee52c2..168f28c78 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -29,10 +29,6 @@ import * as os from 'os'; import { Status } from './constants'; import { Duration } from './duration'; -import { - LoadBalancingConfig, - validateLoadBalancingConfig, -} from './load-balancer'; export interface MethodConfigName { service: string; @@ -68,6 +64,10 @@ export interface RetryThrottling { tokenRatio: number; } +export interface LoadBalancingConfig { + [key: string]: object; +} + export interface ServiceConfig { loadBalancingPolicy?: string; loadBalancingConfig: LoadBalancingConfig[]; @@ -338,6 +338,22 @@ export function validateRetryThrottling(obj: any): RetryThrottling { }; } +function validateLoadBalancingConfig(obj: any): LoadBalancingConfig { + if (!(typeof obj === 'object' && obj !== null)) { + throw new Error(`Invalid loadBalancingConfig: unexpected type ${typeof obj}`); + } + const keys = Object.keys(obj); + if (keys.length > 1) { + throw new Error(`Invalid loadBalancingConfig: unexpected multiple keys ${keys}`); + } + if (keys.length === 0) { + throw new Error('Invalid loadBalancingConfig: load balancing policy name required'); + } + return { + [keys[0]]: obj[keys[0]] + }; +} + export function validateServiceConfig(obj: any): ServiceConfig { const result: ServiceConfig = { loadBalancingConfig: [], @@ -353,6 +369,7 @@ export function validateServiceConfig(obj: any): ServiceConfig { if ('loadBalancingConfig' in obj) { if (Array.isArray(obj.loadBalancingConfig)) { for (const config of obj.loadBalancingConfig) { + result.loadBalancingConfig.push(validateLoadBalancingConfig(config)); } } else { diff --git a/packages/grpc-js/test/test-deadline.ts b/packages/grpc-js/test/test-deadline.ts index 9de3687c4..24aebd4d7 100644 --- a/packages/grpc-js/test/test-deadline.ts +++ b/packages/grpc-js/test/test-deadline.ts @@ -18,12 +18,10 @@ import * as assert from 'assert'; import * as grpc from '../src'; -import { experimental } from '../src'; import { ServiceClient, ServiceClientConstructor } from '../src/make-client'; import { loadProtoFile } from './common'; -import ServiceConfig = experimental.ServiceConfig; -const TIMEOUT_SERVICE_CONFIG: ServiceConfig = { +const TIMEOUT_SERVICE_CONFIG: grpc.ServiceConfig = { loadBalancingConfig: [], methodConfig: [ { From 08bcbfc677612e21c9af2bd0c6ea0f5f75517688 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 7 Aug 2023 17:25:39 -0700 Subject: [PATCH 553/694] grpc-js-xds: Adjust LB policy config handling for grpc-js changes --- packages/grpc-js-xds/src/duration.ts | 33 +++++ packages/grpc-js-xds/src/load-balancer-cds.ts | 83 +++++------ packages/grpc-js-xds/src/load-balancer-lrs.ts | 25 ++-- .../grpc-js-xds/src/load-balancer-priority.ts | 79 +++++++---- .../src/load-balancer-weighted-target.ts | 48 +++++-- .../src/load-balancer-xds-cluster-impl.ts | 21 +-- .../src/load-balancer-xds-cluster-manager.ts | 45 +++--- .../src/load-balancer-xds-cluster-resolver.ts | 131 +++++++++++------- packages/grpc-js-xds/src/resolver-xds.ts | 42 ++---- packages/grpc-js-xds/src/route-action.ts | 5 +- .../cluster-resource-type.ts | 46 ++---- 11 files changed, 302 insertions(+), 256 deletions(-) create mode 100644 packages/grpc-js-xds/src/duration.ts diff --git a/packages/grpc-js-xds/src/duration.ts b/packages/grpc-js-xds/src/duration.ts new file mode 100644 index 000000000..07f33651f --- /dev/null +++ b/packages/grpc-js-xds/src/duration.ts @@ -0,0 +1,33 @@ +/* + * 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. + */ + +import { experimental } from '@grpc/grpc-js'; +import { Duration__Output } from './generated/google/protobuf/Duration'; +import Duration = experimental.Duration; + +/** + * Convert a Duration protobuf message object to a Duration object as used in + * the ServiceConfig definition. The difference is that the protobuf message + * defines seconds as a long, which is represented as a string in JavaScript, + * and the one used in the service config defines it as a number. + * @param duration + */ +export function protoDurationToDuration(duration: Duration__Output): Duration { + return { + seconds: Number.parseInt(duration.seconds), + nanos: duration.nanos + }; +} diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 6f791299c..3ebcbbdf6 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -15,7 +15,7 @@ * */ -import { connectivityState, status, Metadata, logVerbosity, experimental } from '@grpc/grpc-js'; +import { connectivityState, status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from '@grpc/grpc-js'; import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import SubchannelAddress = experimental.SubchannelAddress; @@ -24,17 +24,18 @@ import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; -import LoadBalancingConfig = experimental.LoadBalancingConfig; -import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; import QueuePicker = experimental.QueuePicker; +import OutlierDetectionRawConfig = experimental.OutlierDetectionRawConfig; +import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; import { Duration__Output } from './generated/google/protobuf/Duration'; import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; -import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler, XdsClusterResolverLoadBalancingConfig } from './load-balancer-xds-cluster-resolver'; +import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler } from './load-balancer-xds-cluster-resolver'; import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources'; -import { CdsUpdate, ClusterResourceType, OutlierDetectionUpdate } from './xds-resource-type/cluster-resource-type'; +import { CdsUpdate, ClusterResourceType } from './xds-resource-type/cluster-resource-type'; const TRACER_NAME = 'cds_balancer'; @@ -44,7 +45,7 @@ function trace(text: string): void { const TYPE_NAME = 'cds'; -export class CdsLoadBalancingConfig implements LoadBalancingConfig { +class CdsLoadBalancingConfig implements TypedLoadBalancingConfig { getLoadBalancerName(): string { return TYPE_NAME; } @@ -72,29 +73,6 @@ export class CdsLoadBalancingConfig implements LoadBalancingConfig { } } -function durationToMs(duration: Duration__Output): number { - return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; -} - -function translateOutlierDetectionConfig(outlierDetection: OutlierDetectionUpdate | undefined): OutlierDetectionLoadBalancingConfig | undefined { - if (!EXPERIMENTAL_OUTLIER_DETECTION) { - return undefined; - } - if (!outlierDetection) { - /* No-op outlier detection config, with all fields unset. */ - return new OutlierDetectionLoadBalancingConfig(null, null, null, null, null, null, []); - } - return new OutlierDetectionLoadBalancingConfig( - outlierDetection.intervalMs, - outlierDetection.baseEjectionTimeMs, - outlierDetection.maxEjectionTimeMs, - outlierDetection.maxEjectionPercent, - outlierDetection.successRateConfig, - outlierDetection.failurePercentageConfig, - [] - ); -} - interface ClusterEntry { watcher: Watcher; latestUpdate?: CdsUpdate; @@ -133,7 +111,7 @@ function generateDiscoverymechanismForCdsUpdate(config: CdsUpdate): DiscoveryMec type: config.type, eds_service_name: config.edsServiceName, dns_hostname: config.dnsHostname, - outlier_detection: translateOutlierDetectionConfig(config.outlierDetectionUpdate) + outlier_detection: config.outlierDetectionUpdate }; } @@ -141,8 +119,8 @@ const RECURSION_DEPTH_LIMIT = 15; /** * Prerequisite: isClusterTreeFullyUpdated(tree, root) - * @param tree - * @param root + * @param tree + * @param root */ function getDiscoveryMechanismList(tree: ClusterTree, root: string): DiscoveryMechanism[] { const visited = new Set(); @@ -189,6 +167,11 @@ export class CdsLoadBalancer implements LoadBalancer { this.childBalancer = new XdsClusterResolverChildPolicyHandler(channelControlHelper); } + private reportError(errorMessage: string) { + trace('CDS cluster reporting error ' + errorMessage); + this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage, metadata: new Metadata()})); + } + private addCluster(cluster: string) { if (cluster in this.clusterTree) { return; @@ -208,19 +191,28 @@ export class CdsLoadBalancer implements LoadBalancer { try { discoveryMechanismList = getDiscoveryMechanismList(this.clusterTree, this.latestConfig!.getCluster()); } catch (e) { - this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: e.message, metadata: new Metadata()})); + this.reportError((e as Error).message); + return; + } + const clusterResolverConfig: LoadBalancingConfig = { + xds_cluster_resolver: { + discovery_mechanisms: discoveryMechanismList, + locality_picking_policy: [], + endpoint_picking_policy: [] + } + }; + let parsedClusterResolverConfig: TypedLoadBalancingConfig; + try { + parsedClusterResolverConfig = parseLoadBalancingConfig(clusterResolverConfig); + } catch (e) { + this.reportError(`CDS cluster ${this.latestConfig?.getCluster()} child config parsing failed with error ${(e as Error).message}`); return; } - const clusterResolverConfig = new XdsClusterResolverLoadBalancingConfig( - discoveryMechanismList, - [], - [] - ); trace('Child update config: ' + JSON.stringify(clusterResolverConfig)); this.updatedChild = true; this.childBalancer.updateAddressList( [], - clusterResolverConfig, + parsedClusterResolverConfig, this.latestAttributes ); } @@ -231,20 +223,13 @@ export class CdsLoadBalancer implements LoadBalancer { this.clusterTree[cluster].latestUpdate = undefined; this.clusterTree[cluster].children = []; } - this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `CDS resource ${cluster} does not exist`, metadata: new Metadata()})); + this.reportError(`CDS resource ${cluster} does not exist`); this.childBalancer.destroy(); }, onError: (statusObj) => { if (!this.updatedChild) { trace('Transitioning to transient failure due to onError update for cluster' + cluster); - this.channelControlHelper.updateState( - connectivityState.TRANSIENT_FAILURE, - new UnavailablePicker({ - code: status.UNAVAILABLE, - details: `xDS request failed with error ${statusObj.details}`, - metadata: new Metadata(), - }) - ); + this.reportError(`xDS request failed with error ${statusObj.details}`); } } }); @@ -275,7 +260,7 @@ export class CdsLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { if (!(lbConfig instanceof CdsLoadBalancingConfig)) { diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index a2deb72c3..145b4deda 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -22,9 +22,7 @@ import { XdsClusterLocalityStats, XdsClient, getSingletonXdsClient } from './xds import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; -import getFirstUsableConfig = experimental.getFirstUsableConfig; import SubchannelAddress = experimental.SubchannelAddress; -import LoadBalancingConfig = experimental.LoadBalancingConfig; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import Picker = experimental.Picker; import PickArgs = experimental.PickArgs; @@ -34,11 +32,12 @@ import Filter = experimental.Filter; import BaseFilter = experimental.BaseFilter; import FilterFactory = experimental.FilterFactory; import Call = experimental.CallStream; -import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; +import selectLbConfigFromList = experimental.selectLbConfigFromList; const TYPE_NAME = 'lrs'; -export class LrsLoadBalancingConfig implements LoadBalancingConfig { +class LrsLoadBalancingConfig implements TypedLoadBalancingConfig { getLoadBalancerName(): string { return TYPE_NAME; } @@ -49,12 +48,12 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig { eds_service_name: this.edsServiceName, lrs_load_reporting_server_name: this.lrsLoadReportingServer, locality: this.locality, - child_policy: this.childPolicy.map(policy => policy.toJsonObject()) + child_policy: [this.childPolicy.toJsonObject()] } } } - constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, private locality: Locality__Output, private childPolicy: LoadBalancingConfig[]) {} + constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, private locality: Locality__Output, private childPolicy: TypedLoadBalancingConfig) {} getClusterName() { return this.clusterName; @@ -98,11 +97,15 @@ export class LrsLoadBalancingConfig implements LoadBalancingConfig { if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { throw new Error('lrs config must have a child_policy array'); } + const childConfig = selectLbConfigFromList(obj.config); + if (!childConfig) { + throw new Error('lrs config child_policy parsing failed'); + } return new LrsLoadBalancingConfig(obj.cluster_name, obj.eds_service_name, validateXdsServerConfig(obj.lrs_load_reporting_server), { region: obj.locality.region ?? '', zone: obj.locality.zone ?? '', sub_zone: obj.locality.sub_zone ?? '' - }, obj.child_policy.map(validateLoadBalancingConfig)); + }, childConfig); } } @@ -161,7 +164,7 @@ export class LrsLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { if (!(lbConfig instanceof LrsLoadBalancingConfig)) { @@ -173,11 +176,7 @@ export class LrsLoadBalancer implements LoadBalancer { lbConfig.getEdsServiceName(), lbConfig.getLocality() ); - const childPolicy: LoadBalancingConfig = getFirstUsableConfig( - lbConfig.getChildPolicy(), - true - ); - this.childBalancer.updateAddressList(addressList, childPolicy, attributes); + this.childBalancer.updateAddressList(addressList, lbConfig.getChildPolicy(), attributes); } exitIdle(): void { this.childBalancer.exitIdle(); diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index a9d03d0a6..4d26a0f41 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -15,19 +15,18 @@ * */ -import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, ChannelOptions } from '@grpc/grpc-js'; -import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, LoadBalancingConfig } from '@grpc/grpc-js'; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; -import getFirstUsableConfig = experimental.getFirstUsableConfig; import registerLoadBalancerType = experimental.registerLoadBalancerType; import SubchannelAddress = experimental.SubchannelAddress; import subchannelAddressToString = experimental.subchannelAddressToString; -import LoadBalancingConfig = experimental.LoadBalancingConfig; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import Picker = experimental.Picker; import QueuePicker = experimental.QueuePicker; import UnavailablePicker = experimental.UnavailablePicker; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import selectLbConfigFromList = experimental.selectLbConfigFromList; const TRACER_NAME = 'priority'; @@ -50,12 +49,31 @@ export function isLocalitySubchannelAddress( return Array.isArray((address as LocalitySubchannelAddress).localityPath); } -export interface PriorityChild { +/** + * Type of the config for an individual child in the JSON representation of + * a priority LB policy config. + */ +export interface PriorityChildRaw { config: LoadBalancingConfig[]; ignore_reresolution_requests: boolean; } -export class PriorityLoadBalancingConfig implements LoadBalancingConfig { +/** + * The JSON representation of the config for the priority LB policy. The + * LoadBalancingConfig for a priority policy should have the form + * { priority: PriorityRawConfig } + */ +export interface PriorityRawConfig { + children: {[name: string]: PriorityChildRaw}; + priorities: string[]; +} + +interface PriorityChild { + config: TypedLoadBalancingConfig; + ignore_reresolution_requests: boolean; +} + +class PriorityLoadBalancingConfig implements TypedLoadBalancingConfig { getLoadBalancerName(): string { return TYPE_NAME; } @@ -63,7 +81,7 @@ export class PriorityLoadBalancingConfig implements LoadBalancingConfig { const childrenField: {[key: string]: object} = {} for (const [childName, childValue] of this.children.entries()) { childrenField[childName] = { - config: childValue.config.map(value => value.toJsonObject()) + config: [childValue.config.toJsonObject()] }; } return { @@ -93,7 +111,7 @@ export class PriorityLoadBalancingConfig implements LoadBalancingConfig { throw new Error('Priority config must have a priorities list'); } const childrenMap: Map = new Map(); - for (const childName of obj.children) { + for (const childName of Object.keys(obj.children)) { const childObj = obj.children[childName] if (!('config' in childObj && Array.isArray(childObj.config))) { throw new Error(`Priority child ${childName} must have a config list`); @@ -101,8 +119,12 @@ export class PriorityLoadBalancingConfig implements LoadBalancingConfig { if (!('ignore_reresolution_requests' in childObj && typeof childObj.ignore_reresolution_requests === 'boolean')) { throw new Error(`Priority child ${childName} must have a boolean field ignore_reresolution_requests`); } + const childConfig = selectLbConfigFromList(childObj.config); + if (!childConfig) { + throw new Error(`Priority child ${childName} config parsing failed`); + } childrenMap.set(childName, { - config: childObj.config.map(validateLoadBalancingConfig), + config: childConfig, ignore_reresolution_requests: childObj.ignore_reresolution_requests }); } @@ -113,7 +135,7 @@ export class PriorityLoadBalancingConfig implements LoadBalancingConfig { interface PriorityChildBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void; exitIdle(): void; @@ -129,7 +151,7 @@ interface PriorityChildBalancer { interface UpdateArgs { subchannelAddress: SubchannelAddress[]; - lbConfig: LoadBalancingConfig; + lbConfig: TypedLoadBalancingConfig; ignoreReresolutionRequests: boolean; } @@ -193,7 +215,7 @@ export class PriorityLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { this.childBalancer.updateAddressList(addressList, lbConfig, attributes); @@ -387,7 +409,7 @@ export class PriorityLoadBalancer implements LoadBalancer { updateAddressList( addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig, + lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { if (!(lbConfig instanceof PriorityLoadBalancingConfig)) { @@ -431,23 +453,20 @@ export class PriorityLoadBalancer implements LoadBalancer { /* Pair up the new child configs with the corresponding address lists, and * update all existing children with their new configs */ for (const [childName, childConfig] of lbConfig.getChildren()) { - const chosenChildConfig = getFirstUsableConfig(childConfig.config); - if (chosenChildConfig !== null) { - const childAddresses = childAddressMap.get(childName) ?? []; - trace('Assigning child ' + childName + ' address list ' + childAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')) - this.latestUpdates.set(childName, { - subchannelAddress: childAddresses, - lbConfig: chosenChildConfig, - ignoreReresolutionRequests: childConfig.ignore_reresolution_requests - }); - const existingChild = this.children.get(childName); - if (existingChild !== undefined) { - existingChild.updateAddressList( - childAddresses, - chosenChildConfig, - attributes - ); - } + const childAddresses = childAddressMap.get(childName) ?? []; + trace('Assigning child ' + childName + ' address list ' + childAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')) + this.latestUpdates.set(childName, { + subchannelAddress: childAddresses, + lbConfig: childConfig.config, + ignoreReresolutionRequests: childConfig.ignore_reresolution_requests + }); + const existingChild = this.children.get(childName); + if (existingChild !== undefined) { + existingChild.updateAddressList( + childAddresses, + childConfig.config, + attributes + ); } } // Deactivate all children that are no longer in the priority list diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts index 7cd92d98b..231f3b179 100644 --- a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -15,12 +15,11 @@ * */ -import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental } from "@grpc/grpc-js"; +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from "@grpc/grpc-js"; import { isLocalitySubchannelAddress, LocalitySubchannelAddress } from "./load-balancer-priority"; -import LoadBalancingConfig = experimental.LoadBalancingConfig; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; -import getFirstUsableConfig = experimental.getFirstUsableConfig; import registerLoadBalancerType = experimental.registerLoadBalancerType; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import Picker = experimental.Picker; @@ -30,7 +29,7 @@ import QueuePicker = experimental.QueuePicker; import UnavailablePicker = experimental.UnavailablePicker; import SubchannelAddress = experimental.SubchannelAddress; import subchannelAddressToString = experimental.subchannelAddressToString; -import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import selectLbConfigFromList = experimental.selectLbConfigFromList; const TRACER_NAME = 'weighted_target'; @@ -42,12 +41,30 @@ const TYPE_NAME = 'weighted_target'; const DEFAULT_RETENTION_INTERVAL_MS = 15 * 60 * 1000; - export interface WeightedTarget { +/** + * Type of the config for an individual child in the JSON representation of + * a weighted target LB policy config. + */ +export interface WeightedTargetRaw { weight: number; child_policy: LoadBalancingConfig[]; } -export class WeightedTargetLoadBalancingConfig implements LoadBalancingConfig { +/** + * The JSON representation of the config for the weighted target LB policy. The + * LoadBalancingConfig for a weighted target policy should have the form + * { weighted_target: WeightedTargetRawConfig } + */ +export interface WeightedTargetRawConfig { + targets: {[name: string]: WeightedTargetRaw }; +} + +interface WeightedTarget { + weight: number; + child_policy: TypedLoadBalancingConfig; +} + +class WeightedTargetLoadBalancingConfig implements TypedLoadBalancingConfig { getLoadBalancerName(): string { return TYPE_NAME; } @@ -64,7 +81,7 @@ export class WeightedTargetLoadBalancingConfig implements LoadBalancingConfig { for (const [targetName, targetValue] of this.targets.entries()) { targetsField[targetName] = { weight: targetValue.weight, - child_policy: targetValue.child_policy.map(policy => policy.toJsonObject()) + child_policy: [targetValue.child_policy.toJsonObject()] }; } return { @@ -79,7 +96,7 @@ export class WeightedTargetLoadBalancingConfig implements LoadBalancingConfig { if (!('targets' in obj && obj.targets !== null && typeof obj.targets === 'object')) { throw new Error('Weighted target config must have a targets map'); } - for (const key of obj.targets) { + for (const key of Object.keys(obj.targets)) { const targetObj = obj.targets[key]; if (!('weight' in targetObj && typeof targetObj.weight === 'number')) { throw new Error(`Weighted target ${key} must have a numeric weight`); @@ -87,9 +104,13 @@ export class WeightedTargetLoadBalancingConfig implements LoadBalancingConfig { if (!('child_policy' in targetObj && Array.isArray(targetObj.child_policy))) { throw new Error(`Weighted target ${key} must have a child_policy array`); } + const childConfig = selectLbConfigFromList(targetObj.child_policy); + if (!childConfig) { + throw new Error(`Weighted target ${key} config parsing failed`); + } const validatedTarget: WeightedTarget = { weight: targetObj.weight, - child_policy: targetObj.child_policy.map(validateLoadBalancingConfig) + child_policy: childConfig } targetsMap.set(key, validatedTarget); } @@ -171,10 +192,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void { this.weight = lbConfig.weight; - const childConfig = getFirstUsableConfig(lbConfig.child_policy); - if (childConfig !== null) { - this.childBalancer.updateAddressList(addressList, childConfig, attributes); - } + this.childBalancer.updateAddressList(addressList, lbConfig.child_policy, attributes); } exitIdle(): void { this.childBalancer.exitIdle(); @@ -301,7 +319,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { this.channelControlHelper.updateState(connectivityState, picker); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof WeightedTargetLoadBalancingConfig)) { // Reject a config of the wrong type trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); @@ -384,4 +402,4 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { export function setup() { registerLoadBalancerType(TYPE_NAME, WeightedTargetLoadBalancer, WeightedTargetLoadBalancingConfig); -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index edfd52016..e5db45afd 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -19,8 +19,6 @@ import { experimental, logVerbosity, status as Status, Metadata, connectivitySta import { validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from "./xds-client"; -import LoadBalancingConfig = experimental.LoadBalancingConfig; -import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import registerLoadBalancerType = experimental.registerLoadBalancerType; import SubchannelAddress = experimental.SubchannelAddress; @@ -31,7 +29,8 @@ import PickResultType = experimental.PickResultType; import ChannelControlHelper = experimental.ChannelControlHelper; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import createChildChannelControlHelper = experimental.createChildChannelControlHelper; -import getFirstUsableConfig = experimental.getFirstUsableConfig; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; +import selectLbConfigFromList = experimental.selectLbConfigFromList; const TRACER_NAME = 'xds_cluster_impl'; @@ -58,7 +57,7 @@ function validateDropCategory(obj: any): DropCategory { return obj; } -export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { +class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { private maxConcurrentRequests: number; getLoadBalancerName(): string { return TYPE_NAME; @@ -67,7 +66,7 @@ export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { const jsonObj: {[key: string]: any} = { cluster: this.cluster, drop_categories: this.dropCategories, - child_policy: this.childPolicy.map(policy => policy.toJsonObject()), + child_policy: [this.childPolicy.toJsonObject()], max_concurrent_requests: this.maxConcurrentRequests }; if (this.edsServiceName !== undefined) { @@ -81,7 +80,7 @@ export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { }; } - constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: LoadBalancingConfig[], private edsServiceName?: string, private lrsLoadReportingServer?: XdsServerConfig, maxConcurrentRequests?: number) { + constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: TypedLoadBalancingConfig, private edsServiceName?: string, private lrsLoadReportingServer?: XdsServerConfig, maxConcurrentRequests?: number) { this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; } @@ -125,7 +124,11 @@ export class XdsClusterImplLoadBalancingConfig implements LoadBalancingConfig { if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { throw new Error('xds_cluster_impl config must have an array field child_policy'); } - return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), obj.child_policy.map(validateLoadBalancingConfig), obj.eds_service_name, obj.lrs_load_reporting_server ? validateXdsServerConfig(obj.lrs_load_reporting_server) : undefined, obj.max_concurrent_requests); + const childConfig = selectLbConfigFromList(obj.child_policy); + if (!childConfig) { + throw new Error('xds_cluster_impl config child_policy parsing failed'); + } + return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), childConfig, obj.eds_service_name, obj.lrs_load_reporting_server ? validateXdsServerConfig(obj.lrs_load_reporting_server) : undefined, obj.max_concurrent_requests); } } @@ -234,7 +237,7 @@ class XdsClusterImplBalancer implements LoadBalancer { } })); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsClusterImplLoadBalancingConfig)) { trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); return; @@ -251,7 +254,7 @@ class XdsClusterImplBalancer implements LoadBalancer { ); } - this.childBalancer.updateAddressList(addressList, getFirstUsableConfig(lbConfig.getChildPolicy(), true), attributes); + this.childBalancer.updateAddressList(addressList, lbConfig.getChildPolicy(), attributes); } exitIdle(): void { this.childBalancer.exitIdle(); diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index bfdb4dccc..ce3207dfd 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -17,8 +17,7 @@ import { connectivityState as ConnectivityState, status as Status, experimental, logVerbosity, Metadata, status } from "@grpc/grpc-js/"; -import LoadBalancingConfig = experimental.LoadBalancingConfig; -import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import Picker = experimental.Picker; import PickResult = experimental.PickResult; @@ -28,8 +27,8 @@ import UnavailablePicker = experimental.UnavailablePicker; import QueuePicker = experimental.QueuePicker; import SubchannelAddress = experimental.SubchannelAddress; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; -import getFirstUsableConfig = experimental.getFirstUsableConfig; import ChannelControlHelper = experimental.ChannelControlHelper; +import selectLbConfigFromList = experimental.selectLbConfigFromList; import registerLoadBalancerType = experimental.registerLoadBalancerType; const TRACER_NAME = 'xds_cluster_manager'; @@ -40,16 +39,12 @@ function trace(text: string): void { const TYPE_NAME = 'xds_cluster_manager'; -interface ClusterManagerChild { - child_policy: LoadBalancingConfig[]; -} - -export class XdsClusterManagerLoadBalancingConfig implements LoadBalancingConfig { +class XdsClusterManagerLoadBalancingConfig implements TypedLoadBalancingConfig { getLoadBalancerName(): string { return TYPE_NAME; } - constructor(private children: Map) {} + constructor(private children: Map) {} getChildren() { return this.children; @@ -57,9 +52,9 @@ export class XdsClusterManagerLoadBalancingConfig implements LoadBalancingConfig toJsonObject(): object { const childrenField: {[key: string]: object} = {}; - for (const [childName, childValue] of this.children.entries()) { + for (const [childName, childPolicy] of this.children.entries()) { childrenField[childName] = { - child_policy: childValue.child_policy.map(policy => policy.toJsonObject()) + child_policy: [childPolicy.toJsonObject()] }; } return { @@ -70,19 +65,20 @@ export class XdsClusterManagerLoadBalancingConfig implements LoadBalancingConfig } static createFromJson(obj: any): XdsClusterManagerLoadBalancingConfig { - const childrenMap: Map = new Map(); + const childrenMap: Map = new Map(); if (!('children' in obj && obj.children !== null && typeof obj.children === 'object')) { throw new Error('xds_cluster_manager config must have a children map'); } - for (const key of obj.children) { + for (const key of Object.keys(obj.children)) { const childObj = obj.children[key]; if (!('child_policy' in childObj && Array.isArray(childObj.child_policy))) { throw new Error(`xds_cluster_manager child ${key} must have a child_policy array`); } - const validatedChild = { - child_policy: childObj.child_policy.map(validateLoadBalancingConfig) - }; - childrenMap.set(key, validatedChild); + const childPolicy = selectLbConfigFromList(childObj.child_policy); + if (childPolicy === null) { + throw new Error(`xds_cluster_mananger child ${key} has no recognized sucessfully parsed child_policy`); + } + childrenMap.set(key, childPolicy); } return new XdsClusterManagerLoadBalancingConfig(childrenMap); } @@ -115,7 +111,7 @@ class XdsClusterManagerPicker implements Picker { } interface XdsClusterManagerChild { - updateAddressList(addressList: SubchannelAddress[], lbConfig: ClusterManagerChild, attributes: { [key: string]: unknown; }): void; + updateAddressList(addressList: SubchannelAddress[], childConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void; exitIdle(): void; resetBackoff(): void; destroy(): void; @@ -146,11 +142,8 @@ class XdsClusterManager implements LoadBalancer { this.picker = picker; this.parent.maybeUpdateState(); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: ClusterManagerChild, attributes: { [key: string]: unknown; }): void { - const childConfig = getFirstUsableConfig(lbConfig.child_policy); - if (childConfig !== null) { - this.childBalancer.updateAddressList(addressList, childConfig, attributes); - } + updateAddressList(addressList: SubchannelAddress[], childConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + this.childBalancer.updateAddressList(addressList, childConfig, attributes); } exitIdle(): void { this.childBalancer.exitIdle(); @@ -241,8 +234,8 @@ class XdsClusterManager implements LoadBalancer { ); this.channelControlHelper.updateState(connectivityState, picker); } - - updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + + updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsClusterManagerLoadBalancingConfig)) { // Reject a config of the wrong type trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); @@ -296,4 +289,4 @@ class XdsClusterManager implements LoadBalancer { export function setup() { registerLoadBalancerType(TYPE_NAME, XdsClusterManager, XdsClusterManagerLoadBalancingConfig); -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index 40f67c476..f3c64aecb 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -15,29 +15,30 @@ * */ -import { experimental, logVerbosity } from "@grpc/grpc-js"; +import { LoadBalancingConfig, Metadata, connectivityState, experimental, logVerbosity, status } from "@grpc/grpc-js"; import { registerLoadBalancerType } from "@grpc/grpc-js/build/src/load-balancer"; import { EXPERIMENTAL_OUTLIER_DETECTION } from "./environment"; import { Locality__Output } from "./generated/envoy/config/core/v3/Locality"; import { ClusterLoadAssignment__Output } from "./generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; -import { LrsLoadBalancingConfig } from "./load-balancer-lrs"; -import { LocalitySubchannelAddress, PriorityChild, PriorityLoadBalancingConfig } from "./load-balancer-priority"; -import { WeightedTarget, WeightedTargetLoadBalancingConfig } from "./load-balancer-weighted-target"; +import { LocalitySubchannelAddress, PriorityChildRaw } from "./load-balancer-priority"; import { getSingletonXdsClient, Watcher, XdsClient } from "./xds-client"; -import { DropCategory, XdsClusterImplLoadBalancingConfig } from "./load-balancer-xds-cluster-impl"; +import { DropCategory } from "./load-balancer-xds-cluster-impl"; -import LoadBalancingConfig = experimental.LoadBalancingConfig; -import validateLoadBalancingConfig = experimental.validateLoadBalancingConfig; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import Resolver = experimental.Resolver; import SubchannelAddress = experimental.SubchannelAddress; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import createResolver = experimental.createResolver; import ChannelControlHelper = experimental.ChannelControlHelper; -import OutlierDetectionLoadBalancingConfig = experimental.OutlierDetectionLoadBalancingConfig; +import OutlierDetectionRawConfig = experimental.OutlierDetectionRawConfig; import subchannelAddressToString = experimental.subchannelAddressToString; +import selectLbConfigFromList = experimental.selectLbConfigFromList; +import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; +import UnavailablePicker = experimental.UnavailablePicker; import { serverConfigEqual, validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; import { EndpointResourceType } from "./xds-resource-type/endpoint-resource-type"; +import { WeightedTargetRaw } from "./load-balancer-weighted-target"; const TRACER_NAME = 'xds_cluster_resolver'; @@ -52,7 +53,7 @@ export interface DiscoveryMechanism { type: 'EDS' | 'LOGICAL_DNS'; eds_service_name?: string; dns_hostname?: string; - outlier_detection?: OutlierDetectionLoadBalancingConfig; + outlier_detection?: OutlierDetectionRawConfig; } function validateDiscoveryMechanism(obj: any): DiscoveryMechanism { @@ -62,41 +63,34 @@ function validateDiscoveryMechanism(obj: any): DiscoveryMechanism { if (!('type' in obj && (obj.type === 'EDS' || obj.type === 'LOGICAL_DNS'))) { throw new Error('discovery_mechanisms entry must have a field "type" with the value "EDS" or "LOGICAL_DNS"'); } - if ('max_concurrent_requests' in obj && typeof obj.max_concurrent_requests !== "number") { + if ('max_concurrent_requests' in obj && obj.max_concurrent_requests !== undefined && typeof obj.max_concurrent_requests !== "number") { throw new Error('discovery_mechanisms entry max_concurrent_requests field must be a number if provided'); } - if ('eds_service_name' in obj && typeof obj.eds_service_name !== 'string') { + if ('eds_service_name' in obj && obj.eds_service_name !== undefined && typeof obj.eds_service_name !== 'string') { throw new Error('discovery_mechanisms entry eds_service_name field must be a string if provided'); } - if ('dns_hostname' in obj && typeof obj.dns_hostname !== 'string') { + if ('dns_hostname' in obj && obj.dns_hostname !== undefined && typeof obj.dns_hostname !== 'string') { throw new Error('discovery_mechanisms entry dns_hostname field must be a string if provided'); } - if (EXPERIMENTAL_OUTLIER_DETECTION) { - const outlierDetectionConfig = validateLoadBalancingConfig(obj.outlier_detection); - if (!(outlierDetectionConfig instanceof OutlierDetectionLoadBalancingConfig)) { - throw new Error('eds config outlier_detection must be a valid outlier detection config if provided'); - } - return {...obj, lrs_load_reporting_server: validateXdsServerConfig(obj.lrs_load_reporting_server), outlier_detection: outlierDetectionConfig}; - } - return obj; + return {...obj, lrs_load_reporting_server: obj.lrs_load_reporting_server ? validateXdsServerConfig(obj.lrs_load_reporting_server) : undefined}; } const TYPE_NAME = 'xds_cluster_resolver'; -export class XdsClusterResolverLoadBalancingConfig implements LoadBalancingConfig { +class XdsClusterResolverLoadBalancingConfig implements TypedLoadBalancingConfig { getLoadBalancerName(): string { return TYPE_NAME; } toJsonObject(): object { return { [TYPE_NAME]: { - discovery_mechanisms: this.discoveryMechanisms.map(mechanism => ({...mechanism, outlier_detection: mechanism.outlier_detection?.toJsonObject()})), - locality_picking_policy: this.localityPickingPolicy.map(policy => policy.toJsonObject()), - endpoint_picking_policy: this.endpointPickingPolicy.map(policy => policy.toJsonObject()) + discovery_mechanisms: this.discoveryMechanisms, + locality_picking_policy: this.localityPickingPolicy, + endpoint_picking_policy: this.endpointPickingPolicy } } } - + constructor(private discoveryMechanisms: DiscoveryMechanism[], private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[]) {} getDiscoveryMechanisms() { @@ -123,8 +117,8 @@ export class XdsClusterResolverLoadBalancingConfig implements LoadBalancingConfi } return new XdsClusterResolverLoadBalancingConfig( obj.discovery_mechanisms.map(validateDiscoveryMechanism), - obj.locality_picking_policy.map(validateLoadBalancingConfig), - obj.endpoint_picking_policy.map(validateLoadBalancingConfig) + obj.locality_picking_policy, + obj.endpoint_picking_policy ); } } @@ -264,16 +258,15 @@ export class XdsClusterResolver implements LoadBalancer { } } const fullPriorityList: string[] = []; - const priorityChildren = new Map(); + const priorityChildren: {[name: string]: PriorityChildRaw} = {}; const addressList: LocalitySubchannelAddress[] = []; for (const entry of this.discoveryMechanismList) { const newPriorityNames: string[] = []; const newLocalityPriorities = new Map(); - const defaultEndpointPickingPolicy = entry.discoveryMechanism.type === 'EDS' ? validateLoadBalancingConfig({ round_robin: {} }) : validateLoadBalancingConfig({ pick_first: {} }); - const endpointPickingPolicy: LoadBalancingConfig[] = [ - ...this.latestConfig.getEndpointPickingPolicy(), - defaultEndpointPickingPolicy - ]; + const configEndpointPickingPolicy = this.latestConfig.getEndpointPickingPolicy(); + const defaultEndpointPickingPolicy: LoadBalancingConfig = entry.discoveryMechanism.type === 'EDS' ? { round_robin: {} } : { pick_first: {} }; + const endpointPickingPolicy: LoadBalancingConfig[] = configEndpointPickingPolicy.length > 0 ? configEndpointPickingPolicy : [defaultEndpointPickingPolicy]; + for (const [priority, priorityEntry] of entry.latestUpdate!.entries()) { /** * Highest (smallest number) priority value that any of the localities in @@ -308,18 +301,25 @@ export class XdsClusterResolver implements LoadBalancer { } newPriorityNames[priority] = newPriorityName; - const childTargets = new Map(); + const childTargets: {[locality: string]: WeightedTargetRaw} = {}; for (const localityObj of priorityEntry.localities) { let childPolicy: LoadBalancingConfig[]; if (entry.discoveryMechanism.lrs_load_reporting_server !== undefined) { - childPolicy = [new LrsLoadBalancingConfig(entry.discoveryMechanism.cluster, entry.discoveryMechanism.eds_service_name ?? '', entry.discoveryMechanism.lrs_load_reporting_server, localityObj.locality, endpointPickingPolicy)]; + childPolicy = [{ + lrs: { + cluster_name: entry.discoveryMechanism.cluster, + eds_service_name: entry.discoveryMechanism.eds_service_name ?? '', + locality: {...localityObj.locality}, + lrs_load_reporting_server: {...entry.discoveryMechanism.lrs_load_reporting_server} + } + }]; } else { childPolicy = endpointPickingPolicy; } - childTargets.set(localityToName(localityObj.locality), { + childTargets[localityToName(localityObj.locality)] = { weight: localityObj.weight, child_policy: childPolicy, - }); + }; for (const address of localityObj.addresses) { addressList.push({ localityPath: [ @@ -331,34 +331,65 @@ export class XdsClusterResolver implements LoadBalancer { } newLocalityPriorities.set(localityToName(localityObj.locality), priority); } - const weightedTargetConfig = new WeightedTargetLoadBalancingConfig(childTargets); - const xdsClusterImplConfig = new XdsClusterImplLoadBalancingConfig(entry.discoveryMechanism.cluster, priorityEntry.dropCategories, [weightedTargetConfig], entry.discoveryMechanism.eds_service_name, entry.discoveryMechanism.lrs_load_reporting_server, entry.discoveryMechanism.max_concurrent_requests); - let outlierDetectionConfig: OutlierDetectionLoadBalancingConfig | undefined; + const xdsClusterImplConfig = { + xds_cluster_impl: { + cluster: entry.discoveryMechanism.cluster, + drop_categories: priorityEntry.dropCategories, + max_concurrent_requests: entry.discoveryMechanism.max_concurrent_requests, + eds_service_name: entry.discoveryMechanism.eds_service_name, + lrs_load_reporting_server: entry.discoveryMechanism.lrs_load_reporting_server, + child_policy: [{ + weighted_target: { + targets: childTargets + } + }] + } + } + let priorityChildConfig: LoadBalancingConfig; if (EXPERIMENTAL_OUTLIER_DETECTION) { - outlierDetectionConfig = entry.discoveryMechanism.outlier_detection?.copyWithChildPolicy([xdsClusterImplConfig]); + priorityChildConfig = { + outlier_detection: { + ...entry.discoveryMechanism.outlier_detection, + child_policy: [xdsClusterImplConfig] + } + } + } else { + priorityChildConfig = xdsClusterImplConfig; } - const priorityChildConfig = outlierDetectionConfig ?? xdsClusterImplConfig; - - priorityChildren.set(newPriorityName, { + + priorityChildren[newPriorityName] = { config: [priorityChildConfig], ignore_reresolution_requests: entry.discoveryMechanism.type === 'EDS' - }); + }; } entry.localityPriorities = newLocalityPriorities; entry.priorityNames = newPriorityNames; fullPriorityList.push(...newPriorityNames); } - const childConfig: PriorityLoadBalancingConfig = new PriorityLoadBalancingConfig(priorityChildren, fullPriorityList); + const childConfig = { + priority: { + children: priorityChildren, + priorities: fullPriorityList + } + } + let typedChildConfig: TypedLoadBalancingConfig; + try { + typedChildConfig = parseLoadBalancingConfig(childConfig); + } catch (e) { + trace('LB policy config parsing failed with error ' + (e as Error).message); + this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `LB policy config parsing failed with error ${(e as Error).message}`, metadata: new Metadata()})); + return; + } trace('Child update addresses: ' + addressList.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); - trace('Child update priority config: ' + JSON.stringify(childConfig.toJsonObject(), undefined, 2)); + trace('Child update priority config: ' + JSON.stringify(childConfig, undefined, 2)); this.childBalancer.updateAddressList( addressList, - childConfig, + typedChildConfig, this.latestAttributes ); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsClusterResolverLoadBalancingConfig)) { trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2)); return; @@ -459,7 +490,7 @@ function maybeServerConfigEqual(config1: XdsServerConfig | undefined, config2: X } export class XdsClusterResolverChildPolicyHandler extends ChildLoadBalancerHandler { - protected configUpdateRequiresNewPolicyInstance(oldConfig: LoadBalancingConfig, newConfig: LoadBalancingConfig): boolean { + protected configUpdateRequiresNewPolicyInstance(oldConfig: TypedLoadBalancingConfig, newConfig: TypedLoadBalancingConfig): boolean { if (!(oldConfig instanceof XdsClusterResolverLoadBalancingConfig && newConfig instanceof XdsClusterResolverLoadBalancingConfig)) { return super.configUpdateRequiresNewPolicyInstance(oldConfig, newConfig); } diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index a934e7285..cd830d521 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -19,23 +19,19 @@ import * as protoLoader from '@grpc/proto-loader'; import { RE2 } from 're2-wasm'; import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client'; -import { StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions } from '@grpc/grpc-js'; +import { StatusObject, status, logVerbosity, Metadata, experimental, ChannelOptions, ServiceConfig, LoadBalancingConfig, RetryPolicy } from '@grpc/grpc-js'; import Resolver = experimental.Resolver; import GrpcUri = experimental.GrpcUri; import ResolverListener = experimental.ResolverListener; import uriToString = experimental.uriToString; -import ServiceConfig = experimental.ServiceConfig; import registerResolver = experimental.registerResolver; import { Listener__Output } from './generated/envoy/config/listener/v3/Listener'; import { RouteConfiguration__Output } from './generated/envoy/config/route/v3/RouteConfiguration'; import { HttpConnectionManager__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager'; -import { CdsLoadBalancingConfig } from './load-balancer-cds'; import { VirtualHost__Output } from './generated/envoy/config/route/v3/VirtualHost'; import { RouteMatch__Output } from './generated/envoy/config/route/v3/RouteMatch'; import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderMatcher'; import ConfigSelector = experimental.ConfigSelector; -import LoadBalancingConfig = experimental.LoadBalancingConfig; -import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluster-manager'; import { ContainsValueMatcher, ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; import { envoyFractionToFraction, Fraction } from "./fraction"; import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; @@ -46,10 +42,10 @@ import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTop import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_FEDERATION, EXPERIMENTAL_RETRY } from './environment'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; -import RetryPolicy = experimental.RetryPolicy; import { BootstrapInfo, loadBootstrapInfo, validateBootstrapConfig } from './xds-bootstrap'; import { ListenerResourceType } from './xds-resource-type/listener-resource-type'; import { RouteConfigurationResourceType } from './xds-resource-type/route-config-resource-type'; +import { protoDurationToDuration } from './duration'; const TRACER_NAME = 'xds_resolver'; @@ -208,20 +204,6 @@ function getPredicateForMatcher(routeMatch: RouteMatch__Output): Matcher { return new FullMatcher(pathMatcher, headerMatchers, runtimeFraction); } -/** - * Convert a Duration protobuf message object to a Duration object as used in - * the ServiceConfig definition. The difference is that the protobuf message - * defines seconds as a long, which is represented as a string in JavaScript, - * and the one used in the service config defines it as a number. - * @param duration - */ -function protoDurationToDuration(duration: Duration__Output): Duration { - return { - seconds: Number.parseInt(duration.seconds), - nanos: duration.nanos - } -} - function protoDurationToSecondsString(duration: Duration__Output): string { return `${duration.seconds + duration.nanos / 1_000_000_000}s`; } @@ -235,7 +217,7 @@ function getDefaultRetryMaxInterval(baseInterval: string): string { /** * Encode a text string as a valid path of a URI, as specified in RFC-3986 section 3.3 * @param uriPath A value representing an unencoded URI path - * @returns + * @returns */ function encodeURIPath(uriPath: string): string { return uriPath.replace(/[^A-Za-z0-9._~!$&^()*+,;=/-]/g, substring => encodeURIComponent(substring)); @@ -447,7 +429,7 @@ class XdsResolver implements Resolver { } } } - let retryPolicy: RetryPolicy | undefined = undefined; + let retryPolicy: RetryPolicy | undefined = undefined; if (EXPERIMENTAL_RETRY) { const retryConfig = route.route!.retry_policy ?? virtualHost.retry_policy; if (retryConfig) { @@ -458,10 +440,10 @@ class XdsResolver implements Resolver { } } if (retryableStatusCodes.length > 0) { - const baseInterval = retryConfig.retry_back_off?.base_interval ? - protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) : + const baseInterval = retryConfig.retry_back_off?.base_interval ? + protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) : DEFAULT_RETRY_BASE_INTERVAL; - const maxInterval = retryConfig.retry_back_off?.max_interval ? + const maxInterval = retryConfig.retry_back_off?.max_interval ? protoDurationToSecondsString(retryConfig.retry_back_off.max_interval) : getDefaultRetryMaxInterval(baseInterval); retryPolicy = { @@ -602,11 +584,11 @@ class XdsResolver implements Resolver { trace(matcher.toString()); trace('=> ' + action.toString()); } - const clusterConfigMap = new Map(); + const clusterConfigMap: {[key: string]: {child_policy: LoadBalancingConfig[]}} = {}; for (const clusterName of this.clusterRefcounts.keys()) { - clusterConfigMap.set(clusterName, {child_policy: [new CdsLoadBalancingConfig(clusterName)]}); + clusterConfigMap[clusterName] = {child_policy: [{cds: {cluster: clusterName}}]}; } - const lbPolicyConfig = new XdsClusterManagerLoadBalancingConfig(clusterConfigMap); + const lbPolicyConfig = {xds_cluster_manager: {children: clusterConfigMap}}; const serviceConfig: ServiceConfig = { methodConfig: [], loadBalancingConfig: [lbPolicyConfig] @@ -634,7 +616,7 @@ class XdsResolver implements Resolver { this.isLdsWatcherActive = true; } catch (e) { - this.reportResolutionError(e.message); + this.reportResolutionError((e as Error).message); } } } @@ -647,7 +629,7 @@ class XdsResolver implements Resolver { try { this.bootstrapInfo = loadBootstrapInfo(); } catch (e) { - this.reportResolutionError(e.message); + this.reportResolutionError((e as Error).message); } this.startResolution(); } diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts index 5ae5885af..83530f1b4 100644 --- a/packages/grpc-js-xds/src/route-action.ts +++ b/packages/grpc-js-xds/src/route-action.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { experimental } from '@grpc/grpc-js'; +import { MethodConfig, experimental } from '@grpc/grpc-js'; import Duration = experimental.Duration; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; -import MethodConfig = experimental.MethodConfig; export interface ClusterResult { name: string; @@ -101,4 +100,4 @@ export class WeightedClusterRouteAction implements RouteAction { const clusterListString = this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ') return 'WeightedCluster(' + clusterListString + ', ' + JSON.stringify(this.methodConfig) + ')'; } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index f431bf238..e617bef94 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -29,15 +29,7 @@ import { Any__Output } from "../generated/google/protobuf/Any"; import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; import { Watcher, XdsClient } from "../xds-client"; - -export interface OutlierDetectionUpdate { - intervalMs: number | null; - baseEjectionTimeMs: number | null; - maxEjectionTimeMs: number | null; - maxEjectionPercent: number | null; - successRateConfig: Partial | null; - failurePercentageConfig: Partial | null; -} +import { protoDurationToDuration } from "../duration"; export interface CdsUpdate { type: 'AGGREGATE' | 'EDS' | 'LOGICAL_DNS'; @@ -47,29 +39,20 @@ export interface CdsUpdate { maxConcurrentRequests?: number; edsServiceName?: string; dnsHostname?: string; - outlierDetectionUpdate?: OutlierDetectionUpdate; + outlierDetectionUpdate?: experimental.OutlierDetectionRawConfig; } -function durationToMs(duration: Duration__Output): number { - return (Number(duration.seconds) * 1_000 + duration.nanos / 1_000_000) | 0; -} - -function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Output | null): OutlierDetectionUpdate | undefined { +function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Output | null): experimental.OutlierDetectionRawConfig | undefined { if (!EXPERIMENTAL_OUTLIER_DETECTION) { return undefined; } if (!outlierDetection) { /* No-op outlier detection config, with all fields unset. */ return { - intervalMs: null, - baseEjectionTimeMs: null, - maxEjectionTimeMs: null, - maxEjectionPercent: null, - successRateConfig: null, - failurePercentageConfig: null + child_policy: [] }; } - let successRateConfig: Partial | null = null; + let successRateConfig: Partial | undefined = undefined; /* Success rate ejection is enabled by default, so we only disable it if * enforcing_success_rate is set and it has the value 0 */ if (!outlierDetection.enforcing_success_rate || outlierDetection.enforcing_success_rate.value > 0) { @@ -80,7 +63,7 @@ function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Outpu stdev_factor: outlierDetection.success_rate_stdev_factor?.value }; } - let failurePercentageConfig: Partial | null = null; + let failurePercentageConfig: Partial | undefined = undefined; /* Failure percentage ejection is disabled by default, so we only enable it * if enforcing_failure_percentage is set and it has a value greater than 0 */ if (outlierDetection.enforcing_failure_percentage && outlierDetection.enforcing_failure_percentage.value > 0) { @@ -92,19 +75,20 @@ function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Outpu } } return { - intervalMs: outlierDetection.interval ? durationToMs(outlierDetection.interval) : null, - baseEjectionTimeMs: outlierDetection.base_ejection_time ? durationToMs(outlierDetection.base_ejection_time) : null, - maxEjectionTimeMs: outlierDetection.max_ejection_time ? durationToMs(outlierDetection.max_ejection_time) : null, - maxEjectionPercent : outlierDetection.max_ejection_percent?.value ?? null, - successRateConfig: successRateConfig, - failurePercentageConfig: failurePercentageConfig + interval: outlierDetection.interval ? protoDurationToDuration(outlierDetection.interval) : undefined, + base_ejection_time: outlierDetection.base_ejection_time ? protoDurationToDuration(outlierDetection.base_ejection_time) : undefined, + max_ejection_time: outlierDetection.max_ejection_time ? protoDurationToDuration(outlierDetection.max_ejection_time) : undefined, + max_ejection_percent: outlierDetection.max_ejection_percent?.value, + success_rate_ejection: successRateConfig, + failure_percentage_ejection: failurePercentageConfig, + child_policy: [] }; } export class ClusterResourceType extends XdsResourceType { private static singleton: ClusterResourceType = new ClusterResourceType(); - + private constructor() { super(); } @@ -124,7 +108,7 @@ export class ClusterResourceType extends XdsResourceType { /* The maximum values here come from the official Protobuf documentation: * https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration */ - return Number(duration.seconds) >= 0 && + return Number(duration.seconds) >= 0 && Number(duration.seconds) <= 315_576_000_000 && duration.nanos >= 0 && duration.nanos <= 999_999_999; From a4ba9253523755c979c53b5cab978f7afd2ba8a6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 8 Aug 2023 10:37:20 -0700 Subject: [PATCH 554/694] grpc-js: Add null check in pick_first array access --- packages/grpc-js/src/load-balancer-pick-first.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 08971980b..37bc8e0ff 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -306,7 +306,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.children[subchannelIndex].subchannel.getAddress() ); process.nextTick(() => { - this.children[subchannelIndex].subchannel.startConnecting(); + this.children[subchannelIndex]?.subchannel.startConnecting(); }); } this.connectionDelayTimeout = setTimeout(() => { From 8f9bd7a9ee600ec0ac014ec21a782e2a44dd54ff Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 9 Aug 2023 10:45:07 -0700 Subject: [PATCH 555/694] grpc-js-xds: Fix handling of LRS server configs --- packages/grpc-js-xds/src/xds-bootstrap.ts | 26 ++++++++-------- packages/grpc-js-xds/src/xds-client.ts | 16 +++++----- packages/grpc-js-xds/test/test-bootstrap.ts | 33 +++++++++++++++++++++ 3 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 packages/grpc-js-xds/test/test-bootstrap.ts diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index 327ee1b06..f5df10dfa 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -41,9 +41,9 @@ export interface ChannelCredsConfig { } export interface XdsServerConfig { - serverUri: string; - channelCreds: ChannelCredsConfig[]; - serverFeatures: string[]; + server_uri: string; + channel_creds: ChannelCredsConfig[]; + server_features: string[]; } export interface Authority { @@ -61,19 +61,19 @@ export interface BootstrapInfo { const KNOWN_SERVER_FEATURES = ['ignore_resource_deletion']; export function serverConfigEqual(config1: XdsServerConfig, config2: XdsServerConfig): boolean { - if (config1.serverUri !== config2.serverUri) { + if (config1.server_uri !== config2.server_uri) { return false; } for (const feature of KNOWN_SERVER_FEATURES) { - if ((feature in config1.serverFeatures) !== (feature in config2.serverFeatures)) { + if ((feature in config1.server_features) !== (feature in config2.server_features)) { return false; } } - if (config1.channelCreds.length !== config2.channelCreds.length) { + if (config1.channel_creds.length !== config2.channel_creds.length) { return false; } - for (const [index, creds1] of config1.channelCreds.entries()) { - const creds2 = config2.channelCreds[index]; + for (const [index, creds1] of config1.channel_creds.entries()) { + const creds2 = config2.channel_creds[index]; if (creds1.type !== creds2.type) { return false; } @@ -93,7 +93,7 @@ function validateChannelCredsConfig(obj: any): ChannelCredsConfig { `xds_servers.channel_creds.type field: expected string, got ${typeof obj.type}` ); } - if ('config' in obj) { + if ('config' in obj && obj.config !== undefined) { if (typeof obj.config !== 'object' || obj.config === null) { throw new Error( 'xds_servers.channel_creds config field must be an object if provided' @@ -152,9 +152,9 @@ export function validateXdsServerConfig(obj: any): XdsServerConfig { } } return { - serverUri: obj.server_uri, - channelCreds: obj.channel_creds.map(validateChannelCredsConfig), - serverFeatures: obj.server_features ?? [] + server_uri: obj.server_uri, + channel_creds: obj.channel_creds.map(validateChannelCredsConfig), + server_features: obj.server_features ?? [] }; } @@ -387,7 +387,7 @@ export function loadBootstrapInfo(): BootstrapInfo { return loadedBootstrapInfo; } - + throw new Error( 'The GRPC_XDS_BOOTSTRAP or GRPC_XDS_BOOTSTRAP_CONFIG environment variables need to be set to the path to the bootstrap file to use xDS' ); diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 2ae9618b3..464e26596 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -602,9 +602,9 @@ class ClusterLoadReportMap { * Get the indicated map entry if it exists, or create a new one if it does * not. Increments the refcount of that entry, so a call to this method * should correspond to a later call to unref - * @param clusterName - * @param edsServiceName - * @returns + * @param clusterName + * @param edsServiceName + * @returns */ getOrCreate(clusterName: string, edsServiceName: string): ClusterLoadReport { for (const statsObj of this.statsMap) { @@ -833,12 +833,12 @@ class XdsSingleServerClient { this.maybeStartLrsStream(); }); this.lrsBackoff.unref(); - this.ignoreResourceDeletion = xdsServerConfig.serverFeatures.includes('ignore_resource_deletion'); + this.ignoreResourceDeletion = xdsServerConfig.server_features.includes('ignore_resource_deletion'); const channelArgs = { // 5 minutes 'grpc.keepalive_time_ms': 5 * 60 * 1000 } - const credentialsConfigs = xdsServerConfig.channelCreds; + const credentialsConfigs = xdsServerConfig.channel_creds; let channelCreds: ChannelCredentials | null = null; for (const config of credentialsConfigs) { if (config.type === 'google_default') { @@ -849,8 +849,8 @@ class XdsSingleServerClient { break; } } - const serverUri = this.xdsServerConfig.serverUri - this.trace('Starting xDS client connected to server URI ' + this.xdsServerConfig.serverUri); + const serverUri = this.xdsServerConfig.server_uri + this.trace('Starting xDS client connected to server URI ' + this.xdsServerConfig.server_uri); /* Bootstrap validation rules guarantee that a matching channel credentials * config exists in the list. */ const channel = new Channel(serverUri, channelCreds!, channelArgs); @@ -949,7 +949,7 @@ class XdsSingleServerClient { } trace(text: string) { - trace(this.xdsServerConfig.serverUri + ' ' + text); + trace(this.xdsServerConfig.server_uri + ' ' + text); } subscribe(type: XdsResourceType, name: XdsResourceName) { diff --git a/packages/grpc-js-xds/test/test-bootstrap.ts b/packages/grpc-js-xds/test/test-bootstrap.ts new file mode 100644 index 000000000..f7874d2a1 --- /dev/null +++ b/packages/grpc-js-xds/test/test-bootstrap.ts @@ -0,0 +1,33 @@ +/* + * 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. + * + */ + +import * as assert from 'assert'; +import { validateXdsServerConfig } from "../src/xds-bootstrap"; + +describe('bootstrap', () => { + /* validateXdsServerConfig is used when creating the cds config, and then + * the resulting value is validated again when creating the + * xds_cluster_resolver config. */ + it('validateXdsServerConfig should be idempotent', () => { + const config = { + server_uri: 'localhost', + channel_creds: [{type: 'google_default'}], + server_features: ['test_feature'] + }; + assert.deepStrictEqual(validateXdsServerConfig(validateXdsServerConfig(config)), validateXdsServerConfig(config)); + }); +}); From 11e19fb4502de9fe9481b807969abea2a586f0f2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 9 Aug 2023 11:02:33 -0700 Subject: [PATCH 556/694] Enable LRS in local tests and fix LRS config generation bugs --- packages/grpc-js-xds/src/load-balancer-lrs.ts | 2 +- packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts | 3 ++- packages/grpc-js-xds/test/framework.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index 145b4deda..a0e568eaf 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -97,7 +97,7 @@ class LrsLoadBalancingConfig implements TypedLoadBalancingConfig { if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { throw new Error('lrs config must have a child_policy array'); } - const childConfig = selectLbConfigFromList(obj.config); + const childConfig = selectLbConfigFromList(obj.child_policy); if (!childConfig) { throw new Error('lrs config child_policy parsing failed'); } diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index f3c64aecb..dd9b80107 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -310,7 +310,8 @@ export class XdsClusterResolver implements LoadBalancer { cluster_name: entry.discoveryMechanism.cluster, eds_service_name: entry.discoveryMechanism.eds_service_name ?? '', locality: {...localityObj.locality}, - lrs_load_reporting_server: {...entry.discoveryMechanism.lrs_load_reporting_server} + lrs_load_reporting_server: {...entry.discoveryMechanism.lrs_load_reporting_server}, + child_policy: endpointPickingPolicy } }]; } else { diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index 4bb9fa142..8fd2b34e0 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -83,7 +83,8 @@ export class FakeEdsCluster implements FakeCluster { name: this.clusterName, type: 'EDS', eds_cluster_config: {eds_config: {ads: {}}, service_name: this.endpointName}, - lb_policy: 'ROUND_ROBIN' + lb_policy: 'ROUND_ROBIN', + lrs_server: {self: {}} } } From 7ae331bd930af590c2f77156b2fecda0d56c4616 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 9 Aug 2023 11:07:34 -0700 Subject: [PATCH 557/694] Also enable LRS for LOGICAL_DNS test cluster resources --- packages/grpc-js-xds/test/framework.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index 8fd2b34e0..250811206 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -157,7 +157,8 @@ export class FakeDnsCluster implements FakeCluster { } }] }] - } + }, + lrs_server: {self: {}} }; } getAllClusterConfigs(): Cluster[] { From 4f8db6907ef2f4c98bdad2cc60fb0ca873bfd144 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 10 Aug 2023 09:40:37 -0700 Subject: [PATCH 558/694] grpc-js-xds: Fix a typo in xds_cluster_impl parsing code --- .../grpc-js-xds/src/load-balancer-xds-cluster-impl.ts | 2 +- packages/grpc-js-xds/test/framework.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index e5db45afd..9a9304518 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -115,7 +115,7 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { if ('eds_service_name' in obj && !(obj.eds_service_name === undefined || typeof obj.eds_service_name === 'string')) { throw new Error('xds_cluster_impl config eds_service_name field must be a string if provided'); } - if ('max_concurrent_requests' in obj && (!obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { + if ('max_concurrent_requests' in obj && !(obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { throw new Error('xds_cluster_impl config max_concurrent_requests must be a number if provided'); } if (!('drop_categories' in obj && Array.isArray(obj.drop_categories))) { diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index 250811206..f5e7bc1de 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -84,7 +84,15 @@ export class FakeEdsCluster implements FakeCluster { type: 'EDS', eds_cluster_config: {eds_config: {ads: {}}, service_name: this.endpointName}, lb_policy: 'ROUND_ROBIN', - lrs_server: {self: {}} + lrs_server: {self: {}}, + circuit_breakers: { + thresholds: [ + { + priority: 'DEFAULT', + max_requests: {value: 1000} + } + ] + } } } From b2ad73a0f3244e80da7871a54809941d1b0dbf24 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 10 Aug 2023 13:54:43 -0700 Subject: [PATCH 559/694] grpc-js-xds: Add config parsing tests --- packages/grpc-js-xds/src/load-balancer-cds.ts | 7 +- packages/grpc-js-xds/src/load-balancer-lrs.ts | 5 +- .../grpc-js-xds/src/load-balancer-priority.ts | 3 +- packages/grpc-js-xds/src/xds-bootstrap.ts | 3 + .../grpc-js-xds/test/test-confg-parsing.ts | 370 ++++++++++++++++++ 5 files changed, 382 insertions(+), 6 deletions(-) create mode 100644 packages/grpc-js-xds/test/test-confg-parsing.ts diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 3ebcbbdf6..44de10ac4 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -65,11 +65,10 @@ class CdsLoadBalancingConfig implements TypedLoadBalancingConfig { } static createFromJson(obj: any): CdsLoadBalancingConfig { - if ('cluster' in obj) { - return new CdsLoadBalancingConfig(obj.cluster); - } else { - throw new Error('Missing "cluster" in cds load balancing config'); + if (!('cluster' in obj && typeof obj.cluster === 'string')) { + throw new Error('cds config must have a string field cluster'); } + return new CdsLoadBalancingConfig(obj.cluster); } } diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts index a0e568eaf..40c653a03 100644 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ b/packages/grpc-js-xds/src/load-balancer-lrs.ts @@ -46,7 +46,7 @@ class LrsLoadBalancingConfig implements TypedLoadBalancingConfig { [TYPE_NAME]: { cluster_name: this.clusterName, eds_service_name: this.edsServiceName, - lrs_load_reporting_server_name: this.lrsLoadReportingServer, + lrs_load_reporting_server: this.lrsLoadReportingServer, locality: this.locality, child_policy: [this.childPolicy.toJsonObject()] } @@ -97,6 +97,9 @@ class LrsLoadBalancingConfig implements TypedLoadBalancingConfig { if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { throw new Error('lrs config must have a child_policy array'); } + if (!('lrs_load_reporting_server' in obj && obj.lrs_load_reporting_server !== null && typeof obj.lrs_load_reporting_server === 'object')) { + throw new Error('lrs config must have an object field lrs_load_reporting_server'); + } const childConfig = selectLbConfigFromList(obj.child_policy); if (!childConfig) { throw new Error('lrs config child_policy parsing failed'); diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index 4d26a0f41..4372b0eac 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -81,7 +81,8 @@ class PriorityLoadBalancingConfig implements TypedLoadBalancingConfig { const childrenField: {[key: string]: object} = {} for (const [childName, childValue] of this.children.entries()) { childrenField[childName] = { - config: [childValue.config.toJsonObject()] + config: [childValue.config.toJsonObject()], + ignore_reresolution_requests: childValue.ignore_reresolution_requests }; } return { diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index f5df10dfa..fccd3edcf 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -112,6 +112,9 @@ const SUPPORTED_CHANNEL_CREDS_TYPES = [ ]; export function validateXdsServerConfig(obj: any): XdsServerConfig { + if (!(typeof obj === 'object' && obj !== null)) { + throw new Error('xDS server config must be an object'); + } if (!('server_uri' in obj)) { throw new Error('server_uri field missing in xds_servers element'); } diff --git a/packages/grpc-js-xds/test/test-confg-parsing.ts b/packages/grpc-js-xds/test/test-confg-parsing.ts new file mode 100644 index 000000000..ba91bb2e1 --- /dev/null +++ b/packages/grpc-js-xds/test/test-confg-parsing.ts @@ -0,0 +1,370 @@ +/* + * 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. + * + */ + +import { experimental, LoadBalancingConfig } from "@grpc/grpc-js"; +import { register } from "../src"; +import assert = require("assert"); +import parseLoadbalancingConfig = experimental.parseLoadBalancingConfig; + +register(); + +/** + * Describes a test case for config parsing. input is passed to + * parseLoadBalancingConfig. If error is set, the expectation is that that + * operation throws an error with a matching message. Otherwise, toJsonObject + * is called on the result, and it is expected to match output, or input if + * output is unset. + */ +interface TestCase { + name: string; + input: object, + output?: object; + error?: RegExp; +} + +/* The main purpose of these tests is to verify that configs that are expected + * to be valid parse successfully, and configs that are expected to be invalid + * throw errors. The specific output of this parsing is a lower priority + * concern. + * Note: some tests have an expected output that is different from the output, + * but all non-error tests additionally verify that parsing the output again + * produces the same output. */ +const allTestCases: {[lbPolicyName: string]: TestCase[]} = { + cds: [ + { + name: 'populated cluster field', + input: { + cluster: 'abc' + } + }, + { + name: 'empty', + input: {}, + error: /cluster/ + }, + { + name: 'non-string cluster', + input: { + cluster: 123 + }, + error: /string.*cluster/ + } + ], + xds_cluster_resolver: [ + { + name: 'empty fields', + input: { + discovery_mechanisms: [], + locality_picking_policy: [], + endpoint_picking_policy: [] + } + }, + { + name: 'missing discovery_mechanisms', + input: { + locality_picking_policy: [], + endpoint_picking_policy: [] + }, + error: /discovery_mechanisms/ + }, + { + name: 'missing locality_picking_policy', + input: { + discovery_mechanisms: [], + endpoint_picking_policy: [] + }, + error: /locality_picking_policy/ + }, + { + name: 'missing endpoint_picking_policy', + input: { + discovery_mechanisms: [], + locality_picking_policy: [] + }, + error: /endpoint_picking_policy/ + }, + { + name: 'discovery_mechanism: EDS', + input: { + discovery_mechanisms: [{ + cluster: 'abc', + type: 'EDS' + }], + locality_picking_policy: [], + endpoint_picking_policy: [] + }, + output: { + discovery_mechanisms: [{ + cluster: 'abc', + type: 'EDS', + lrs_load_reporting_server: undefined + }], + locality_picking_policy: [], + endpoint_picking_policy: [] + } + }, + { + name: 'discovery_mechanism: LOGICAL_DNS', + input: { + discovery_mechanisms: [{ + cluster: 'abc', + type: 'LOGICAL_DNS' + }], + locality_picking_policy: [], + endpoint_picking_policy: [] + }, + output: { + discovery_mechanisms: [{ + cluster: 'abc', + type: 'LOGICAL_DNS', + lrs_load_reporting_server: undefined + }], + locality_picking_policy: [], + endpoint_picking_policy: [] + } + }, + { + name: 'discovery_mechanism: undefined optional fields', + input: { + discovery_mechanisms: [{ + cluster: 'abc', + type: 'EDS', + max_concurrent_requests: undefined, + eds_service_name: undefined, + dns_hostname: undefined, + lrs_load_reporting_server: undefined + }], + locality_picking_policy: [], + endpoint_picking_policy: [] + } + }, + { + name: 'discovery_mechanism: populated optional fields', + input: { + discovery_mechanisms: [{ + cluster: 'abc', + type: 'EDS', + max_concurrent_requests: 100, + eds_service_name: 'def', + dns_hostname: 'localhost', + lrs_load_reporting_server: { + server_uri: 'localhost:12345', + channel_creds: [{ + type: 'google_default', + config: {} + }], + server_features: ['test'] + } + }], + locality_picking_policy: [], + endpoint_picking_policy: [] + } + } + ], + xds_cluster_impl: [ + { + name: 'only required fields', + input: { + cluster: 'abc', + drop_categories: [], + child_policy: [{round_robin: {}}] + }, + output: { + cluster: 'abc', + drop_categories: [], + child_policy: [{round_robin: {}}], + max_concurrent_requests: 1024 + } + }, + { + name: 'undefined optional fields', + input: { + cluster: 'abc', + drop_categories: [], + child_policy: [{round_robin: {}}], + eds_service_name: undefined, + max_concurrent_requests: undefined + }, + output: { + cluster: 'abc', + drop_categories: [], + child_policy: [{round_robin: {}}], + max_concurrent_requests: 1024 + } + }, + { + name: 'populated optional fields', + input: { + cluster: 'abc', + drop_categories: [{ + category: 'test', + requests_per_million: 100 + }], + child_policy: [{round_robin: {}}], + eds_service_name: 'def', + max_concurrent_requests: 123 + }, + } + ], + lrs: [ + { + name: 'only required fields', + input: { + cluster_name: 'abc', + eds_service_name: 'def', + locality: {}, + child_policy: [{round_robin: {}}], + lrs_load_reporting_server: { + server_uri: 'localhost:12345', + channel_creds: [{ + type: 'google_default', + config: {} + }], + server_features: ['test'] + } + }, + output: { + cluster_name: 'abc', + eds_service_name: 'def', + locality: { + region: '', + zone: '', + sub_zone: '' + }, + child_policy: [{round_robin: {}}], + lrs_load_reporting_server: { + server_uri: 'localhost:12345', + channel_creds: [{ + type: 'google_default', + config: {} + }], + server_features: ['test'] + } + } + }, + { + name: 'populated optional fields', + input: { + cluster_name: 'abc', + eds_service_name: 'def', + locality: { + region: 'a', + zone: 'b', + sub_zone: 'c' + }, + child_policy: [{round_robin: {}}], + lrs_load_reporting_server: { + server_uri: 'localhost:12345', + channel_creds: [{ + type: 'google_default', + config: {} + }], + server_features: ['test'] + } + } + } + ], + priority: [ + { + name: 'empty fields', + input: { + children: {}, + priorities: [] + } + }, + { + name: 'populated fields', + input: { + children: { + child0: { + config: [{round_robin: {}}], + ignore_reresolution_requests: true + }, + child1: { + config: [{round_robin: {}}], + ignore_reresolution_requests: false + } + }, + priorities: ['child0', 'child1'] + } + } + ], + weighted_target: [ + { + name: 'empty targets field', + input: { + targets: {} + } + }, + { + name: 'populated targets field', + input: { + targets: { + target0: { + weight: 1, + child_policy: [{round_robin: {}}] + }, + target1: { + weight: 2, + child_policy: [{round_robin: {}}] + } + } + } + } + ], + xds_cluster_manager: [ + { + name: 'empty children field', + input: { + children: {} + } + }, + { + name: 'populated children field', + input: { + children: { + child0: { + child_policy: [{round_robin: {}}] + } + } + } + } + ] +} + +describe('Load balancing policy config parsing', () => { + for (const [lbPolicyName, testCases] of Object.entries(allTestCases)) { + describe(lbPolicyName, () => { + for (const testCase of testCases) { + it(testCase.name, () => { + const lbConfigInput = {[lbPolicyName]: testCase.input}; + if (testCase.error) { + assert.throws(() => { + parseLoadbalancingConfig(lbConfigInput); + }, testCase.error); + } else { + const expectedOutput = testCase.output ?? testCase.input; + const parsedJson = parseLoadbalancingConfig(lbConfigInput).toJsonObject(); + assert.deepStrictEqual(parsedJson, {[lbPolicyName]: expectedOutput}); + // Test idempotency + assert.deepStrictEqual(parseLoadbalancingConfig(parsedJson).toJsonObject(), parsedJson); + } + }); + } + }); + } +}); From d7c27fb3aa329fc924d7ae35a4334849898ff18e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 11 Aug 2023 11:09:55 -0700 Subject: [PATCH 560/694] grpc-js: Add config parsing tests and fix outlier detection config parsing --- .../src/load-balancer-outlier-detection.ts | 26 ++- packages/grpc-js/test/test-confg-parsing.ts | 212 ++++++++++++++++++ 2 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 packages/grpc-js/test/test-confg-parsing.ts diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 00d0e0a53..be62b4c36 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -107,7 +107,7 @@ function validateFieldType( expectedType: TypeofValues, objectName?: string ) { - if (fieldName in obj && typeof obj[fieldName] !== expectedType) { + if (fieldName in obj && obj[fieldName] !== undefined && typeof obj[fieldName] !== expectedType) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; throw new Error( `outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[ @@ -123,7 +123,7 @@ function validatePositiveDuration( objectName?: string ) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; - if (fieldName in obj) { + if (fieldName in obj && obj[fieldName] !== undefined) { if (!isDuration(obj[fieldName])) { throw new Error( `outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[ @@ -149,7 +149,7 @@ function validatePositiveDuration( function validatePercentage(obj: any, fieldName: string, objectName?: string) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; validateFieldType(obj, fieldName, 'number', objectName); - if (fieldName in obj && !(obj[fieldName] >= 0 && obj[fieldName] <= 100)) { + if (fieldName in obj && obj[fieldName] !== undefined && !(obj[fieldName] >= 0 && obj[fieldName] <= 100)) { throw new Error( `outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)` ); @@ -201,13 +201,15 @@ export class OutlierDetectionLoadBalancingConfig } toJsonObject(): object { return { - interval: msToDuration(this.intervalMs), - base_ejection_time: msToDuration(this.baseEjectionTimeMs), - max_ejection_time: msToDuration(this.maxEjectionTimeMs), - max_ejection_percent: this.maxEjectionPercent, - success_rate_ejection: this.successRateEjection, - failure_percentage_ejection: this.failurePercentageEjection, - child_policy: [this.childPolicy.toJsonObject()] + outlier_detection: { + interval: msToDuration(this.intervalMs), + base_ejection_time: msToDuration(this.baseEjectionTimeMs), + max_ejection_time: msToDuration(this.maxEjectionTimeMs), + max_ejection_percent: this.maxEjectionPercent, + success_rate_ejection: this.successRateEjection ?? undefined, + failure_percentage_ejection: this.failurePercentageEjection ?? undefined, + child_policy: [this.childPolicy.toJsonObject()] + } }; } @@ -238,7 +240,7 @@ export class OutlierDetectionLoadBalancingConfig validatePositiveDuration(obj, 'base_ejection_time'); validatePositiveDuration(obj, 'max_ejection_time'); validatePercentage(obj, 'max_ejection_percent'); - if ('success_rate_ejection' in obj) { + if ('success_rate_ejection' in obj && obj.success_rate_ejection !== undefined) { if (typeof obj.success_rate_ejection !== 'object') { throw new Error( 'outlier detection config success_rate_ejection must be an object' @@ -268,7 +270,7 @@ export class OutlierDetectionLoadBalancingConfig 'success_rate_ejection' ); } - if ('failure_percentage_ejection' in obj) { + if ('failure_percentage_ejection' in obj && obj.failure_percentage_ejection !== undefined) { if (typeof obj.failure_percentage_ejection !== 'object') { throw new Error( 'outlier detection config failure_percentage_ejection must be an object' diff --git a/packages/grpc-js/test/test-confg-parsing.ts b/packages/grpc-js/test/test-confg-parsing.ts new file mode 100644 index 000000000..569b83f5e --- /dev/null +++ b/packages/grpc-js/test/test-confg-parsing.ts @@ -0,0 +1,212 @@ +/* + * 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. + * + */ + +import { experimental } from '../src'; +import * as assert from 'assert'; +import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; + +/** + * Describes a test case for config parsing. input is passed to + * parseLoadBalancingConfig. If error is set, the expectation is that that + * operation throws an error with a matching message. Otherwise, toJsonObject + * is called on the result, and it is expected to match output, or input if + * output is unset. + */ +interface TestCase { + name: string; + input: object, + output?: object; + error?: RegExp; +} + +/* The main purpose of these tests is to verify that configs that are expected + * to be valid parse successfully, and configs that are expected to be invalid + * throw errors. The specific output of this parsing is a lower priority + * concern. + * Note: some tests have an expected output that is different from the output, + * but all non-error tests additionally verify that parsing the output again + * produces the same output. */ +const allTestCases: {[lbPolicyName: string]: TestCase[]} = { + pick_first: [ + { + name: 'no fields set', + input: {}, + output: { + shuffleAddressList: false + } + }, + { + name: 'shuffleAddressList set', + input: { + shuffleAddressList: true + } + } + ], + round_robin: [ + { + name: 'no fields set', + input: {} + } + ], + outlier_detection: [ + { + name: 'only required fields set', + input: { + child_policy: [{round_robin: {}}] + }, + output: { + interval: { + seconds: 10, + nanos: 0 + }, + base_ejection_time: { + seconds: 30, + nanos: 0 + }, + max_ejection_time: { + seconds: 300, + nanos: 0 + }, + max_ejection_percent: 10, + success_rate_ejection: undefined, + failure_percentage_ejection: undefined, + child_policy: [{round_robin: {}}] + } + }, + { + name: 'all optional fields undefined', + input: { + interval: undefined, + base_ejection_time: undefined, + max_ejection_time: undefined, + max_ejection_percent: undefined, + success_rate_ejection: undefined, + failure_percentage_ejection: undefined, + child_policy: [{round_robin: {}}] + }, + output: { + interval: { + seconds: 10, + nanos: 0 + }, + base_ejection_time: { + seconds: 30, + nanos: 0 + }, + max_ejection_time: { + seconds: 300, + nanos: 0 + }, + max_ejection_percent: 10, + success_rate_ejection: undefined, + failure_percentage_ejection: undefined, + child_policy: [{round_robin: {}}] + } + }, + { + name: 'empty ejection configs', + input: { + success_rate_ejection: {}, + failure_percentage_ejection: {}, + child_policy: [{round_robin: {}}] + }, + output: { + interval: { + seconds: 10, + nanos: 0 + }, + base_ejection_time: { + seconds: 30, + nanos: 0 + }, + max_ejection_time: { + seconds: 300, + nanos: 0 + }, + max_ejection_percent: 10, + success_rate_ejection: { + stdev_factor: 1900, + enforcement_percentage: 100, + minimum_hosts: 5, + request_volume: 100 + }, + failure_percentage_ejection: { + threshold: 85, + enforcement_percentage: 100, + minimum_hosts: 5, + request_volume: 50, + }, + child_policy: [{round_robin: {}}] + } + }, + { + name: 'all fields populated', + input: { + interval: { + seconds: 20, + nanos: 0 + }, + base_ejection_time: { + seconds: 40, + nanos: 0 + }, + max_ejection_time: { + seconds: 400, + nanos: 0 + }, + max_ejection_percent: 20, + success_rate_ejection: { + stdev_factor: 1800, + enforcement_percentage: 90, + minimum_hosts: 4, + request_volume: 200 + }, + failure_percentage_ejection: { + threshold: 95, + enforcement_percentage: 90, + minimum_hosts: 4, + request_volume: 60, + }, + child_policy: [{round_robin: {}}] + + } + } + ] +} + +describe('Load balancing policy config parsing', () => { + for (const [lbPolicyName, testCases] of Object.entries(allTestCases)) { + describe(lbPolicyName, () => { + for (const testCase of testCases) { + it(testCase.name, () => { + const lbConfigInput = {[lbPolicyName]: testCase.input}; + if (testCase.error) { + assert.throws(() => { + parseLoadBalancingConfig(lbConfigInput); + }, testCase.error); + } else { + const expectedOutput = testCase.output ?? testCase.input; + const parsedJson = parseLoadBalancingConfig(lbConfigInput).toJsonObject(); + assert.deepStrictEqual(parsedJson, {[lbPolicyName]: expectedOutput}); + // Test idempotency + assert.deepStrictEqual(parseLoadBalancingConfig(parsedJson).toJsonObject(), parsedJson); + } + }); + } + }); + } +}); From ea5c18d2329d5c3c4cd9eda699cadb4a0dd412d2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 14 Aug 2023 10:15:46 -0700 Subject: [PATCH 561/694] grpc-js: Switch Timer type to Timeout --- packages/grpc-js/src/backoff-timeout.ts | 2 +- packages/grpc-js/src/internal-channel.ts | 4 ++-- packages/grpc-js/src/load-balancer-outlier-detection.ts | 2 +- packages/grpc-js/src/resolver-dns.ts | 2 +- packages/grpc-js/src/resolving-call.ts | 2 +- packages/grpc-js/src/retrying-call.ts | 2 +- packages/grpc-js/src/server-call.ts | 2 +- packages/grpc-js/src/server.ts | 6 +++--- packages/grpc-js/src/subchannel-pool.ts | 2 +- packages/grpc-js/src/transport.ts | 4 ++-- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/grpc-js/src/backoff-timeout.ts b/packages/grpc-js/src/backoff-timeout.ts index f523e259a..3ffd26064 100644 --- a/packages/grpc-js/src/backoff-timeout.ts +++ b/packages/grpc-js/src/backoff-timeout.ts @@ -63,7 +63,7 @@ export class BackoffTimeout { * to an object representing a timer that has ended, but it can still be * interacted with without error. */ - private timerId: NodeJS.Timer; + private timerId: NodeJS.Timeout; /** * Indicates whether the timer is currently running. */ diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 88dd34741..2817201e2 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -166,7 +166,7 @@ export class InternalChannel { * the invariant is that callRefTimer is reffed if and only if pickQueue * is non-empty. */ - private readonly callRefTimer: NodeJS.Timer; + private readonly callRefTimer: NodeJS.Timeout; private configSelector: ConfigSelector | null = null; /** * This is the error from the name resolver if it failed most recently. It @@ -182,7 +182,7 @@ export class InternalChannel { new Set(); private callCount = 0; - private idleTimer: NodeJS.Timer | null = null; + private idleTimer: NodeJS.Timeout | null = null; private readonly idleTimeoutMs: number; // Channelz info diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index be62b4c36..63a1d8f1d 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -507,7 +507,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; private addressMap: Map = new Map(); private latestConfig: OutlierDetectionLoadBalancingConfig | null = null; - private ejectionTimer: NodeJS.Timer; + private ejectionTimer: NodeJS.Timeout; private timerStartTime: Date | null = null; constructor(channelControlHelper: ChannelControlHelper) { diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index b55278525..c40cb8ec5 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -96,7 +96,7 @@ class DnsResolver implements Resolver { private defaultResolutionError: StatusObject; private backoff: BackoffTimeout; private continueResolving = false; - private nextResolutionTimer: NodeJS.Timer; + private nextResolutionTimer: NodeJS.Timeout; private isNextResolutionTimerRunning = false; private isServiceConfigEnabled = true; constructor( diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index 8aa717c06..723533dba 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -53,7 +53,7 @@ export class ResolvingCall implements Call { private deadline: Deadline; private host: string; private statusWatchers: ((status: StatusObject) => void)[] = []; - private deadlineTimer: NodeJS.Timer = setTimeout(() => {}, 0); + private deadlineTimer: NodeJS.Timeout = setTimeout(() => {}, 0); private filterStack: FilterStack | null = null; constructor( diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index c329161c3..e6e1cbb44 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -194,7 +194,7 @@ export class RetryingCall implements Call { * Number of attempts so far */ private attempts = 0; - private hedgingTimer: NodeJS.Timer | null = null; + private hedgingTimer: NodeJS.Timeout | null = null; private committedCallIndex: number | null = null; private initialRetryBackoffSec = 0; private nextRetryBackoffSec = 0; diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index b1898fd26..95f928350 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -408,7 +408,7 @@ export class Http2ServerCallStream< ResponseType > extends EventEmitter { cancelled = false; - deadlineTimer: NodeJS.Timer | null = null; + deadlineTimer: NodeJS.Timeout | null = null; private statusSent = false; private deadline: Deadline = Infinity; private wantTrailers = false; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index f5e79a339..c9308ca62 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -1079,8 +1079,8 @@ export class Server { ); this.sessionChildrenTracker.refChild(channelzRef); } - let connectionAgeTimer: NodeJS.Timer | null = null; - let connectionAgeGraceTimer: NodeJS.Timer | null = null; + let connectionAgeTimer: NodeJS.Timeout | null = null; + let connectionAgeGraceTimer: NodeJS.Timeout | null = null; let sessionClosedByServer = false; if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { // Apply a random jitter within a +/-10% range @@ -1115,7 +1115,7 @@ export class Server { } }, this.maxConnectionAgeMs + jitter).unref?.(); } - const keeapliveTimeTimer: NodeJS.Timer | null = setInterval(() => { + const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => { const timeoutTImer = setTimeout(() => { sessionClosedByServer = true; if (this.channelzEnabled) { diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts index 0cbc028ed..a5dec729d 100644 --- a/packages/grpc-js/src/subchannel-pool.ts +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -45,7 +45,7 @@ export class SubchannelPool { /** * A timer of a task performing a periodic subchannel cleanup. */ - private cleanupTimer: NodeJS.Timer | null = null; + private cleanupTimer: NodeJS.Timeout | null = null; /** * A pool of subchannels use for making connections. Subchannels with the diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 37854e68a..18d83cbfe 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -108,7 +108,7 @@ class Http2Transport implements Transport { /** * Timer reference for timeout that indicates when to send the next ping */ - private keepaliveTimerId: NodeJS.Timer | null = null; + private keepaliveTimerId: NodeJS.Timeout | null = null; /** * Indicates that the keepalive timer ran out while there were no active * calls, and a ping should be sent the next time a call starts. @@ -117,7 +117,7 @@ class Http2Transport implements Transport { /** * Timer reference tracking when the most recent ping will be considered lost */ - private keepaliveTimeoutId: NodeJS.Timer | null = null; + private keepaliveTimeoutId: NodeJS.Timeout | null = null; /** * Indicates whether keepalive pings should be sent without any active calls */ From 12217720527d702ca4d263b41b60d17438e08c16 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 14 Aug 2023 10:15:46 -0700 Subject: [PATCH 562/694] grpc-js: Switch Timer type to Timeout --- packages/grpc-js/src/backoff-timeout.ts | 2 +- packages/grpc-js/src/internal-channel.ts | 4 ++-- packages/grpc-js/src/load-balancer-outlier-detection.ts | 2 +- packages/grpc-js/src/resolver-dns.ts | 2 +- packages/grpc-js/src/resolving-call.ts | 2 +- packages/grpc-js/src/retrying-call.ts | 2 +- packages/grpc-js/src/server-call.ts | 2 +- packages/grpc-js/src/server.ts | 6 +++--- packages/grpc-js/src/subchannel-pool.ts | 2 +- packages/grpc-js/src/transport.ts | 4 ++-- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/grpc-js/src/backoff-timeout.ts b/packages/grpc-js/src/backoff-timeout.ts index f523e259a..3ffd26064 100644 --- a/packages/grpc-js/src/backoff-timeout.ts +++ b/packages/grpc-js/src/backoff-timeout.ts @@ -63,7 +63,7 @@ export class BackoffTimeout { * to an object representing a timer that has ended, but it can still be * interacted with without error. */ - private timerId: NodeJS.Timer; + private timerId: NodeJS.Timeout; /** * Indicates whether the timer is currently running. */ diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index f183729e8..0ed189c03 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -166,7 +166,7 @@ export class InternalChannel { * the invariant is that callRefTimer is reffed if and only if pickQueue * is non-empty. */ - private readonly callRefTimer: NodeJS.Timer; + private readonly callRefTimer: NodeJS.Timeout; private configSelector: ConfigSelector | null = null; /** * This is the error from the name resolver if it failed most recently. It @@ -182,7 +182,7 @@ export class InternalChannel { new Set(); private callCount = 0; - private idleTimer: NodeJS.Timer | null = null; + private idleTimer: NodeJS.Timeout | null = null; private readonly idleTimeoutMs: number; // Channelz info diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 4abbd0843..3e4b46feb 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -502,7 +502,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; private addressMap: Map = new Map(); private latestConfig: OutlierDetectionLoadBalancingConfig | null = null; - private ejectionTimer: NodeJS.Timer; + private ejectionTimer: NodeJS.Timeout; private timerStartTime: Date | null = null; constructor(channelControlHelper: ChannelControlHelper) { diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index b55278525..c40cb8ec5 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -96,7 +96,7 @@ class DnsResolver implements Resolver { private defaultResolutionError: StatusObject; private backoff: BackoffTimeout; private continueResolving = false; - private nextResolutionTimer: NodeJS.Timer; + private nextResolutionTimer: NodeJS.Timeout; private isNextResolutionTimerRunning = false; private isServiceConfigEnabled = true; constructor( diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index 8aa717c06..723533dba 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -53,7 +53,7 @@ export class ResolvingCall implements Call { private deadline: Deadline; private host: string; private statusWatchers: ((status: StatusObject) => void)[] = []; - private deadlineTimer: NodeJS.Timer = setTimeout(() => {}, 0); + private deadlineTimer: NodeJS.Timeout = setTimeout(() => {}, 0); private filterStack: FilterStack | null = null; constructor( diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index c329161c3..e6e1cbb44 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -194,7 +194,7 @@ export class RetryingCall implements Call { * Number of attempts so far */ private attempts = 0; - private hedgingTimer: NodeJS.Timer | null = null; + private hedgingTimer: NodeJS.Timeout | null = null; private committedCallIndex: number | null = null; private initialRetryBackoffSec = 0; private nextRetryBackoffSec = 0; diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index b1898fd26..95f928350 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -408,7 +408,7 @@ export class Http2ServerCallStream< ResponseType > extends EventEmitter { cancelled = false; - deadlineTimer: NodeJS.Timer | null = null; + deadlineTimer: NodeJS.Timeout | null = null; private statusSent = false; private deadline: Deadline = Infinity; private wantTrailers = false; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index f5e79a339..c9308ca62 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -1079,8 +1079,8 @@ export class Server { ); this.sessionChildrenTracker.refChild(channelzRef); } - let connectionAgeTimer: NodeJS.Timer | null = null; - let connectionAgeGraceTimer: NodeJS.Timer | null = null; + let connectionAgeTimer: NodeJS.Timeout | null = null; + let connectionAgeGraceTimer: NodeJS.Timeout | null = null; let sessionClosedByServer = false; if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { // Apply a random jitter within a +/-10% range @@ -1115,7 +1115,7 @@ export class Server { } }, this.maxConnectionAgeMs + jitter).unref?.(); } - const keeapliveTimeTimer: NodeJS.Timer | null = setInterval(() => { + const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => { const timeoutTImer = setTimeout(() => { sessionClosedByServer = true; if (this.channelzEnabled) { diff --git a/packages/grpc-js/src/subchannel-pool.ts b/packages/grpc-js/src/subchannel-pool.ts index 0cbc028ed..a5dec729d 100644 --- a/packages/grpc-js/src/subchannel-pool.ts +++ b/packages/grpc-js/src/subchannel-pool.ts @@ -45,7 +45,7 @@ export class SubchannelPool { /** * A timer of a task performing a periodic subchannel cleanup. */ - private cleanupTimer: NodeJS.Timer | null = null; + private cleanupTimer: NodeJS.Timeout | null = null; /** * A pool of subchannels use for making connections. Subchannels with the diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 37854e68a..18d83cbfe 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -108,7 +108,7 @@ class Http2Transport implements Transport { /** * Timer reference for timeout that indicates when to send the next ping */ - private keepaliveTimerId: NodeJS.Timer | null = null; + private keepaliveTimerId: NodeJS.Timeout | null = null; /** * Indicates that the keepalive timer ran out while there were no active * calls, and a ping should be sent the next time a call starts. @@ -117,7 +117,7 @@ class Http2Transport implements Transport { /** * Timer reference tracking when the most recent ping will be considered lost */ - private keepaliveTimeoutId: NodeJS.Timer | null = null; + private keepaliveTimeoutId: NodeJS.Timeout | null = null; /** * Indicates whether keepalive pings should be sent without any active calls */ From eb6f1338ab852e716f45099a199ba9ddd28c092f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 14 Aug 2023 09:29:25 -0700 Subject: [PATCH 563/694] grpc-js-xds: Implement custom LB policies --- packages/grpc-js-xds/src/environment.ts | 1 + packages/grpc-js-xds/src/index.ts | 6 +- .../grpc-js-xds/src/lb-policy-registry.ts | 62 ++++++++++ .../src/lb-policy-registry/round-robin.ts | 36 ++++++ .../src/lb-policy-registry/typed-struct.ts | 110 +++++++++++++++++ packages/grpc-js-xds/src/load-balancer-cds.ts | 4 +- .../grpc-js-xds/src/load-balancer-priority.ts | 3 + .../src/load-balancer-xds-cluster-impl.ts | 54 +++++++-- .../src/load-balancer-xds-cluster-resolver.ts | 60 +++------- .../src/load-balancer-xds-wrr-locality.ts | 112 ++++++++++++++++++ .../cluster-resource-type.ts | 42 +++++-- 11 files changed, 421 insertions(+), 69 deletions(-) create mode 100644 packages/grpc-js-xds/src/lb-policy-registry.ts create mode 100644 packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts create mode 100644 packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts create mode 100644 packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 32c9f28ba..530bb256c 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -19,3 +19,4 @@ export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_F export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; 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'; diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 51926ac47..319719bde 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -19,10 +19,10 @@ import * as resolver_xds from './resolver-xds'; import * as load_balancer_cds from './load-balancer-cds'; import * as xds_cluster_resolver from './load-balancer-xds-cluster-resolver'; import * as xds_cluster_impl from './load-balancer-xds-cluster-impl'; -import * as load_balancer_lrs from './load-balancer-lrs'; import * as load_balancer_priority from './load-balancer-priority'; import * as load_balancer_weighted_target from './load-balancer-weighted-target'; import * as load_balancer_xds_cluster_manager from './load-balancer-xds-cluster-manager'; +import * as xds_wrr_locality from './load-balancer-xds-wrr-locality'; import * as router_filter from './http-filter/router-filter'; import * as fault_injection_filter from './http-filter/fault-injection-filter'; import * as csds from './csds'; @@ -35,11 +35,11 @@ export function register() { load_balancer_cds.setup(); xds_cluster_resolver.setup(); xds_cluster_impl.setup(); - load_balancer_lrs.setup(); load_balancer_priority.setup(); load_balancer_weighted_target.setup(); load_balancer_xds_cluster_manager.setup(); + xds_wrr_locality.setup(); router_filter.setup(); fault_injection_filter.setup(); csds.setup(); -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/lb-policy-registry.ts b/packages/grpc-js-xds/src/lb-policy-registry.ts new file mode 100644 index 000000000..18d86655e --- /dev/null +++ b/packages/grpc-js-xds/src/lb-policy-registry.ts @@ -0,0 +1,62 @@ +/* + * 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. + * + */ + +import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; +import { LoadBalancingPolicy__Output } from "./generated/envoy/config/cluster/v3/LoadBalancingPolicy"; +import { TypedExtensionConfig__Output } from "./generated/envoy/config/core/v3/TypedExtensionConfig"; + +const TRACER_NAME = 'lb_policy_registry'; +function trace(text: string) { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const MAX_RECURSION_DEPTH = 16; + +interface ProtoLbPolicyConverter { + (protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig +} + +interface RegisteredLbPolicy { + convertToLoadBalancingPolicy: ProtoLbPolicyConverter; +} + +const registry: {[typeUrl: string]: RegisteredLbPolicy} = {} + +export function registerLbPolicy(typeUrl: string, converter: ProtoLbPolicyConverter) { + registry[typeUrl] = {convertToLoadBalancingPolicy: converter}; +} + +export function convertToLoadBalancingConfig(protoPolicy: LoadBalancingPolicy__Output, recursionDepth = 0): LoadBalancingConfig { + if (recursionDepth > MAX_RECURSION_DEPTH) { + throw new Error(`convertToLoadBalancingConfig: Max recursion depth ${MAX_RECURSION_DEPTH} reached`); + } + for (const policyCandidate of protoPolicy.policies) { + const extensionConfig = policyCandidate.typed_extension_config; + if (!extensionConfig?.typed_config) { + continue; + } + const typeUrl = extensionConfig.typed_config.type_url; + if (typeUrl in registry) { + try { + return registry[typeUrl].convertToLoadBalancingPolicy(extensionConfig, childPolicy => convertToLoadBalancingConfig(childPolicy, recursionDepth + 1)); + } catch (e) { + throw new Error(`Error parsing ${typeUrl} LoadBalancingPolicy: ${(e as Error).message}`); + } + } + } + throw new Error('No registered LB policy found in list'); +} diff --git a/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts b/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts new file mode 100644 index 000000000..9585bfde2 --- /dev/null +++ b/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts @@ -0,0 +1,36 @@ +/* + * 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. + * + */ + +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 { registerLbPolicy } from "../lb-policy-registry"; + +const ROUND_ROBIN_TYPE_URL = 'envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin'; + +function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig { + if (protoPolicy.typed_config?.type_url !== ROUND_ROBIN_TYPE_URL) { + throw new Error(`Round robin LB policy parsing error: unexpected type URL ${protoPolicy.typed_config?.type_url}`); + } + return { + round_robin: {} + }; +} + +export function setup() { + registerLbPolicy(ROUND_ROBIN_TYPE_URL, convertToLoadBalancingPolicy); +} diff --git a/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts new file mode 100644 index 000000000..70a6c02b3 --- /dev/null +++ b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts @@ -0,0 +1,110 @@ +/* + * 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. + * + */ + +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 { registerLbPolicy } from "../lb-policy-registry"; +import { loadProtosWithOptionsSync } from "@grpc/proto-loader/build/src/util"; +import { Any__Output } from "../generated/google/protobuf/Any"; +import { Struct__Output } from "../generated/google/protobuf/Struct"; +import { Value__Output } from "../generated/google/protobuf/Value"; +import { TypedStruct__Output } from "../generated/xds/type/v3/TypedStruct"; + +const XDS_TYPED_STRUCT_TYPE_URL = 'xds.type.v3.TypedStruct'; +const UDPA_TYPED_STRUCT_TYPE_URL = 'udpa.type.v1.TypedStruct'; + +const resourceRoot = loadProtosWithOptionsSync([ + 'xds/type/v3/typed_struct.proto', + 'udpa/type/v1/typed_struct.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/xds/', + ], + } +); + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +/* xds.type.v3.TypedStruct and udpa.type.v1.TypedStruct have identical interfaces */ +function decodeTypedStruct(message: Any__Output): TypedStruct__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 TypedStruct__Output; + } else { + throw new Error(`TypedStruct parsing error: unexpected type URL ${message.type_url}`); + } +} + +type FlatValue = boolean | null | number | string | FlatValue[] | FlatStruct; +interface FlatStruct { + [key: string]: FlatValue; +} + +function flattenValue(value: Value__Output): FlatValue { + switch (value.kind) { + case 'boolValue': + return value.boolValue!; + case 'listValue': + return value.listValue!.values.map(flattenValue); + case 'nullValue': + return null; + case 'numberValue': + return value.numberValue!; + case 'stringValue': + return value.stringValue!; + case 'structValue': + return flattenStruct(value.structValue!); + default: + throw new Error(`Struct parsing error: unexpected value kind ${value.kind}`); + } +} + +function flattenStruct(struct: Struct__Output): FlatStruct { + const result: FlatStruct = {}; + for (const [key, value] of Object.entries(struct.fields)) { + result[key] = flattenValue(value); + } + return result; +} + +function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig { + if (protoPolicy.typed_config?.type_url !== XDS_TYPED_STRUCT_TYPE_URL && protoPolicy.typed_config?.type_url !== UDPA_TYPED_STRUCT_TYPE_URL) { + throw new Error(`Typed struct LB policy parsing error: unexpected type URL ${protoPolicy.typed_config?.type_url}`); + } + const typedStruct = decodeTypedStruct(protoPolicy.typed_config); + if (!typedStruct.value) { + throw new Error(`Typed struct LB parsing error: unexpected value ${typedStruct.value}`); + } + const policyName = typedStruct.type_url.substring(typedStruct.type_url.lastIndexOf('/') + 1); + return { + [policyName]: flattenStruct(typedStruct.value) + }; +} + +export function setup() { + registerLbPolicy(XDS_TYPED_STRUCT_TYPE_URL, convertToLoadBalancingPolicy); + registerLbPolicy(UDPA_TYPED_STRUCT_TYPE_URL, convertToLoadBalancingPolicy); +} diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 44de10ac4..647ab2b97 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -193,11 +193,11 @@ export class CdsLoadBalancer implements LoadBalancer { this.reportError((e as Error).message); return; } + const rootClusterUpdate = this.clusterTree[this.latestConfig!.getCluster()].latestUpdate!; const clusterResolverConfig: LoadBalancingConfig = { xds_cluster_resolver: { discovery_mechanisms: discoveryMechanismList, - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: rootClusterUpdate.lbPolicyConfig } }; let parsedClusterResolverConfig: TypedLoadBalancingConfig; diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index 4372b0eac..4a3e41a11 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -27,6 +27,7 @@ import QueuePicker = experimental.QueuePicker; import UnavailablePicker = experimental.UnavailablePicker; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import selectLbConfigFromList = experimental.selectLbConfigFromList; +import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; const TRACER_NAME = 'priority'; @@ -41,6 +42,8 @@ const DEFAULT_RETENTION_INTERVAL_MS = 15 * 60 * 1000; export type LocalitySubchannelAddress = SubchannelAddress & { localityPath: string[]; + locality: Locality__Output; + weight: number; }; export function isLocalitySubchannelAddress( diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index 9a9304518..eb3df8c38 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -17,7 +17,8 @@ import { experimental, logVerbosity, status as Status, Metadata, connectivityState } from "@grpc/grpc-js"; import { validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; -import { getSingletonXdsClient, XdsClient, XdsClusterDropStats } from "./xds-client"; +import { getSingletonXdsClient, XdsClient, XdsClusterDropStats, XdsClusterLocalityStats } from "./xds-client"; +import { LocalitySubchannelAddress } from "./load-balancer-priority"; import LoadBalancer = experimental.LoadBalancer; import registerLoadBalancerType = experimental.registerLoadBalancerType; @@ -31,6 +32,8 @@ import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import createChildChannelControlHelper = experimental.createChildChannelControlHelper; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import selectLbConfigFromList = experimental.selectLbConfigFromList; +import SubchannelInterface = experimental.SubchannelInterface; +import BaseSubchannelWrapper = experimental.BaseSubchannelWrapper; const TRACER_NAME = 'xds_cluster_impl'; @@ -80,7 +83,7 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { }; } - constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: TypedLoadBalancingConfig, private edsServiceName?: string, private lrsLoadReportingServer?: XdsServerConfig, maxConcurrentRequests?: number) { + constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: TypedLoadBalancingConfig, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, maxConcurrentRequests?: number) { this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; } @@ -112,8 +115,8 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { if (!('cluster' in obj && typeof obj.cluster === 'string')) { throw new Error('xds_cluster_impl config must have a string field cluster'); } - if ('eds_service_name' in obj && !(obj.eds_service_name === undefined || typeof obj.eds_service_name === 'string')) { - throw new Error('xds_cluster_impl config eds_service_name field must be a string if provided'); + if (!('eds_service_name' in obj && typeof obj.eds_service_name === 'string')) { + throw new Error('xds_cluster_impl config must have a string field eds_service_name'); } if ('max_concurrent_requests' in obj && !(obj.max_concurrent_requests === undefined || typeof obj.max_concurrent_requests === 'number')) { throw new Error('xds_cluster_impl config max_concurrent_requests must be a number if provided'); @@ -128,7 +131,7 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { if (!childConfig) { throw new Error('xds_cluster_impl config child_policy parsing failed'); } - return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), childConfig, obj.eds_service_name, obj.lrs_load_reporting_server ? validateXdsServerConfig(obj.lrs_load_reporting_server) : undefined, obj.max_concurrent_requests); + return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), childConfig, obj.eds_service_name, validateXdsServerConfig(obj.lrs_load_reporting_server), obj.max_concurrent_requests); } } @@ -156,7 +159,25 @@ class CallCounterMap { const callCounterMap = new CallCounterMap(); -class DropPicker implements Picker { +class LocalitySubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { + constructor(child: SubchannelInterface, private statsObject: XdsClusterLocalityStats) { + super(child); + } + + getStatsObject() { + return this.statsObject; + } + + getWrappedSubchannel(): SubchannelInterface { + return this.child; + } +} + +/** + * This picker is responsible for implementing the drop configuration, and for + * recording drop stats and per-locality stats. + */ +class XdsClusterImplPicker implements Picker { constructor(private originalPicker: Picker, private callCounterMapKey: string, private maxConcurrentRequests: number, private dropCategories: DropCategory[], private clusterDropStats: XdsClusterDropStats | null) {} private checkForMaxConcurrentRequestsDrop(): boolean { @@ -186,16 +207,19 @@ class DropPicker implements Picker { } if (details === null) { const originalPick = this.originalPicker.pick(pickArgs); + const pickSubchannel = originalPick.subchannel ? (originalPick.subchannel as LocalitySubchannelWrapper) : null; return { pickResultType: originalPick.pickResultType, status: originalPick.status, - subchannel: originalPick.subchannel, + subchannel: pickSubchannel?.getWrappedSubchannel() ?? null, onCallStarted: () => { originalPick.onCallStarted?.(); + pickSubchannel?.getStatsObject().addCallStarted(); callCounterMap.startCall(this.callCounterMapKey); }, onCallEnded: status => { originalPick.onCallEnded?.(status); + pickSubchannel?.getStatsObject().addCallFinished(status !== Status.OK) callCounterMap.endCall(this.callCounterMapKey); } }; @@ -227,11 +251,25 @@ class XdsClusterImplBalancer implements LoadBalancer { constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { + createSubchannel: (subchannelAddress, subchannelArgs) => { + if (!this.xdsClient || !this.latestConfig) { + throw new Error('xds_cluster_impl: invalid state: createSubchannel called with xdsClient or latestConfig not populated'); + } + const locality = (subchannelAddress as LocalitySubchannelAddress).locality ?? ''; + const wrapperChild = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); + const statsObj = this.xdsClient.addClusterLocalityStats( + this.latestConfig.getLrsLoadReportingServer(), + this.latestConfig.getCluster(), + this.latestConfig.getEdsServiceName(), + locality + ); + return new LocalitySubchannelWrapper(wrapperChild, statsObj); + }, updateState: (connectivityState, originalPicker) => { if (this.latestConfig === null) { channelControlHelper.updateState(connectivityState, originalPicker); } else { - const picker = new DropPicker(originalPicker, getCallCounterMapKey(this.latestConfig.getCluster(), this.latestConfig.getEdsServiceName()), this.latestConfig.getMaxConcurrentRequests(), this.latestConfig.getDropCategories(), this.clusterDropStats); + const picker = new XdsClusterImplPicker(originalPicker, getCallCounterMapKey(this.latestConfig.getCluster(), this.latestConfig.getEdsServiceName()), this.latestConfig.getMaxConcurrentRequests(), this.latestConfig.getDropCategories(), this.clusterDropStats); channelControlHelper.updateState(connectivityState, picker); } } diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index dd9b80107..6c11c52bf 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -38,7 +38,6 @@ import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; import UnavailablePicker = experimental.UnavailablePicker; import { serverConfigEqual, validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; import { EndpointResourceType } from "./xds-resource-type/endpoint-resource-type"; -import { WeightedTargetRaw } from "./load-balancer-weighted-target"; const TRACER_NAME = 'xds_cluster_resolver'; @@ -85,40 +84,31 @@ class XdsClusterResolverLoadBalancingConfig implements TypedLoadBalancingConfig return { [TYPE_NAME]: { discovery_mechanisms: this.discoveryMechanisms, - locality_picking_policy: this.localityPickingPolicy, - endpoint_picking_policy: this.endpointPickingPolicy + xds_lb_policy: this.xdsLbPolicy } } } - constructor(private discoveryMechanisms: DiscoveryMechanism[], private localityPickingPolicy: LoadBalancingConfig[], private endpointPickingPolicy: LoadBalancingConfig[]) {} + constructor(private discoveryMechanisms: DiscoveryMechanism[], private xdsLbPolicy: LoadBalancingConfig[]) {} getDiscoveryMechanisms() { return this.discoveryMechanisms; } - getLocalityPickingPolicy() { - return this.localityPickingPolicy; - } - - getEndpointPickingPolicy() { - return this.endpointPickingPolicy; + getXdsLbPolicy() { + return this.xdsLbPolicy; } static createFromJson(obj: any): XdsClusterResolverLoadBalancingConfig { if (!('discovery_mechanisms' in obj && Array.isArray(obj.discovery_mechanisms))) { throw new Error('xds_cluster_resolver config must have a discovery_mechanisms array'); } - if (!('locality_picking_policy' in obj && Array.isArray(obj.locality_picking_policy))) { - throw new Error('xds_cluster_resolver config must have a locality_picking_policy array'); - } - if (!('endpoint_picking_policy' in obj && Array.isArray(obj.endpoint_picking_policy))) { - throw new Error('xds_cluster_resolver config must have a endpoint_picking_policy array'); + if (!('xds_lb_policy' in obj && Array.isArray(obj.xds_lb_policy))) { + throw new Error('xds_cluster_resolver config must have a xds_lb_policy array'); } return new XdsClusterResolverLoadBalancingConfig( obj.discovery_mechanisms.map(validateDiscoveryMechanism), - obj.locality_picking_policy, - obj.endpoint_picking_policy + obj.xds_lb_policy ); } } @@ -223,7 +213,7 @@ function getDnsPriorities(addresses: SubchannelAddress[]): PriorityEntry[] { }]; } -function localityToName(locality: Locality__Output) { +export function localityToName(locality: Locality__Output) { return `{region=${locality.region},zone=${locality.zone},sub_zone=${locality.sub_zone}}`; } @@ -260,12 +250,11 @@ export class XdsClusterResolver implements LoadBalancer { const fullPriorityList: string[] = []; const priorityChildren: {[name: string]: PriorityChildRaw} = {}; const addressList: LocalitySubchannelAddress[] = []; + const edsChildPolicy = this.latestConfig.getXdsLbPolicy(); for (const entry of this.discoveryMechanismList) { const newPriorityNames: string[] = []; const newLocalityPriorities = new Map(); - const configEndpointPickingPolicy = this.latestConfig.getEndpointPickingPolicy(); - const defaultEndpointPickingPolicy: LoadBalancingConfig = entry.discoveryMechanism.type === 'EDS' ? { round_robin: {} } : { pick_first: {} }; - const endpointPickingPolicy: LoadBalancingConfig[] = configEndpointPickingPolicy.length > 0 ? configEndpointPickingPolicy : [defaultEndpointPickingPolicy]; + const xdsClusterImplChildPolicy: LoadBalancingConfig[] = entry.discoveryMechanism.type === 'EDS' ? edsChildPolicy : [{ pick_first: {} }]; for (const [priority, priorityEntry] of entry.latestUpdate!.entries()) { /** @@ -301,32 +290,15 @@ export class XdsClusterResolver implements LoadBalancer { } newPriorityNames[priority] = newPriorityName; - const childTargets: {[locality: string]: WeightedTargetRaw} = {}; for (const localityObj of priorityEntry.localities) { - let childPolicy: LoadBalancingConfig[]; - if (entry.discoveryMechanism.lrs_load_reporting_server !== undefined) { - childPolicy = [{ - lrs: { - cluster_name: entry.discoveryMechanism.cluster, - eds_service_name: entry.discoveryMechanism.eds_service_name ?? '', - locality: {...localityObj.locality}, - lrs_load_reporting_server: {...entry.discoveryMechanism.lrs_load_reporting_server}, - child_policy: endpointPickingPolicy - } - }]; - } else { - childPolicy = endpointPickingPolicy; - } - childTargets[localityToName(localityObj.locality)] = { - weight: localityObj.weight, - child_policy: childPolicy, - }; for (const address of localityObj.addresses) { addressList.push({ localityPath: [ newPriorityName, localityToName(localityObj.locality), ], + locality: localityObj.locality, + weight: localityObj.weight, ...address, }); } @@ -337,13 +309,9 @@ export class XdsClusterResolver implements LoadBalancer { cluster: entry.discoveryMechanism.cluster, drop_categories: priorityEntry.dropCategories, max_concurrent_requests: entry.discoveryMechanism.max_concurrent_requests, - eds_service_name: entry.discoveryMechanism.eds_service_name, + eds_service_name: entry.discoveryMechanism.eds_service_name ?? '', lrs_load_reporting_server: entry.discoveryMechanism.lrs_load_reporting_server, - child_policy: [{ - weighted_target: { - targets: childTargets - } - }] + child_policy: xdsClusterImplChildPolicy } } let priorityChildConfig: LoadBalancingConfig; diff --git a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts new file mode 100644 index 000000000..757f9b93b --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts @@ -0,0 +1,112 @@ +/* + * 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. + * + */ + +import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; +import { WeightedTargetRaw } from "./load-balancer-weighted-target"; +import { isLocalitySubchannelAddress } from "./load-balancer-priority"; +import { localityToName } from "./load-balancer-xds-cluster-resolver"; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import SubchannelAddress = experimental.SubchannelAddress; +import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; +import registerLoadBalancerType = experimental.registerLoadBalancerType; + +const TRACER_NAME = 'xds_wrr_locality'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'xds_wrr_locality'; + +class XdsWrrLocalityLoadBalancingConfig implements TypedLoadBalancingConfig { + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + return { + [TYPE_NAME]: { + child_policy: this.childPolicy + } + } + } + + constructor(private childPolicy: LoadBalancingConfig[]) {} + + getChildPolicy() { + return this.childPolicy; + } + + static createFromJson(obj: any): XdsWrrLocalityLoadBalancingConfig { + if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { + throw new Error('xds_wrr_locality config must have a child_policy array'); + } + return new XdsWrrLocalityLoadBalancingConfig( + obj.child_policy + ); + } +} + +class XdsWrrLocalityLoadBalancer implements LoadBalancer { + private childBalancer: ChildLoadBalancerHandler; + constructor(private readonly channelControlHelper: ChannelControlHelper) { + this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper); + } + updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof XdsWrrLocalityLoadBalancingConfig)) { + trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2)); + return; + } + const targets: {[localityName: string]: WeightedTargetRaw} = {}; + for (const address of addressList) { + if (!isLocalitySubchannelAddress(address)) { + return; + } + const localityName = localityToName(address.locality); + if (!(localityName in targets)) { + targets[localityName] = { + child_policy: lbConfig.getChildPolicy(), + weight: address.weight + }; + } + } + const childConfig = { + weighted_target: { + targets: targets + } + }; + this.childBalancer.updateAddressList(addressList, parseLoadBalancingConfig(childConfig), attributes); + } + exitIdle(): void { + this.childBalancer.exitIdle(); + } + resetBackoff(): void { + this.childBalancer.resetBackoff(); + } + destroy(): void { + this.childBalancer.destroy(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +export function setup() { + registerLoadBalancerType(TYPE_NAME, XdsWrrLocalityLoadBalancer, XdsWrrLocalityLoadBalancingConfig); +} diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index e617bef94..726a5d2d2 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -17,19 +17,20 @@ import { CDS_TYPE_URL, CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from "../resources"; import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; -import { experimental } from "@grpc/grpc-js"; +import { LoadBalancingConfig, experimental } from "@grpc/grpc-js"; import { XdsServerConfig } from "../xds-bootstrap"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { OutlierDetection__Output } from "../generated/envoy/config/cluster/v3/OutlierDetection"; -import { EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; +import { EXPERIMENTAL_CUSTOM_LB_CONFIG, EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; import { Any__Output } from "../generated/google/protobuf/Any"; - -import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; -import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; import { Watcher, XdsClient } from "../xds-client"; import { protoDurationToDuration } from "../duration"; +import { convertToLoadBalancingConfig } from "../lb-policy-registry"; +import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; +import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; +import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; export interface CdsUpdate { type: 'AGGREGATE' | 'EDS' | 'LOGICAL_DNS'; @@ -39,6 +40,7 @@ export interface CdsUpdate { maxConcurrentRequests?: number; edsServiceName?: string; dnsHostname?: string; + lbPolicyConfig: LoadBalancingConfig[]; outlierDetectionUpdate?: experimental.OutlierDetectionRawConfig; } @@ -85,7 +87,6 @@ function convertOutlierDetectionUpdate(outlierDetection: OutlierDetection__Outpu }; } - export class ClusterResourceType extends XdsResourceType { private static singleton: ClusterResourceType = new ClusterResourceType(); @@ -122,7 +123,25 @@ export class ClusterResourceType extends XdsResourceType { } private validateResource(context: XdsDecodeContext, message: Cluster__Output): CdsUpdate | null { - if (message.lb_policy !== 'ROUND_ROBIN') { + let lbPolicyConfig: LoadBalancingConfig; + if (EXPERIMENTAL_CUSTOM_LB_CONFIG && message.load_balancing_policy) { + try { + lbPolicyConfig = convertToLoadBalancingConfig(message.load_balancing_policy); + } catch (e) { + return null; + } + try { + parseLoadBalancingConfig(lbPolicyConfig); + } catch (e) { + return null; + } + } else if (message.lb_policy === 'ROUND_ROBIN') { + lbPolicyConfig = { + xds_wrr_locality: { + child_policy: [{round_robin: {}}] + } + }; + } else { return null; } if (message.lrs_server) { @@ -167,7 +186,8 @@ export class ClusterResourceType extends XdsResourceType { type: 'AGGREGATE', name: message.name, aggregateChildren: clusterConfig.clusters, - outlierDetectionUpdate: convertOutlierDetectionUpdate(null) + outlierDetectionUpdate: convertOutlierDetectionUpdate(null), + lbPolicyConfig: [lbPolicyConfig] }; } else { let maxConcurrentRequests: number | undefined = undefined; @@ -190,7 +210,8 @@ export class ClusterResourceType extends XdsResourceType { maxConcurrentRequests: maxConcurrentRequests, edsServiceName: message.eds_cluster_config.service_name === '' ? undefined : message.eds_cluster_config.service_name, lrsLoadReportingServer: message.lrs_server ? context.server : undefined, - outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) + outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection), + lbPolicyConfig: [lbPolicyConfig] } } else if (message.type === 'LOGICAL_DNS') { if (!message.load_assignment) { @@ -219,7 +240,8 @@ export class ClusterResourceType extends XdsResourceType { maxConcurrentRequests: maxConcurrentRequests, dnsHostname: `${socketAddress.address}:${socketAddress.port_value}`, lrsLoadReportingServer: message.lrs_server ? context.server : undefined, - outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection) + outlierDetectionUpdate: convertOutlierDetectionUpdate(message.outlier_detection), + lbPolicyConfig: [lbPolicyConfig] }; } } From 13a6e6d273d1fe57bb4e5142431fee46d294779a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 16 Aug 2023 10:24:47 -0700 Subject: [PATCH 564/694] grpc-js-xds: Update envoy-api dependency and code generation --- packages/grpc-js-xds/deps/envoy-api | 2 +- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/generated/ads.ts | 7 +- packages/grpc-js-xds/src/generated/cluster.ts | 15 +- packages/grpc-js-xds/src/generated/csds.ts | 191 +------ .../grpc-js-xds/src/generated/endpoint.ts | 6 + .../envoy/admin/v3/ClientResourceStatus.ts | 2 +- .../envoy/admin/v3/ClustersConfigDump.ts | 6 +- .../envoy/admin/v3/EcdsConfigDump.ts | 100 ++++ .../envoy/admin/v3/EndpointsConfigDump.ts | 6 +- .../envoy/admin/v3/ListenersConfigDump.ts | 6 +- .../envoy/admin/v3/RoutesConfigDump.ts | 6 +- .../envoy/admin/v3/ScopedRoutesConfigDump.ts | 6 +- .../envoy/admin/v3/UpdateFailureState.ts | 2 +- .../envoy/config/accesslog/v3/AccessLog.ts | 8 +- .../config/accesslog/v3/AccessLogFilter.ts | 19 +- .../config/accesslog/v3/DurationFilter.ts | 10 +- .../config/accesslog/v3/LogTypeFilter.ts | 33 ++ .../config/accesslog/v3/RuntimeFilter.ts | 12 +- .../config/cluster/v3/CircuitBreakers.ts | 32 ++ .../envoy/config/cluster/v3/Cluster.ts | 401 +++++++++------ .../config/cluster/v3/ClusterCollection.ts | 4 +- .../envoy/config/cluster/v3/Filter.ts | 12 +- .../config/cluster/v3/LoadBalancingPolicy.ts | 6 + .../config/cluster/v3/OutlierDetection.ts | 30 +- .../cluster/v3/UpstreamConnectionOptions.ts | 12 + .../generated/envoy/config/core/v3/Address.ts | 6 +- .../core/v3/AlternateProtocolsCacheOptions.ts | 80 +++ .../envoy/config/core/v3/ApiConfigSource.ts | 27 +- .../envoy/config/core/v3/BindConfig.ts | 51 +- .../envoy/config/core/v3/ConfigSource.ts | 49 +- .../envoy/config/core/v3/DataSource.ts | 16 +- .../config/core/v3/DnsResolutionConfig.ts | 6 - .../config/core/v3/DnsResolverOptions.ts | 6 - .../config/core/v3/EnvoyInternalAddress.ts | 28 +- .../envoy/config/core/v3/Extension.ts | 12 +- .../config/core/v3/ExtensionConfigSource.ts | 6 +- .../config/core/v3/ExtraSourceAddress.ts | 38 ++ .../envoy/config/core/v3/GrpcService.ts | 23 +- .../envoy/config/core/v3/HeaderValue.ts | 4 +- .../envoy/config/core/v3/HeaderValueOption.ts | 36 +- .../envoy/config/core/v3/HealthCheck.ts | 129 ++++- .../envoy/config/core/v3/HealthStatus.ts | 6 +- .../envoy/config/core/v3/HealthStatusSet.ts | 17 + .../config/core/v3/Http1ProtocolOptions.ts | 94 +++- .../config/core/v3/Http2ProtocolOptions.ts | 72 ++- .../config/core/v3/HttpProtocolOptions.ts | 20 +- .../generated/envoy/config/core/v3/HttpUri.ts | 4 +- .../envoy/config/core/v3/KeepaliveSettings.ts | 12 +- .../envoy/config/core/v3/Metadata.ts | 24 +- .../generated/envoy/config/core/v3/Node.ts | 8 +- .../envoy/config/core/v3/PathConfigSource.ts | 85 ++++ .../config/core/v3/ProxyProtocolConfig.ts | 11 + .../core/v3/ProxyProtocolPassThroughTLVs.ts | 43 ++ .../config/core/v3/QuicKeepAliveSettings.ts | 53 ++ .../config/core/v3/QuicProtocolOptions.ts | 35 +- .../core/v3/RuntimeFractionalPercent.ts | 4 +- .../envoy/config/core/v3/SocketAddress.ts | 12 +- .../envoy/config/core/v3/SocketOption.ts | 40 ++ .../config/core/v3/SocketOptionsOverride.ts | 11 + .../core/v3/SubstitutionFormatString.ts | 12 +- .../config/core/v3/TypedExtensionConfig.ts | 10 +- .../core/v3/UpstreamHttpProtocolOptions.ts | 14 +- .../envoy/config/endpoint/v3/ClusterStats.ts | 12 +- .../envoy/config/endpoint/v3/Endpoint.ts | 26 + .../envoy/config/endpoint/v3/LbEndpoint.ts | 20 +- .../config/endpoint/v3/LocalityLbEndpoints.ts | 18 +- .../config/listener/v3/AdditionalAddress.ts | 38 ++ .../config/listener/v3/ApiListenerManager.ts | 18 + .../envoy/config/listener/v3/Filter.ts | 6 +- .../envoy/config/listener/v3/FilterChain.ts | 34 +- .../config/listener/v3/FilterChainMatch.ts | 2 + .../envoy/config/listener/v3/Listener.ts | 191 +++++-- .../config/listener/v3/ListenerCollection.ts | 4 +- .../config/listener/v3/ListenerFilter.ts | 29 +- .../config/listener/v3/ListenerManager.ts | 18 + .../config/listener/v3/QuicProtocolOptions.ts | 52 +- .../config/listener/v3/UdpListenerConfig.ts | 31 +- .../listener/v3/ValidationListenerManager.ts | 18 + .../config/route/v3/ClusterSpecifierPlugin.ts | 18 +- .../envoy/config/route/v3/CorsPolicy.ts | 54 +- .../config/route/v3/DirectResponseAction.ts | 4 +- .../envoy/config/route/v3/FilterConfig.ts | 36 +- .../envoy/config/route/v3/HeaderMatcher.ts | 106 +++- .../config/route/v3/QueryParameterMatcher.ts | 4 +- .../envoy/config/route/v3/RateLimit.ts | 197 +++++++- .../envoy/config/route/v3/RedirectAction.ts | 16 +- .../envoy/config/route/v3/RetryPolicy.ts | 16 +- .../generated/envoy/config/route/v3/Route.ts | 68 ++- .../envoy/config/route/v3/RouteAction.ts | 298 ++++++++--- .../config/route/v3/RouteConfiguration.ts | 100 +++- .../envoy/config/route/v3/RouteList.ts | 25 + .../envoy/config/route/v3/RouteMatch.ts | 65 ++- .../route/v3/ScopedRouteConfiguration.ts | 27 +- .../envoy/config/route/v3/Tracing.ts | 4 +- .../envoy/config/route/v3/VirtualCluster.ts | 4 +- .../envoy/config/route/v3/VirtualHost.ts | 90 +++- .../envoy/config/route/v3/WeightedCluster.ts | 106 ++-- .../data/accesslog/v3/AccessLogCommon.ts | 421 ++++++++++++++++ .../envoy/data/accesslog/v3/AccessLogType.ts | 15 + .../data/accesslog/v3/ConnectionProperties.ts | 31 ++ .../data/accesslog/v3/HTTPAccessLogEntry.ts | 50 ++ .../accesslog/v3/HTTPRequestProperties.ts | 163 ++++++ .../accesslog/v3/HTTPResponseProperties.ts | 92 ++++ .../envoy/data/accesslog/v3/ResponseFlags.ts | 255 ++++++++++ .../data/accesslog/v3/TCPAccessLogEntry.ts | 26 + .../envoy/data/accesslog/v3/TLSProperties.ts | 131 +++++ .../filters/http/fault/v3/HTTPFault.ts | 41 +- .../v3/HttpConnectionManager.ts | 465 ++++++++++++++++-- .../http_connection_manager/v3/HttpFilter.ts | 10 +- .../v3/ResponseMapper.ts | 12 +- .../wrr_locality/v3/WrrLocality.ts | 25 + .../v3/AggregatedDiscoveryService.ts | 4 +- .../discovery/v3/DeltaDiscoveryRequest.ts | 53 +- .../discovery/v3/DeltaDiscoveryResponse.ts | 17 +- .../service/discovery/v3/DiscoveryRequest.ts | 29 +- .../v3/DynamicParameterConstraints.ts | 119 +++++ .../envoy/service/discovery/v3/Resource.ts | 32 +- .../service/discovery/v3/ResourceLocator.ts | 34 ++ .../service/discovery/v3/ResourceName.ts | 33 ++ .../load_stats/v3/LoadStatsResponse.ts | 20 +- .../envoy/type/http/v3/PathTransformation.ts | 16 +- .../envoy/type/matcher/v3/RegexMatcher.ts | 40 +- .../envoy/type/matcher/v3/StringMatcher.ts | 20 +- .../envoy/type/matcher/v3/StructMatcher.ts | 4 +- .../envoy/type/metadata/v3/MetadataKey.ts | 4 +- packages/grpc-js-xds/src/generated/fault.ts | 26 +- .../google/protobuf/MethodOptions.ts | 3 - .../src/generated/http_connection_manager.ts | 33 ++ .../grpc-js-xds/src/generated/listener.ts | 38 ++ packages/grpc-js-xds/src/generated/lrs.ts | 2 + packages/grpc-js-xds/src/generated/route.ts | 16 + .../grpc-js-xds/src/generated/wrr_locality.ts | 231 +++++++++ .../xds/core/v3/TypedExtensionConfig.ts | 43 ++ .../xds/type/matcher/v3/ListStringMatcher.ts | 17 + .../generated/xds/type/matcher/v3/Matcher.ts | 307 ++++++++++++ .../xds/type/matcher/v3/RegexMatcher.ts | 80 +++ .../xds/type/matcher/v3/StringMatcher.ts | 109 ++++ packages/grpc-js-xds/src/load-balancer-lrs.ts | 200 -------- 139 files changed, 5715 insertions(+), 1347 deletions(-) create mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtraSourceAddress.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/PathConfigSource.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicKeepAliveSettings.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOptionsOverride.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/AdditionalAddress.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListenerManager.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerManager.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ValidationListenerManager.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteList.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ConnectionProperties.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPResponseProperties.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TCPAccessLogEntry.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DynamicParameterConstraints.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceLocator.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceName.ts create mode 100644 packages/grpc-js-xds/src/generated/wrr_locality.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/core/v3/TypedExtensionConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/type/matcher/v3/ListStringMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/type/matcher/v3/Matcher.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/type/matcher/v3/RegexMatcher.ts create mode 100644 packages/grpc-js-xds/src/generated/xds/type/matcher/v3/StringMatcher.ts delete mode 100644 packages/grpc-js-xds/src/load-balancer-lrs.ts diff --git a/packages/grpc-js-xds/deps/envoy-api b/packages/grpc-js-xds/deps/envoy-api index 20b1b5fce..e53e7bbd0 160000 --- a/packages/grpc-js-xds/deps/envoy-api +++ b/packages/grpc-js-xds/deps/envoy-api @@ -1 +1 @@ -Subproject commit 20b1b5fcee88a20a08b71051a961181839ec7268 +Subproject commit e53e7bbd012f81965f2e79848ad9a58ceb67201f diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 7fd7e700d..3c61b7383 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", + "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", "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/ads.ts b/packages/grpc-js-xds/src/generated/ads.ts index 228f6f1d4..d7483075c 100644 --- a/packages/grpc-js-xds/src/generated/ads.ts +++ b/packages/grpc-js-xds/src/generated/ads.ts @@ -24,6 +24,7 @@ export interface ProtoGrpcType { DataSource: MessageTypeDefinition EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition HeaderValueOption: MessageTypeDefinition @@ -44,6 +45,7 @@ export interface ProtoGrpcType { RuntimeUInt32: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition @@ -56,7 +58,7 @@ export interface ProtoGrpcType { v3: { AdsDummy: MessageTypeDefinition /** - * See https://github.com/lyft/envoy-api#apis for a description of the role of + * See https://github.com/envoyproxy/envoy-api#apis for a description of the role of * ADS and how it is intended to be used by a management server. ADS requests * have the same structure as their singleton xDS counterparts, but can * multiplex many resource types on a single stream. The type_url in the @@ -68,7 +70,10 @@ export interface ProtoGrpcType { DeltaDiscoveryResponse: MessageTypeDefinition DiscoveryRequest: MessageTypeDefinition DiscoveryResponse: MessageTypeDefinition + DynamicParameterConstraints: MessageTypeDefinition Resource: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + ResourceName: MessageTypeDefinition } } } diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index 681bc5a2d..1aa37589b 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -20,7 +20,6 @@ export interface ProtoGrpcType { LoadBalancingPolicy: MessageTypeDefinition OutlierDetection: MessageTypeDefinition TrackClusterStats: MessageTypeDefinition - UpstreamBindConfig: MessageTypeDefinition UpstreamConnectionOptions: MessageTypeDefinition } } @@ -45,6 +44,7 @@ export interface ProtoGrpcType { EventServiceConfig: MessageTypeDefinition Extension: MessageTypeDefinition ExtensionConfigSource: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition GrpcProtocolOptions: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition @@ -52,6 +52,7 @@ export interface ProtoGrpcType { HeaderValueOption: MessageTypeDefinition HealthCheck: MessageTypeDefinition HealthStatus: EnumTypeDefinition + HealthStatusSet: MessageTypeDefinition Http1ProtocolOptions: MessageTypeDefinition Http2ProtocolOptions: MessageTypeDefinition Http3ProtocolOptions: MessageTypeDefinition @@ -61,8 +62,10 @@ export interface ProtoGrpcType { Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition + PathConfigSource: MessageTypeDefinition Pipe: MessageTypeDefinition QueryParameter: MessageTypeDefinition + QuicKeepAliveSettings: MessageTypeDefinition QuicProtocolOptions: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition @@ -78,6 +81,7 @@ export interface ProtoGrpcType { SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TcpProtocolOptions: MessageTypeDefinition TrafficDirection: EnumTypeDefinition @@ -97,15 +101,6 @@ export interface ProtoGrpcType { } } } - extensions: { - clusters: { - aggregate: { - v3: { - ClusterConfig: MessageTypeDefinition - } - } - } - } type: { matcher: { v3: { diff --git a/packages/grpc-js-xds/src/generated/csds.ts b/packages/grpc-js-xds/src/generated/csds.ts index 58e903bc9..e09151f50 100644 --- a/packages/grpc-js-xds/src/generated/csds.ts +++ b/packages/grpc-js-xds/src/generated/csds.ts @@ -11,109 +11,41 @@ export interface ProtoGrpcType { envoy: { admin: { v3: { - BootstrapConfigDump: MessageTypeDefinition ClientResourceStatus: EnumTypeDefinition ClustersConfigDump: MessageTypeDefinition - ConfigDump: MessageTypeDefinition + EcdsConfigDump: MessageTypeDefinition EndpointsConfigDump: MessageTypeDefinition ListenersConfigDump: MessageTypeDefinition RoutesConfigDump: MessageTypeDefinition ScopedRoutesConfigDump: MessageTypeDefinition - SecretsConfigDump: MessageTypeDefinition UpdateFailureState: MessageTypeDefinition } } annotations: { } config: { - accesslog: { - v3: { - AccessLog: MessageTypeDefinition - AccessLogFilter: MessageTypeDefinition - AndFilter: MessageTypeDefinition - ComparisonFilter: MessageTypeDefinition - DurationFilter: MessageTypeDefinition - ExtensionFilter: MessageTypeDefinition - GrpcStatusFilter: MessageTypeDefinition - HeaderFilter: MessageTypeDefinition - MetadataFilter: MessageTypeDefinition - NotHealthCheckFilter: MessageTypeDefinition - OrFilter: MessageTypeDefinition - ResponseFlagFilter: MessageTypeDefinition - RuntimeFilter: MessageTypeDefinition - StatusCodeFilter: MessageTypeDefinition - TraceableFilter: MessageTypeDefinition - } - } - bootstrap: { - v3: { - Admin: MessageTypeDefinition - Bootstrap: MessageTypeDefinition - ClusterManager: MessageTypeDefinition - CustomInlineHeader: MessageTypeDefinition - FatalAction: MessageTypeDefinition - LayeredRuntime: MessageTypeDefinition - Runtime: MessageTypeDefinition - RuntimeLayer: MessageTypeDefinition - Watchdog: MessageTypeDefinition - Watchdogs: MessageTypeDefinition - } - } - cluster: { - v3: { - CircuitBreakers: MessageTypeDefinition - Cluster: MessageTypeDefinition - ClusterCollection: MessageTypeDefinition - Filter: MessageTypeDefinition - LoadBalancingPolicy: MessageTypeDefinition - OutlierDetection: MessageTypeDefinition - TrackClusterStats: MessageTypeDefinition - UpstreamBindConfig: MessageTypeDefinition - UpstreamConnectionOptions: MessageTypeDefinition - } - } core: { v3: { Address: MessageTypeDefinition - AggregatedConfigSource: MessageTypeDefinition - AlternateProtocolsCacheOptions: MessageTypeDefinition - ApiConfigSource: MessageTypeDefinition - ApiVersion: EnumTypeDefinition AsyncDataSource: MessageTypeDefinition BackoffStrategy: MessageTypeDefinition BindConfig: MessageTypeDefinition BuildVersion: MessageTypeDefinition CidrRange: MessageTypeDefinition - ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition - DnsResolutionConfig: MessageTypeDefinition - DnsResolverOptions: MessageTypeDefinition EnvoyInternalAddress: MessageTypeDefinition - EventServiceConfig: MessageTypeDefinition Extension: MessageTypeDefinition - ExtensionConfigSource: MessageTypeDefinition - GrpcProtocolOptions: MessageTypeDefinition - GrpcService: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition HeaderValueOption: MessageTypeDefinition - HealthCheck: MessageTypeDefinition - HealthStatus: EnumTypeDefinition - Http1ProtocolOptions: MessageTypeDefinition - Http2ProtocolOptions: MessageTypeDefinition - Http3ProtocolOptions: MessageTypeDefinition - HttpProtocolOptions: MessageTypeDefinition HttpUri: MessageTypeDefinition - KeepaliveSettings: MessageTypeDefinition Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition Pipe: MessageTypeDefinition - ProxyProtocolConfig: MessageTypeDefinition QueryParameter: MessageTypeDefinition - QuicProtocolOptions: MessageTypeDefinition - RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition RetryPolicy: MessageTypeDefinition @@ -123,114 +55,15 @@ export interface ProtoGrpcType { RuntimeFractionalPercent: MessageTypeDefinition RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition - SchemeHeaderTransformation: MessageTypeDefinition - SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition - TcpProtocolOptions: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition - TypedExtensionConfig: MessageTypeDefinition - UdpSocketConfig: MessageTypeDefinition - UpstreamHttpProtocolOptions: MessageTypeDefinition WatchedDirectory: MessageTypeDefinition } } - endpoint: { - v3: { - ClusterLoadAssignment: MessageTypeDefinition - Endpoint: MessageTypeDefinition - LbEndpoint: MessageTypeDefinition - LedsClusterLocalityConfig: MessageTypeDefinition - LocalityLbEndpoints: MessageTypeDefinition - } - } - listener: { - v3: { - ActiveRawUdpListenerConfig: MessageTypeDefinition - ApiListener: MessageTypeDefinition - Filter: MessageTypeDefinition - FilterChain: MessageTypeDefinition - FilterChainMatch: MessageTypeDefinition - Listener: MessageTypeDefinition - ListenerCollection: MessageTypeDefinition - ListenerFilter: MessageTypeDefinition - ListenerFilterChainMatchPredicate: MessageTypeDefinition - QuicProtocolOptions: MessageTypeDefinition - UdpListenerConfig: MessageTypeDefinition - } - } - metrics: { - v3: { - DogStatsdSink: MessageTypeDefinition - HistogramBucketSettings: MessageTypeDefinition - HystrixSink: MessageTypeDefinition - StatsConfig: MessageTypeDefinition - StatsMatcher: MessageTypeDefinition - StatsSink: MessageTypeDefinition - StatsdSink: MessageTypeDefinition - TagSpecifier: MessageTypeDefinition - } - } - overload: { - v3: { - BufferFactoryConfig: MessageTypeDefinition - OverloadAction: MessageTypeDefinition - OverloadManager: MessageTypeDefinition - ResourceMonitor: MessageTypeDefinition - ScaleTimersOverloadActionConfig: MessageTypeDefinition - ScaledTrigger: MessageTypeDefinition - ThresholdTrigger: MessageTypeDefinition - Trigger: MessageTypeDefinition - } - } - route: { - v3: { - CorsPolicy: MessageTypeDefinition - Decorator: MessageTypeDefinition - DirectResponseAction: MessageTypeDefinition - FilterAction: MessageTypeDefinition - FilterConfig: MessageTypeDefinition - HeaderMatcher: MessageTypeDefinition - HedgePolicy: MessageTypeDefinition - InternalRedirectPolicy: MessageTypeDefinition - NonForwardingAction: MessageTypeDefinition - QueryParameterMatcher: MessageTypeDefinition - RateLimit: MessageTypeDefinition - RedirectAction: MessageTypeDefinition - RetryPolicy: MessageTypeDefinition - Route: MessageTypeDefinition - RouteAction: MessageTypeDefinition - RouteMatch: MessageTypeDefinition - Tracing: MessageTypeDefinition - VirtualCluster: MessageTypeDefinition - VirtualHost: MessageTypeDefinition - WeightedCluster: MessageTypeDefinition - } - } - trace: { - v3: { - Tracing: MessageTypeDefinition - } - } - } - extensions: { - transport_sockets: { - tls: { - v3: { - CertificateProviderPluginInstance: MessageTypeDefinition - CertificateValidationContext: MessageTypeDefinition - GenericSecret: MessageTypeDefinition - PrivateKeyProvider: MessageTypeDefinition - SdsSecretConfig: MessageTypeDefinition - Secret: MessageTypeDefinition - TlsCertificate: MessageTypeDefinition - TlsParameters: MessageTypeDefinition - TlsSessionTicketKeys: MessageTypeDefinition - } - } - } } service: { status: { @@ -256,7 +89,6 @@ export interface ProtoGrpcType { DoubleMatcher: MessageTypeDefinition ListMatcher: MessageTypeDefinition ListStringMatcher: MessageTypeDefinition - MetadataMatcher: MessageTypeDefinition NodeMatcher: MessageTypeDefinition RegexMatchAndSubstitute: MessageTypeDefinition RegexMatcher: MessageTypeDefinition @@ -265,19 +97,7 @@ export interface ProtoGrpcType { ValueMatcher: MessageTypeDefinition } } - metadata: { - v3: { - MetadataKey: MessageTypeDefinition - MetadataKind: MessageTypeDefinition - } - } - tracing: { - v3: { - CustomTag: MessageTypeDefinition - } - } v3: { - CodecClientType: EnumTypeDefinition DoubleRange: MessageTypeDefinition FractionalPercent: MessageTypeDefinition Int32Range: MessageTypeDefinition @@ -300,7 +120,6 @@ export interface ProtoGrpcType { DescriptorProto: MessageTypeDefinition DoubleValue: MessageTypeDefinition Duration: MessageTypeDefinition - Empty: MessageTypeDefinition EnumDescriptorProto: MessageTypeDefinition EnumOptions: MessageTypeDefinition EnumValueDescriptorProto: MessageTypeDefinition @@ -336,7 +155,6 @@ export interface ProtoGrpcType { udpa: { annotations: { FieldMigrateAnnotation: MessageTypeDefinition - FieldSecurityAnnotation: MessageTypeDefinition FileMigrateAnnotation: MessageTypeDefinition MigrateAnnotation: MessageTypeDefinition PackageVersionStatus: EnumTypeDefinition @@ -382,10 +200,7 @@ export interface ProtoGrpcType { } core: { v3: { - Authority: MessageTypeDefinition - CollectionEntry: MessageTypeDefinition ContextParams: MessageTypeDefinition - ResourceLocator: MessageTypeDefinition } } } diff --git a/packages/grpc-js-xds/src/generated/endpoint.ts b/packages/grpc-js-xds/src/generated/endpoint.ts index 9a87bc9a5..4fcf914e3 100644 --- a/packages/grpc-js-xds/src/generated/endpoint.ts +++ b/packages/grpc-js-xds/src/generated/endpoint.ts @@ -28,16 +28,20 @@ export interface ProtoGrpcType { EnvoyInternalAddress: MessageTypeDefinition EventServiceConfig: MessageTypeDefinition Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition HeaderValueOption: MessageTypeDefinition HealthCheck: MessageTypeDefinition HealthStatus: EnumTypeDefinition + HealthStatusSet: MessageTypeDefinition HttpUri: MessageTypeDefinition Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition + PathConfigSource: MessageTypeDefinition Pipe: MessageTypeDefinition QueryParameter: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition @@ -53,9 +57,11 @@ export interface ProtoGrpcType { SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition WatchedDirectory: MessageTypeDefinition } } diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts index 31c3a813a..8488bbdd7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto /** * Resource status from the view of a xDS client, which tells the synchronization diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts index ab7c528bf..aabcd212a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; @@ -27,7 +27,7 @@ export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster { 'last_updated'?: (_google_protobuf_Timestamp | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] @@ -62,7 +62,7 @@ export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output { 'last_updated': (_google_protobuf_Timestamp__Output | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts new file mode 100644 index 000000000..e63307cb5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts @@ -0,0 +1,100 @@ +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; +import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_EcdsConfigDump_EcdsFilterConfig { + /** + * This is the per-resource version information. This version is currently + * taken from the :ref:`version_info + * ` + * field at the time that the ECDS filter was loaded. + */ + 'version_info'?: (string); + /** + * The ECDS filter config. + */ + 'ecds_filter'?: (_google_protobuf_Any | null); + /** + * The timestamp when the ECDS filter was last updated. + */ + 'last_updated'?: (_google_protobuf_Timestamp | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The ``error_state`` field contains the rejected version of this + * particular resource along with the reason and timestamp. For successfully + * updated or acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * [#next-free-field: 6] + */ +export interface _envoy_admin_v3_EcdsConfigDump_EcdsFilterConfig__Output { + /** + * This is the per-resource version information. This version is currently + * taken from the :ref:`version_info + * ` + * field at the time that the ECDS filter was loaded. + */ + 'version_info': (string); + /** + * The ECDS filter config. + */ + 'ecds_filter': (_google_protobuf_Any__Output | null); + /** + * The timestamp when the ECDS filter was last updated. + */ + 'last_updated': (_google_protobuf_Timestamp__Output | null); + /** + * Set if the last update failed, cleared after the next successful update. + * The ``error_state`` field contains the rejected version of this + * particular resource along with the reason and timestamp. For successfully + * updated or acknowledged resource, this field should be empty. + * [#not-implemented-hide:] + */ + 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); + /** + * The client status of this resource. + * [#not-implemented-hide:] + */ + 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); +} + +/** + * Envoy's ECDS service fills this message with all currently extension + * configuration. Extension configuration information can be used to recreate + * an Envoy ECDS listener and HTTP filters as static filters or by returning + * them in ECDS response. + */ +export interface EcdsConfigDump { + /** + * The ECDS filter configs. + */ + 'ecds_filters'?: (_envoy_admin_v3_EcdsConfigDump_EcdsFilterConfig)[]; +} + +/** + * Envoy's ECDS service fills this message with all currently extension + * configuration. Extension configuration information can be used to recreate + * an Envoy ECDS listener and HTTP filters as static filters or by returning + * them in ECDS response. + */ +export interface EcdsConfigDump__Output { + /** + * The ECDS filter configs. + */ + 'ecds_filters': (_envoy_admin_v3_EcdsConfigDump_EcdsFilterConfig__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts index d68b27e7c..ab5485dbe 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; @@ -25,7 +25,7 @@ export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig { 'last_updated'?: (_google_protobuf_Timestamp | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] @@ -58,7 +58,7 @@ export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig__Outp 'last_updated': (_google_protobuf_Timestamp__Output | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts index 745abedae..946e37953 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; @@ -35,7 +35,7 @@ export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener { 'draining_state'?: (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. */ @@ -77,7 +77,7 @@ export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener__Output { 'draining_state': (_envoy_admin_v3_ListenersConfigDump_DynamicListenerState__Output | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts index 2a62e9b74..7b9bb29d0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; @@ -25,7 +25,7 @@ export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig { 'last_updated'?: (_google_protobuf_Timestamp | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] @@ -58,7 +58,7 @@ export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig__Output { 'last_updated': (_google_protobuf_Timestamp__Output | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts index f271635bc..c0723ce69 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; @@ -29,7 +29,7 @@ export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfig 'last_updated'?: (_google_protobuf_Timestamp | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] @@ -66,7 +66,7 @@ export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfig 'last_updated': (_google_protobuf_Timestamp__Output | null); /** * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular + * The ``error_state`` field contains the rejected version of this particular * resource along with the reason and timestamp. For successfully updated or * acknowledged resource, this field should be empty. * [#not-implemented-hide:] diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts index b98e8cd4d..100c65a1b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/UpdateFailureState.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto +// Original file: deps/envoy-api/envoy/admin/v3/config_dump_shared.proto import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts index 367d8f302..73a031fdd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLog.ts @@ -5,9 +5,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ export interface AccessLog { /** - * The name of the access log extension to instantiate. - * The name must match one of the compiled in loggers. - * See the :ref:`extensions listed in typed_config below ` for the default list of available loggers. + * The name of the access log extension configuration. */ 'name'?: (string); /** @@ -24,9 +22,7 @@ export interface AccessLog { export interface AccessLog__Output { /** - * The name of the access log extension to instantiate. - * The name must match one of the compiled in loggers. - * See the :ref:`extensions listed in typed_config below ` for the default list of available loggers. + * The name of the access log extension configuration. */ 'name': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts index 85a952c9c..09563cb7a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/AccessLogFilter.ts @@ -12,9 +12,10 @@ import type { ResponseFlagFilter as _envoy_config_accesslog_v3_ResponseFlagFilte import type { GrpcStatusFilter as _envoy_config_accesslog_v3_GrpcStatusFilter, GrpcStatusFilter__Output as _envoy_config_accesslog_v3_GrpcStatusFilter__Output } from '../../../../envoy/config/accesslog/v3/GrpcStatusFilter'; import type { ExtensionFilter as _envoy_config_accesslog_v3_ExtensionFilter, ExtensionFilter__Output as _envoy_config_accesslog_v3_ExtensionFilter__Output } from '../../../../envoy/config/accesslog/v3/ExtensionFilter'; import type { MetadataFilter as _envoy_config_accesslog_v3_MetadataFilter, MetadataFilter__Output as _envoy_config_accesslog_v3_MetadataFilter__Output } from '../../../../envoy/config/accesslog/v3/MetadataFilter'; +import type { LogTypeFilter as _envoy_config_accesslog_v3_LogTypeFilter, LogTypeFilter__Output as _envoy_config_accesslog_v3_LogTypeFilter__Output } from '../../../../envoy/config/accesslog/v3/LogTypeFilter'; /** - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface AccessLogFilter { /** @@ -59,17 +60,22 @@ export interface AccessLogFilter { 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter | null); /** * Extension filter. + * [#extension-category: envoy.access_loggers.extension_filters] */ 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter | null); /** * Metadata Filter */ 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter | null); - 'filter_specifier'?: "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; + /** + * Log Type Filter + */ + 'log_type_filter'?: (_envoy_config_accesslog_v3_LogTypeFilter | null); + 'filter_specifier'?: "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"|"log_type_filter"; } /** - * [#next-free-field: 13] + * [#next-free-field: 14] */ export interface AccessLogFilter__Output { /** @@ -114,11 +120,16 @@ export interface AccessLogFilter__Output { 'grpc_status_filter'?: (_envoy_config_accesslog_v3_GrpcStatusFilter__Output | null); /** * Extension filter. + * [#extension-category: envoy.access_loggers.extension_filters] */ 'extension_filter'?: (_envoy_config_accesslog_v3_ExtensionFilter__Output | null); /** * Metadata Filter */ 'metadata_filter'?: (_envoy_config_accesslog_v3_MetadataFilter__Output | null); - 'filter_specifier': "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"; + /** + * Log Type Filter + */ + 'log_type_filter'?: (_envoy_config_accesslog_v3_LogTypeFilter__Output | null); + 'filter_specifier': "status_code_filter"|"duration_filter"|"not_health_check_filter"|"traceable_filter"|"runtime_filter"|"and_filter"|"or_filter"|"header_filter"|"response_flag_filter"|"grpc_status_filter"|"extension_filter"|"metadata_filter"|"log_type_filter"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts index ee61e55fa..024936704 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/DurationFilter.ts @@ -3,7 +3,10 @@ import type { ComparisonFilter as _envoy_config_accesslog_v3_ComparisonFilter, ComparisonFilter__Output as _envoy_config_accesslog_v3_ComparisonFilter__Output } from '../../../../envoy/config/accesslog/v3/ComparisonFilter'; /** - * Filters on total request duration in milliseconds. + * Filters based on the duration of the request or stream, in milliseconds. + * For end of stream access logs, the total duration of the stream will be used. + * For :ref:`periodic access logs`, + * the duration of the stream at the time of log recording will be used. */ export interface DurationFilter { /** @@ -13,7 +16,10 @@ export interface DurationFilter { } /** - * Filters on total request duration in milliseconds. + * Filters based on the duration of the request or stream, in milliseconds. + * For end of stream access logs, the total duration of the stream will be used. + * For :ref:`periodic access logs`, + * the duration of the stream at the time of log recording will be used. */ export interface DurationFilter__Output { /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts new file mode 100644 index 000000000..8d51cd33f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts @@ -0,0 +1,33 @@ +// Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto + +import type { AccessLogType as _envoy_data_accesslog_v3_AccessLogType } from '../../../../envoy/data/accesslog/v3/AccessLogType'; + +/** + * Filters based on access log type. + */ +export interface LogTypeFilter { + /** + * Logs only records which their type is one of the types defined in this field. + */ + 'types'?: (_envoy_data_accesslog_v3_AccessLogType | keyof typeof _envoy_data_accesslog_v3_AccessLogType)[]; + /** + * If this field is set to true, the filter will instead block all records + * with a access log type in types field, and allow all other records. + */ + 'exclude'?: (boolean); +} + +/** + * Filters based on access log type. + */ +export interface LogTypeFilter__Output { + /** + * Logs only records which their type is one of the types defined in this field. + */ + 'types': (keyof typeof _envoy_data_accesslog_v3_AccessLogType)[]; + /** + * If this field is set to true, the filter will instead block all records + * with a access log type in types field, and allow all other records. + */ + 'exclude': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts index 83b075388..b1c940088 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/RuntimeFilter.ts @@ -8,7 +8,7 @@ import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalP export interface RuntimeFilter { /** * Runtime key to get an optional overridden numerator for use in the - * *percent_sampled* field. If found in runtime, this value will replace the + * ``percent_sampled`` field. If found in runtime, this value will replace the * default numerator. */ 'runtime_key'?: (string); @@ -24,9 +24,9 @@ export interface RuntimeFilter { * is present, the filter will consistently sample across multiple hosts based * on the runtime key value and the value extracted from * :ref:`x-request-id`. If it is - * missing, or *use_independent_randomness* is set to true, the filter will + * missing, or ``use_independent_randomness`` is set to true, the filter will * randomly sample based on the runtime key value alone. - * *use_independent_randomness* can be used for logging kill switches within + * ``use_independent_randomness`` can be used for logging kill switches within * complex nested :ref:`AndFilter * ` and :ref:`OrFilter * ` blocks that are easier to @@ -43,7 +43,7 @@ export interface RuntimeFilter { export interface RuntimeFilter__Output { /** * Runtime key to get an optional overridden numerator for use in the - * *percent_sampled* field. If found in runtime, this value will replace the + * ``percent_sampled`` field. If found in runtime, this value will replace the * default numerator. */ 'runtime_key': (string); @@ -59,9 +59,9 @@ export interface RuntimeFilter__Output { * is present, the filter will consistently sample across multiple hosts based * on the runtime key value and the value extracted from * :ref:`x-request-id`. If it is - * missing, or *use_independent_randomness* is set to true, the filter will + * missing, or ``use_independent_randomness`` is set to true, the filter will * randomly sample based on the runtime key value alone. - * *use_independent_randomness* can be used for logging kill switches within + * ``use_independent_randomness`` can be used for logging kill switches within * complex nested :ref:`AndFilter * ` and :ref:`OrFilter * ` blocks that are easier to diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts index 12731b056..4a8a4be36 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts @@ -59,11 +59,13 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { /** * The maximum number of pending requests that Envoy will allow to the * upstream cluster. If not specified, the default is 1024. + * This limit is applied as a connection limit for non-HTTP traffic. */ 'max_pending_requests'?: (_google_protobuf_UInt32Value | null); /** * The maximum number of parallel requests that Envoy will make to the * upstream cluster. If not specified, the default is 1024. + * This limit does not apply to non-HTTP traffic. */ 'max_requests'?: (_google_protobuf_UInt32Value | null); /** @@ -121,11 +123,13 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { /** * The maximum number of pending requests that Envoy will allow to the * upstream cluster. If not specified, the default is 1024. + * This limit is applied as a connection limit for non-HTTP traffic. */ 'max_pending_requests': (_google_protobuf_UInt32Value__Output | null); /** * The maximum number of parallel requests that Envoy will make to the * upstream cluster. If not specified, the default is 1024. + * This limit does not apply to non-HTTP traffic. */ 'max_requests': (_google_protobuf_UInt32Value__Output | null); /** @@ -177,6 +181,20 @@ export interface CircuitBreakers { * are used. */ 'thresholds'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds)[]; + /** + * Optional per-host limits which apply to each individual host in a cluster. + * + * .. note:: + * currently only the :ref:`max_connections + * ` field is supported for per-host limits. + * + * If multiple per-host :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, + * the first one in the list is used. If no per-host Thresholds are defined for a given + * :ref:`RoutingPriority`, + * the cluster will not have per-host limits. + */ + 'per_host_thresholds'?: (_envoy_config_cluster_v3_CircuitBreakers_Thresholds)[]; } /** @@ -192,4 +210,18 @@ export interface CircuitBreakers__Output { * are used. */ 'thresholds': (_envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output)[]; + /** + * Optional per-host limits which apply to each individual host in a cluster. + * + * .. note:: + * currently only the :ref:`max_connections + * ` field is supported for per-host limits. + * + * If multiple per-host :ref:`Thresholds` + * are defined with the same :ref:`RoutingPriority`, + * the first one in the list is used. If no per-host Thresholds are defined for a given + * :ref:`RoutingPriority`, + * the cluster will not have per-host limits. + */ + 'per_host_thresholds': (_envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts index 12ec7b633..be30d8212 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts @@ -25,8 +25,9 @@ import type { DnsResolutionConfig as _envoy_config_core_v3_DnsResolutionConfig, import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; import type { RuntimeDouble as _envoy_config_core_v3_RuntimeDouble, RuntimeDouble__Output as _envoy_config_core_v3_RuntimeDouble__Output } from '../../../../envoy/config/core/v3/RuntimeDouble'; -import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; +import type { HealthStatusSet as _envoy_config_core_v3_HealthStatusSet, HealthStatusSet__Output as _envoy_config_core_v3_HealthStatusSet__Output } from '../../../../envoy/config/core/v3/HealthStatusSet'; import type { DoubleValue as _google_protobuf_DoubleValue, DoubleValue__Output as _google_protobuf_DoubleValue__Output } from '../../../../google/protobuf/DoubleValue'; import type { Long } from '@grpc/proto-loader'; @@ -47,7 +48,7 @@ export enum _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection { /** * Common configuration for all load balancer implementations. - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { /** @@ -85,7 +86,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { */ 'ignore_new_hosts_until_first_hc'?: (boolean); /** - * If set to `true`, the cluster manager will drain all existing + * If set to ``true``, the cluster manager will drain all existing * connections to upstream hosts whenever hosts are added or removed from the cluster. */ 'close_connections_on_host_set_change'?: (boolean); @@ -93,12 +94,21 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig { * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ 'consistent_hashing_lb_config'?: (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig | null); + /** + * This controls what hosts are considered valid when using + * :ref:`host overrides `, which is used by some + * filters to modify the load balancing decision. + * + * If this is unset then [UNKNOWN, HEALTHY, DEGRADED] will be applied by default. If this is + * set with an empty set of statuses then host overrides will be ignored by the load balancing. + */ + 'override_host_status'?: (_envoy_config_core_v3_HealthStatusSet | null); 'locality_config_specifier'?: "zone_aware_lb_config"|"locality_weighted_lb_config"; } /** * Common configuration for all load balancer implementations. - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { /** @@ -136,7 +146,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { */ 'ignore_new_hosts_until_first_hc': (boolean); /** - * If set to `true`, the cluster manager will drain all existing + * If set to ``true``, the cluster manager will drain all existing * connections to upstream hosts whenever hosts are added or removed from the cluster. */ 'close_connections_on_host_set_change': (boolean); @@ -144,6 +154,15 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) */ 'consistent_hashing_lb_config': (_envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output | null); + /** + * This controls what hosts are considered valid when using + * :ref:`host overrides `, which is used by some + * filters to modify the load balancing decision. + * + * If this is unset then [UNKNOWN, HEALTHY, DEGRADED] will be applied by default. If this is + * set with an empty set of statuses then host overrides will be ignored by the load balancing. + */ + 'override_host_status': (_envoy_config_core_v3_HealthStatusSet__Output | null); 'locality_config_specifier': "zone_aware_lb_config"|"locality_weighted_lb_config"; } @@ -152,7 +171,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig__Output { */ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig { /** - * If set to `true`, the cluster will use hostname instead of the resolved + * If set to ``true``, the cluster will use hostname instead of the resolved * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. */ 'use_hostname_for_hashing'?: (boolean); @@ -165,7 +184,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashi * Applies to both Ring Hash and Maglev load balancers. * * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified - * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * ``hash_balance_factor``, requests to any upstream host are capped at ``hash_balance_factor/100`` times the average number of requests * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the @@ -173,7 +192,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashi * * If weights are specified on the hosts, they are respected. * - * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * This is an O(N) algorithm, unlike other load balancers. Using a lower ``hash_balance_factor`` results in more hosts * being probed, so use a higher value if you require better performance. */ 'hash_balance_factor'?: (_google_protobuf_UInt32Value | null); @@ -184,7 +203,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashi */ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashingLbConfig__Output { /** - * If set to `true`, the cluster will use hostname instead of the resolved + * If set to ``true``, the cluster will use hostname instead of the resolved * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. */ 'use_hostname_for_hashing': (boolean); @@ -197,7 +216,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashi * Applies to both Ring Hash and Maglev load balancers. * * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified - * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * ``hash_balance_factor``, requests to any upstream host are capped at ``hash_balance_factor/100`` times the average number of requests * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the @@ -205,7 +224,7 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_ConsistentHashi * * If weights are specified on the hosts, they are respected. * - * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * This is an O(N) algorithm, unlike other load balancers. Using a lower ``hash_balance_factor`` results in more hosts * being probed, so use a higher value if you require better performance. */ 'hash_balance_factor': (_google_protobuf_UInt32Value__Output | null); @@ -294,6 +313,10 @@ export enum _envoy_config_cluster_v3_Cluster_DiscoveryType { * If V4_PREFERRED is specified, the DNS resolver will first perform a lookup for addresses in the * IPv4 family and fallback to a lookup for addresses in the IPv6 family. i.e., the callback * target will only get v6 addresses if there were NO v4 addresses to return. + * If ALL is specified, the DNS resolver will perform a lookup for both IPv4 and IPv6 families, + * and return all resolved addresses. When this is used, Happy Eyeballs will be enabled for + * upstream connections. Refer to :ref:`Happy Eyeballs Support ` + * for more information. * For cluster types other than * :ref:`STRICT_DNS` and * :ref:`LOGICAL_DNS`, @@ -306,6 +329,7 @@ export enum _envoy_config_cluster_v3_Cluster_DnsLookupFamily { V4_ONLY = 1, V6_ONLY = 2, V4_PREFERRED = 3, + ALL = 4, } /** @@ -403,9 +427,9 @@ export enum _envoy_config_cluster_v3_Cluster_LbPolicy { /** * Use the new :ref:`load_balancing_policy * ` field to determine the LB policy. - * [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field - * and instead using the new load_balancing_policy field as the one and only mechanism for - * configuring this.] + * This has been deprecated in favor of using the :ref:`load_balancing_policy + * ` field without + * setting any value in :ref:`lb_policy`. */ LOAD_BALANCING_POLICY_CONFIG = 7, } @@ -413,7 +437,7 @@ export enum _envoy_config_cluster_v3_Cluster_LbPolicy { /** * Optionally divide the endpoints in this cluster into subsets defined by * endpoint metadata and selected by route and weighted cluster metadata. - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { /** @@ -427,7 +451,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { * fallback_policy is * :ref:`DEFAULT_SUBSET`. * Each field in default_subset is - * compared to the matching LbEndpoint.Metadata under the *envoy.lb* + * compared to the matching LbEndpoint.Metadata under the ``envoy.lb`` * namespace. It is valid for no hosts to match, in which case the behavior * is the same as a fallback_policy of * :ref:`NO_FALLBACK`. @@ -435,7 +459,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { 'default_subset'?: (_google_protobuf_Struct | null); /** * For each entry, LbEndpoint.Metadata's - * *envoy.lb* namespace is traversed and a subset is created for each unique + * ``envoy.lb`` namespace is traversed and a subset is created for each unique * combination of key and value. For example: * * .. code-block:: json @@ -485,12 +509,22 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { * and any of the elements in the list matches the criteria. */ 'list_as_any'?: (boolean); + /** + * Fallback mechanism that allows to try different route metadata until a host is found. + * If load balancing process, including all its mechanisms (like + * :ref:`fallback_policy`) + * fails to select a host, this policy decides if and how the process is repeated using another metadata. + * + * The value defaults to + * :ref:`METADATA_NO_FALLBACK`. + */ + 'metadata_fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy); } /** * Optionally divide the endpoints in this cluster into subsets defined by * endpoint metadata and selected by route and weighted cluster metadata. - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { /** @@ -504,7 +538,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { * fallback_policy is * :ref:`DEFAULT_SUBSET`. * Each field in default_subset is - * compared to the matching LbEndpoint.Metadata under the *envoy.lb* + * compared to the matching LbEndpoint.Metadata under the ``envoy.lb`` * namespace. It is valid for no hosts to match, in which case the behavior * is the same as a fallback_policy of * :ref:`NO_FALLBACK`. @@ -512,7 +546,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { 'default_subset': (_google_protobuf_Struct__Output | null); /** * For each entry, LbEndpoint.Metadata's - * *envoy.lb* namespace is traversed and a subset is created for each unique + * ``envoy.lb`` namespace is traversed and a subset is created for each unique * combination of key and value. For example: * * .. code-block:: json @@ -562,6 +596,16 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { * and any of the elements in the list matches the criteria. */ 'list_as_any': (boolean); + /** + * Fallback mechanism that allows to try different route metadata until a host is found. + * If load balancing process, including all its mechanisms (like + * :ref:`fallback_policy`) + * fails to select a host, this policy decides if and how the process is repeated using another metadata. + * + * The value defaults to + * :ref:`METADATA_NO_FALLBACK`. + */ + 'metadata_fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy); } // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto @@ -579,6 +623,57 @@ export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPoli DEFAULT_SUBSET = 2, } +// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto + +export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy { + /** + * No fallback. Route metadata will be used as-is. + */ + METADATA_NO_FALLBACK = 0, + /** + * A special metadata key ``fallback_list`` will be used to provide variants of metadata to try. + * Value of ``fallback_list`` key has to be a list. Every list element has to be a struct - it will + * be merged with route metadata, overriding keys that appear in both places. + * ``fallback_list`` entries will be used in order until a host is found. + * + * ``fallback_list`` key itself is removed from metadata before subset load balancing is performed. + * + * Example: + * + * for metadata: + * + * .. code-block:: yaml + * + * version: 1.0 + * fallback_list: + * - version: 2.0 + * hardware: c64 + * - hardware: c32 + * - version: 3.0 + * + * at first, metadata: + * + * .. code-block:: json + * + * {"version": "2.0", "hardware": "c64"} + * + * will be used for load balancing. If no host is found, metadata: + * + * .. code-block:: json + * + * {"version": "1.0", "hardware": "c32"} + * + * is next to try. If it still results in no host, finally metadata: + * + * .. code-block:: json + * + * {"version": "3.0"} + * + * is used. + */ + FALLBACK_LIST = 1, +} + /** * Specifications for subsets. */ @@ -591,12 +686,9 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto * Selects a mode of operation in which each subset has only one host. This mode uses the same rules for * choosing a host, but updating hosts is faster, especially for large numbers of hosts. * - * If a match is found to a host, that host will be used regardless of priority levels, unless the host is unhealthy. - * - * Currently, this mode is only supported if `subset_selectors` has only one entry, and `keys` contains - * only one entry. + * If a match is found to a host, that host will be used regardless of priority levels. * - * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in `keys` + * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in ``keys`` * will use only one of the hosts with the given key; no requests will be routed to the others. The cluster gauge * :ref:`lb_subsets_single_host_per_subset_duplicate` indicates how many duplicates are * present in the current configuration. @@ -616,7 +708,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto * For any other fallback policy the parameter is not used and should not be set. * Only values also present in * :ref:`keys` are allowed, but - * `fallback_keys_subset` cannot be equal to `keys`. + * ``fallback_keys_subset`` cannot be equal to ``keys``. */ 'fallback_keys_subset'?: (string)[]; } @@ -633,12 +725,9 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto * Selects a mode of operation in which each subset has only one host. This mode uses the same rules for * choosing a host, but updating hosts is faster, especially for large numbers of hosts. * - * If a match is found to a host, that host will be used regardless of priority levels, unless the host is unhealthy. + * If a match is found to a host, that host will be used regardless of priority levels. * - * Currently, this mode is only supported if `subset_selectors` has only one entry, and `keys` contains - * only one entry. - * - * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in `keys` + * When this mode is enabled, configurations that contain more than one host with the same metadata value for the single key in ``keys`` * will use only one of the hosts with the given key; no requests will be routed to the others. The cluster gauge * :ref:`lb_subsets_single_host_per_subset_duplicate` indicates how many duplicates are * present in the current configuration. @@ -658,7 +747,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto * For any other fallback policy the parameter is not used and should not be set. * Only values also present in * :ref:`keys` are allowed, but - * `fallback_keys_subset` cannot be equal to `keys`. + * ``fallback_keys_subset`` cannot be equal to ``keys``. */ 'fallback_keys_subset': (string)[]; } @@ -710,18 +799,18 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig { * The following formula is used to calculate the dynamic weights when hosts have different load * balancing weights: * - * `weight = load_balancing_weight / (active_requests + 1)^active_request_bias` + * ``weight = load_balancing_weight / (active_requests + 1)^active_request_bias`` * * The larger the active request bias is, the more aggressively active requests will lower the * effective weight when all host weights are not equal. * - * `active_request_bias` must be greater than or equal to 0.0. + * ``active_request_bias`` must be greater than or equal to 0.0. * - * When `active_request_bias == 0.0` the Least Request Load Balancer doesn't consider the number + * When ``active_request_bias == 0.0`` the Least Request Load Balancer doesn't consider the number * of active requests at the time it picks a host and behaves like the Round Robin Load * Balancer. * - * When `active_request_bias > 0.0` the Least Request Load Balancer scales the load balancing + * When ``active_request_bias > 0.0`` the Least Request Load Balancer scales the load balancing * weight by the number of active requests at the time it does a pick. * * The value is cached for performance reasons and refreshed whenever one of the Load Balancer's @@ -752,18 +841,18 @@ export interface _envoy_config_cluster_v3_Cluster_LeastRequestLbConfig__Output { * The following formula is used to calculate the dynamic weights when hosts have different load * balancing weights: * - * `weight = load_balancing_weight / (active_requests + 1)^active_request_bias` + * ``weight = load_balancing_weight / (active_requests + 1)^active_request_bias`` * * The larger the active request bias is, the more aggressively active requests will lower the * effective weight when all host weights are not equal. * - * `active_request_bias` must be greater than or equal to 0.0. + * ``active_request_bias`` must be greater than or equal to 0.0. * - * When `active_request_bias == 0.0` the Least Request Load Balancer doesn't consider the number + * When ``active_request_bias == 0.0`` the Least Request Load Balancer doesn't consider the number * of active requests at the time it picks a host and behaves like the Round Robin Load * Balancer. * - * When `active_request_bias > 0.0` the Least Request Load Balancer scales the load balancing + * When ``active_request_bias > 0.0`` the Least Request Load Balancer scales the load balancing * weight by the number of active requests at the time it does a pick. * * The value is cached for performance reasons and refreshed whenever one of the Load Balancer's @@ -801,8 +890,8 @@ export interface _envoy_config_cluster_v3_Cluster_CommonLbConfig_LocalityWeighte */ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig { /** - * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. - * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same + * The table size for Maglev hashing. Maglev aims for "minimal disruption" rather than an absolute guarantee. + * Minimal disruption means that when the set of upstream hosts change, a connection will likely be sent to the same * upstream as it was before. Increasing the table size reduces the amount of disruption. * The table size must be prime number limited to 5000011. If it is not specified, the default is 65537. */ @@ -815,8 +904,8 @@ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig { */ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output { /** - * The table size for Maglev hashing. The Maglev aims for ‘minimal disruption’ rather than an absolute guarantee. - * Minimal disruption means that when the set of upstreams changes, a connection will likely be sent to the same + * The table size for Maglev hashing. Maglev aims for "minimal disruption" rather than an absolute guarantee. + * Minimal disruption means that when the set of upstream hosts change, a connection will likely be sent to the same * upstream as it was before. Increasing the table size reduces the amount of disruption. * The table size must be prime number limited to 5000011. If it is not specified, the default is 65537. */ @@ -827,12 +916,12 @@ export interface _envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output { * Specific configuration for the * :ref:`Original Destination ` * load balancing policy. + * [#extension: envoy.clusters.original_dst] */ export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig { /** - * When true, :ref:`x-envoy-original-dst-host - * ` can be used to override destination - * address. + * When true, a HTTP header can be used to override the original dst address. The default header is + * :ref:`x-envoy-original-dst-host `. * * .. attention:: * @@ -845,18 +934,28 @@ export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig { * If the header appears multiple times only the first value is used. */ 'use_http_header'?: (boolean); + /** + * The http header to override destination address if :ref:`use_http_header `. + * is set to true. If the value is empty, :ref:`x-envoy-original-dst-host ` will be used. + */ + 'http_header_name'?: (string); + /** + * The port to override for the original dst address. This port + * will take precedence over filter state and header override ports + */ + 'upstream_port_override'?: (_google_protobuf_UInt32Value | null); } /** * Specific configuration for the * :ref:`Original Destination ` * load balancing policy. + * [#extension: envoy.clusters.original_dst] */ export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output { /** - * When true, :ref:`x-envoy-original-dst-host - * ` can be used to override destination - * address. + * When true, a HTTP header can be used to override the original dst address. The default header is + * :ref:`x-envoy-original-dst-host `. * * .. attention:: * @@ -869,6 +968,16 @@ export interface _envoy_config_cluster_v3_Cluster_OriginalDstLbConfig__Output { * If the header appears multiple times only the first value is used. */ 'use_http_header': (boolean); + /** + * The http header to override destination address if :ref:`use_http_header `. + * is set to true. If the value is empty, :ref:`x-envoy-original-dst-host ` will be used. + */ + 'http_header_name': (string); + /** + * The port to override for the original dst address. This port + * will take precedence over filter state and header override ports + */ + 'upstream_port_override': (_google_protobuf_UInt32Value__Output | null); } export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy { @@ -900,10 +1009,10 @@ export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy { */ 'per_upstream_preconnect_ratio'?: (_google_protobuf_DoubleValue | null); /** - * Indicates how many many streams (rounded up) can be anticipated across a cluster for each + * Indicates how many streams (rounded up) can be anticipated across a cluster for each * stream, useful for low QPS services. This is currently supported for a subset of * deterministic non-hash-based load-balancing algorithms (weighted round robin, random). - * Unlike *per_upstream_preconnect_ratio* this preconnects across the upstream instances in a + * Unlike ``per_upstream_preconnect_ratio`` this preconnects across the upstream instances in a * cluster, doing best effort predictions of what upstream would be picked next and * pre-establishing a connection. * @@ -955,10 +1064,10 @@ export interface _envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output { */ 'per_upstream_preconnect_ratio': (_google_protobuf_DoubleValue__Output | null); /** - * Indicates how many many streams (rounded up) can be anticipated across a cluster for each + * Indicates how many streams (rounded up) can be anticipated across a cluster for each * stream, useful for low QPS services. This is currently supported for a subset of * deterministic non-hash-based load-balancing algorithms (weighted round robin, random). - * Unlike *per_upstream_preconnect_ratio* this preconnects across the upstream instances in a + * Unlike ``per_upstream_preconnect_ratio`` this preconnects across the upstream instances in a * cluster, doing best effort predictions of what upstream would be picked next and * pre-establishing a connection. * @@ -1103,13 +1212,19 @@ export interface _envoy_config_cluster_v3_Cluster_SlowStartConfig { * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. * * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: - * `new_weight = weight * time_factor ^ (1 / aggression)`, - * where `time_factor=(time_since_start_seconds / slow_start_time_seconds)`. + * ``new_weight = weight * max(min_weight_percent, time_factor ^ (1 / aggression))``, + * where ``time_factor=(time_since_start_seconds / slow_start_time_seconds)``. * * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. * Once host exits slow start, time_factor and aggression no longer affect its weight. */ 'aggression'?: (_envoy_config_core_v3_RuntimeDouble | null); + /** + * Configures the minimum percentage of origin weight that avoids too small new weight, + * which may cause endpoints in slow start mode receive no traffic in slow start window. + * If not specified, the default is 10%. + */ + 'min_weight_percent'?: (_envoy_type_v3_Percent | null); } /** @@ -1130,13 +1245,19 @@ export interface _envoy_config_cluster_v3_Cluster_SlowStartConfig__Output { * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. * * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: - * `new_weight = weight * time_factor ^ (1 / aggression)`, - * where `time_factor=(time_since_start_seconds / slow_start_time_seconds)`. + * ``new_weight = weight * max(min_weight_percent, time_factor ^ (1 / aggression))``, + * where ``time_factor=(time_since_start_seconds / slow_start_time_seconds)``. * * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. * Once host exits slow start, time_factor and aggression no longer affect its weight. */ 'aggression': (_envoy_config_core_v3_RuntimeDouble__Output | null); + /** + * Configures the minimum percentage of origin weight that avoids too small new weight, + * which may cause endpoints in slow start mode receive no traffic in slow start window. + * If not specified, the default is 10%. + */ + 'min_weight_percent': (_envoy_type_v3_Percent__Output | null); } /** @@ -1152,7 +1273,7 @@ export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch { * Optional endpoint metadata match criteria. * The connection to the endpoint with metadata matching what is set in this field * will use the transport socket configuration specified here. - * The endpoint's metadata entry in *envoy.transport_socket_match* is used to match + * The endpoint's metadata entry in ``envoy.transport_socket_match`` is used to match * against the values specified in this field. */ 'match'?: (_google_protobuf_Struct | null); @@ -1176,7 +1297,7 @@ export interface _envoy_config_cluster_v3_Cluster_TransportSocketMatch__Output { * Optional endpoint metadata match criteria. * The connection to the endpoint with metadata matching what is set in this field * will use the transport socket configuration specified here. - * The endpoint's metadata entry in *envoy.transport_socket_match* is used to match + * The endpoint's metadata entry in ``envoy.transport_socket_match`` is used to match * against the values specified in this field. */ 'match': (_google_protobuf_Struct__Output | null); @@ -1319,7 +1440,7 @@ export interface Cluster { * set so that Envoy will assume that the upstream supports HTTP/2 when * making new HTTP connection pool connections. Currently, Envoy only * supports prior knowledge for upstream connections. Even if TLS is used - * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 + * with ALPN, ``http2_protocol_options`` must be specified. As an aside this allows HTTP/2 * connections to happen over plain text. * This has been deprecated in favor of http2_protocol_options fields in the * :ref:`http_protocol_options ` @@ -1359,10 +1480,7 @@ export interface Cluster { * :ref:`STRICT_DNS` * and :ref:`LOGICAL_DNS` * this setting is ignored. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple's API only allows overriding DNS resolvers via system settings. - * This field is deprecated in favor of *dns_resolution_config* + * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. */ 'dns_resolvers'?: (_envoy_config_core_v3_Address)[]; @@ -1404,8 +1522,8 @@ export interface Cluster { 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig | null); /** * Optional custom transport socket implementation to use for upstream connections. - * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and - * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * To setup TLS, set a transport socket with name ``envoy.transport_sockets.tls`` and + * :ref:`UpstreamTlsContexts ` in the ``typed_config``. * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ @@ -1415,7 +1533,7 @@ export interface Cluster { * cluster. It can be used for stats, logging, and varying filter behavior. * Fields should use reverse DNS notation to denote which entity within Envoy * will need the information. For instance, if the metadata is intended for - * the Router filter, the filter name should be specified as *envoy.filters.http.router*. + * the Router filter, the filter name should be specified as ``envoy.filters.http.router``. */ 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** @@ -1436,11 +1554,9 @@ export interface Cluster { * emitting stats for the cluster and access logging the cluster name. This will appear as * additional information in configuration dumps of a cluster's current status as * :ref:`observability_name ` - * and as an additional tag "upstream_cluster.name" while tracing. Note: access logging using - * this field is presently enabled with runtime feature - * `envoy.reloadable_features.use_observable_cluster_name`. Any ``:`` in the name will be - * converted to ``_`` when emitting statistics. This should not be confused with :ref:`Router - * Filter Header `. + * and as an additional tag "upstream_cluster.name" while tracing. Note: Any ``:`` in the name + * will be converted to ``_`` when emitting statistics. This should not be confused with + * :ref:`Router Filter Header `. */ 'alt_stat_name'?: (string); /** @@ -1487,7 +1603,7 @@ export interface Cluster { * :ref:`STATIC`, * :ref:`STRICT_DNS` * or :ref:`LOGICAL_DNS` clusters. - * This field supersedes the *hosts* field in the v2 API. + * This field supersedes the ``hosts`` field in the v2 API. * * .. attention:: * @@ -1528,9 +1644,8 @@ export interface Cluster { */ 'filters'?: (_envoy_config_cluster_v3_Filter)[]; /** - * New mechanism for LB policy configuration. Used only if the - * :ref:`lb_policy` field has the value - * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + * If this field is set and is supported by the client, it will supersede the value of + * :ref:`lb_policy`. */ 'load_balancing_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy | null); /** @@ -1552,7 +1667,7 @@ export interface Cluster { 'lrs_server'?: (_envoy_config_core_v3_ConfigSource | null); /** * Configuration to use different transport sockets for different endpoints. - * The entry of *envoy.transport_socket_match* in the + * The entry of ``envoy.transport_socket_match`` in the * :ref:`LbEndpoint.Metadata ` * is used to match against the transport sockets as they appear in the list. The first * :ref:`match ` is used. @@ -1572,16 +1687,16 @@ export interface Cluster { * transport_socket: * name: envoy.transport_sockets.raw_buffer * - * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* + * Connections to the endpoints whose metadata value under ``envoy.transport_socket_match`` * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. * * If a :ref:`socket match ` with empty match * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" * socket match in case above. * - * If an endpoint metadata's value under *envoy.transport_socket_match* does not match any - * *TransportSocketMatch*, socket configuration fallbacks to use the *tls_context* or - * *transport_socket* specified in this cluster. + * If an endpoint metadata's value under ``envoy.transport_socket_match`` does not match any + * ``TransportSocketMatch``, socket configuration fallbacks to use the ``tls_context`` or + * ``transport_socket`` specified in this cluster. * * This field allows gradual and flexible transport socket configuration changes. * @@ -1592,8 +1707,8 @@ export interface Cluster { * * Then the xDS server can configure the CDS to a client, Envoy A, to send mutual TLS * traffic for endpoints with "acceptMTLS": "true", by adding a corresponding - * *TransportSocketMatch* in this field. Other client Envoys receive CDS without - * *transport_socket_match* set, and still send plain text traffic to the same cluster. + * ``TransportSocketMatch`` in this field. Other client Envoys receive CDS without + * ``transport_socket_match`` set, and still send plain text traffic to the same cluster. * * This field can be used to specify custom transport socket configurations for health * checks by adding matching key/value pairs in a health check's @@ -1615,10 +1730,7 @@ export interface Cluster { 'dns_failure_refresh_rate'?: (_envoy_config_cluster_v3_Cluster_RefreshRate | null); /** * Always use TCP queries instead of UDP queries for DNS lookups. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple' API only uses UDP for DNS resolution. - * This field is deprecated in favor of *dns_resolution_config* + * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. */ 'use_tcp_for_dns_lookups'?: (boolean); @@ -1644,7 +1756,7 @@ export interface Cluster { * * .. attention:: * - * This field has been deprecated in favor of `timeout_budgets`, part of + * This field has been deprecated in favor of ``timeout_budgets``, part of * :ref:`track_cluster_stats `. */ 'track_timeout_budgets'?: (boolean); @@ -1655,7 +1767,7 @@ export interface Cluster { * TCP upstreams. * * For HTTP traffic, Envoy will generally take downstream HTTP and send it upstream as upstream - * HTTP, using the http connection pool and the codec from `http2_protocol_options` + * HTTP, using the http connection pool and the codec from ``http2_protocol_options`` * * For routes where CONNECT termination is configured, Envoy will take downstream CONNECT * requests and forward the CONNECT payload upstream over raw TCP using the tcp connection pool. @@ -1678,7 +1790,7 @@ export interface Cluster { */ 'preconnect_policy'?: (_envoy_config_cluster_v3_Cluster_PreconnectPolicy | null); /** - * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate + * If ``connection_pool_per_downstream_connection`` is true, the cluster will use a separate * connection pool for every downstream connection */ 'connection_pool_per_downstream_connection'?: (boolean); @@ -1688,15 +1800,15 @@ export interface Cluster { 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig | null); /** * DNS resolution configuration which includes the underlying dns resolver addresses and options. - * *dns_resolution_config* will be deprecated once - * :ref:'typed_dns_resolver_config ' - * is fully supported. + * This field is deprecated in favor of + * :ref:`typed_dns_resolver_config `. */ 'dns_resolution_config'?: (_envoy_config_core_v3_DnsResolutionConfig | null); /** * Optional configuration for having cluster readiness block on warm-up. Currently, only applicable for * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`. + * or :ref:`LOGICAL_DNS`, + * or :ref:`Redis Cluster`. * If true, cluster readiness blocks on warm-up. If false, the cluster will complete * initialization whether or not warm-up has completed. Defaults to true. */ @@ -1704,16 +1816,15 @@ export interface Cluster { /** * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, * or any other DNS resolver types and the related parameters. - * For example, an object of :ref:`DnsResolutionConfig ` - * can be packed into this *typed_dns_resolver_config*. This configuration will replace the - * :ref:'dns_resolution_config ' - * configuration eventually. - * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. - * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, - * this configuration is optional. - * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. - * When *typed_dns_resolver_config* is missing, the default behavior is in place. - * [#not-implemented-hide:] + * For example, an object of + * :ref:`CaresDnsResolverConfig ` + * can be packed into this ``typed_dns_resolver_config``. This configuration replaces the + * :ref:`dns_resolution_config ` + * configuration. + * During the transition period when both ``dns_resolution_config`` and ``typed_dns_resolver_config`` exists, + * when ``typed_dns_resolver_config`` is in place, Envoy will use it and ignore ``dns_resolution_config``. + * When ``typed_dns_resolver_config`` is missing, the default behavior is in place. + * [#extension-category: envoy.network.dns_resolver] */ 'typed_dns_resolver_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); /** @@ -1808,7 +1919,7 @@ export interface Cluster__Output { * set so that Envoy will assume that the upstream supports HTTP/2 when * making new HTTP connection pool connections. Currently, Envoy only * supports prior knowledge for upstream connections. Even if TLS is used - * with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 + * with ALPN, ``http2_protocol_options`` must be specified. As an aside this allows HTTP/2 * connections to happen over plain text. * This has been deprecated in favor of http2_protocol_options fields in the * :ref:`http_protocol_options ` @@ -1848,10 +1959,7 @@ export interface Cluster__Output { * :ref:`STRICT_DNS` * and :ref:`LOGICAL_DNS` * this setting is ignored. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple's API only allows overriding DNS resolvers via system settings. - * This field is deprecated in favor of *dns_resolution_config* + * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. */ 'dns_resolvers': (_envoy_config_core_v3_Address__Output)[]; @@ -1893,8 +2001,8 @@ export interface Cluster__Output { 'ring_hash_lb_config'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output | null); /** * Optional custom transport socket implementation to use for upstream connections. - * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and - * :ref:`UpstreamTlsContexts ` in the `typed_config`. + * To setup TLS, set a transport socket with name ``envoy.transport_sockets.tls`` and + * :ref:`UpstreamTlsContexts ` in the ``typed_config``. * If no transport socket configuration is specified, new connections * will be set up with plaintext. */ @@ -1904,7 +2012,7 @@ export interface Cluster__Output { * cluster. It can be used for stats, logging, and varying filter behavior. * Fields should use reverse DNS notation to denote which entity within Envoy * will need the information. For instance, if the metadata is intended for - * the Router filter, the filter name should be specified as *envoy.filters.http.router*. + * the Router filter, the filter name should be specified as ``envoy.filters.http.router``. */ 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** @@ -1925,11 +2033,9 @@ export interface Cluster__Output { * emitting stats for the cluster and access logging the cluster name. This will appear as * additional information in configuration dumps of a cluster's current status as * :ref:`observability_name ` - * and as an additional tag "upstream_cluster.name" while tracing. Note: access logging using - * this field is presently enabled with runtime feature - * `envoy.reloadable_features.use_observable_cluster_name`. Any ``:`` in the name will be - * converted to ``_`` when emitting statistics. This should not be confused with :ref:`Router - * Filter Header `. + * and as an additional tag "upstream_cluster.name" while tracing. Note: Any ``:`` in the name + * will be converted to ``_`` when emitting statistics. This should not be confused with + * :ref:`Router Filter Header `. */ 'alt_stat_name': (string); /** @@ -1976,7 +2082,7 @@ export interface Cluster__Output { * :ref:`STATIC`, * :ref:`STRICT_DNS` * or :ref:`LOGICAL_DNS` clusters. - * This field supersedes the *hosts* field in the v2 API. + * This field supersedes the ``hosts`` field in the v2 API. * * .. attention:: * @@ -2017,9 +2123,8 @@ export interface Cluster__Output { */ 'filters': (_envoy_config_cluster_v3_Filter__Output)[]; /** - * New mechanism for LB policy configuration. Used only if the - * :ref:`lb_policy` field has the value - * :ref:`LOAD_BALANCING_POLICY_CONFIG`. + * If this field is set and is supported by the client, it will supersede the value of + * :ref:`lb_policy`. */ 'load_balancing_policy': (_envoy_config_cluster_v3_LoadBalancingPolicy__Output | null); /** @@ -2041,7 +2146,7 @@ export interface Cluster__Output { 'lrs_server': (_envoy_config_core_v3_ConfigSource__Output | null); /** * Configuration to use different transport sockets for different endpoints. - * The entry of *envoy.transport_socket_match* in the + * The entry of ``envoy.transport_socket_match`` in the * :ref:`LbEndpoint.Metadata ` * is used to match against the transport sockets as they appear in the list. The first * :ref:`match ` is used. @@ -2061,16 +2166,16 @@ export interface Cluster__Output { * transport_socket: * name: envoy.transport_sockets.raw_buffer * - * Connections to the endpoints whose metadata value under *envoy.transport_socket_match* + * Connections to the endpoints whose metadata value under ``envoy.transport_socket_match`` * having "acceptMTLS"/"true" key/value pair use the "enableMTLS" socket configuration. * * If a :ref:`socket match ` with empty match * criteria is provided, that always match any endpoint. For example, the "defaultToPlaintext" * socket match in case above. * - * If an endpoint metadata's value under *envoy.transport_socket_match* does not match any - * *TransportSocketMatch*, socket configuration fallbacks to use the *tls_context* or - * *transport_socket* specified in this cluster. + * If an endpoint metadata's value under ``envoy.transport_socket_match`` does not match any + * ``TransportSocketMatch``, socket configuration fallbacks to use the ``tls_context`` or + * ``transport_socket`` specified in this cluster. * * This field allows gradual and flexible transport socket configuration changes. * @@ -2081,8 +2186,8 @@ export interface Cluster__Output { * * Then the xDS server can configure the CDS to a client, Envoy A, to send mutual TLS * traffic for endpoints with "acceptMTLS": "true", by adding a corresponding - * *TransportSocketMatch* in this field. Other client Envoys receive CDS without - * *transport_socket_match* set, and still send plain text traffic to the same cluster. + * ``TransportSocketMatch`` in this field. Other client Envoys receive CDS without + * ``transport_socket_match`` set, and still send plain text traffic to the same cluster. * * This field can be used to specify custom transport socket configurations for health * checks by adding matching key/value pairs in a health check's @@ -2104,10 +2209,7 @@ export interface Cluster__Output { 'dns_failure_refresh_rate': (_envoy_config_cluster_v3_Cluster_RefreshRate__Output | null); /** * Always use TCP queries instead of UDP queries for DNS lookups. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple' API only uses UDP for DNS resolution. - * This field is deprecated in favor of *dns_resolution_config* + * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. */ 'use_tcp_for_dns_lookups': (boolean); @@ -2133,7 +2235,7 @@ export interface Cluster__Output { * * .. attention:: * - * This field has been deprecated in favor of `timeout_budgets`, part of + * This field has been deprecated in favor of ``timeout_budgets``, part of * :ref:`track_cluster_stats `. */ 'track_timeout_budgets': (boolean); @@ -2144,7 +2246,7 @@ export interface Cluster__Output { * TCP upstreams. * * For HTTP traffic, Envoy will generally take downstream HTTP and send it upstream as upstream - * HTTP, using the http connection pool and the codec from `http2_protocol_options` + * HTTP, using the http connection pool and the codec from ``http2_protocol_options`` * * For routes where CONNECT termination is configured, Envoy will take downstream CONNECT * requests and forward the CONNECT payload upstream over raw TCP using the tcp connection pool. @@ -2167,7 +2269,7 @@ export interface Cluster__Output { */ 'preconnect_policy': (_envoy_config_cluster_v3_Cluster_PreconnectPolicy__Output | null); /** - * If `connection_pool_per_downstream_connection` is true, the cluster will use a separate + * If ``connection_pool_per_downstream_connection`` is true, the cluster will use a separate * connection pool for every downstream connection */ 'connection_pool_per_downstream_connection': (boolean); @@ -2177,15 +2279,15 @@ export interface Cluster__Output { 'maglev_lb_config'?: (_envoy_config_cluster_v3_Cluster_MaglevLbConfig__Output | null); /** * DNS resolution configuration which includes the underlying dns resolver addresses and options. - * *dns_resolution_config* will be deprecated once - * :ref:'typed_dns_resolver_config ' - * is fully supported. + * This field is deprecated in favor of + * :ref:`typed_dns_resolver_config `. */ 'dns_resolution_config': (_envoy_config_core_v3_DnsResolutionConfig__Output | null); /** * Optional configuration for having cluster readiness block on warm-up. Currently, only applicable for * :ref:`STRICT_DNS`, - * or :ref:`LOGICAL_DNS`. + * or :ref:`LOGICAL_DNS`, + * or :ref:`Redis Cluster`. * If true, cluster readiness blocks on warm-up. If false, the cluster will complete * initialization whether or not warm-up has completed. Defaults to true. */ @@ -2193,16 +2295,15 @@ export interface Cluster__Output { /** * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, * or any other DNS resolver types and the related parameters. - * For example, an object of :ref:`DnsResolutionConfig ` - * can be packed into this *typed_dns_resolver_config*. This configuration will replace the - * :ref:'dns_resolution_config ' - * configuration eventually. - * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. - * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, - * this configuration is optional. - * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. - * When *typed_dns_resolver_config* is missing, the default behavior is in place. - * [#not-implemented-hide:] + * For example, an object of + * :ref:`CaresDnsResolverConfig ` + * can be packed into this ``typed_dns_resolver_config``. This configuration replaces the + * :ref:`dns_resolution_config ` + * configuration. + * During the transition period when both ``dns_resolution_config`` and ``typed_dns_resolver_config`` exists, + * when ``typed_dns_resolver_config`` is in place, Envoy will use it and ignore ``dns_resolution_config``. + * When ``typed_dns_resolver_config`` is missing, the default behavior is in place. + * [#extension-category: envoy.network.dns_resolver] */ 'typed_dns_resolver_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts index 8b1394d4b..a028c8491 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/ClusterCollection.ts @@ -3,7 +3,7 @@ import type { CollectionEntry as _xds_core_v3_CollectionEntry, CollectionEntry__Output as _xds_core_v3_CollectionEntry__Output } from '../../../../xds/core/v3/CollectionEntry'; /** - * Cluster list collections. Entries are *Cluster* resources or references. + * Cluster list collections. Entries are ``Cluster`` resources or references. * [#not-implemented-hide:] */ export interface ClusterCollection { @@ -11,7 +11,7 @@ export interface ClusterCollection { } /** - * Cluster list collections. Entries are *Cluster* resources or references. + * Cluster list collections. Entries are ``Cluster`` resources or references. * [#not-implemented-hide:] */ export interface ClusterCollection__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts index 9d9031b68..cdcfeae89 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Filter.ts @@ -4,28 +4,28 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ export interface Filter { /** - * The name of the filter to instantiate. The name must match a - * supported upstream filter. Note that Envoy's :ref:`downstream network - * filters ` are not valid upstream filters. + * The name of the filter configuration. */ 'name'?: (string); /** * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. + * Note that Envoy's :ref:`downstream network + * filters ` are not valid upstream filters. */ 'typed_config'?: (_google_protobuf_Any | null); } export interface Filter__Output { /** - * The name of the filter to instantiate. The name must match a - * supported upstream filter. Note that Envoy's :ref:`downstream network - * filters ` are not valid upstream filters. + * The name of the filter configuration. */ 'name': (string); /** * Filter specific configuration which depends on the filter being * instantiated. See the supported filters for further documentation. + * Note that Envoy's :ref:`downstream network + * filters ` are not valid upstream filters. */ 'typed_config': (_google_protobuf_Any__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts index 78d4a6bfd..4e4efbe94 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/LoadBalancingPolicy.ts @@ -3,10 +3,16 @@ import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy { + /** + * [#extension-category: envoy.load_balancing_policies] + */ 'typed_extension_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); } export interface _envoy_config_cluster_v3_LoadBalancingPolicy_Policy__Output { + /** + * [#extension-category: envoy.load_balancing_policies] + */ 'typed_extension_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts index 789004ad4..47dfc1877 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/OutlierDetection.ts @@ -6,13 +6,13 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google /** * See the :ref:`architecture overview ` for * more information on outlier detection. - * [#next-free-field: 22] + * [#next-free-field: 23] */ export interface OutlierDetection { /** - * The number of consecutive 5xx responses or local origin errors that are mapped - * to 5xx error codes before a consecutive 5xx ejection - * occurs. Defaults to 5. + * The number of consecutive server-side error responses (for HTTP traffic, + * 5xx responses; for TCP traffic, connection failures; for Redis, failure to + * respond PONG; etc.) before a consecutive 5xx ejection occurs. Defaults to 5. */ 'consecutive_5xx'?: (_google_protobuf_UInt32Value | null); /** @@ -156,18 +156,25 @@ export interface OutlierDetection { * :ref:`base_ejection_time` value is applied, whatever is larger. */ 'max_ejection_time'?: (_google_protobuf_Duration | null); + /** + * The maximum amount of jitter to add to the ejection time, in order to prevent + * a 'thundering herd' effect where all proxies try to reconnect to host at the same time. + * See :ref:`max_ejection_time_jitter` + * Defaults to 0s. + */ + 'max_ejection_time_jitter'?: (_google_protobuf_Duration | null); } /** * See the :ref:`architecture overview ` for * more information on outlier detection. - * [#next-free-field: 22] + * [#next-free-field: 23] */ export interface OutlierDetection__Output { /** - * The number of consecutive 5xx responses or local origin errors that are mapped - * to 5xx error codes before a consecutive 5xx ejection - * occurs. Defaults to 5. + * The number of consecutive server-side error responses (for HTTP traffic, + * 5xx responses; for TCP traffic, connection failures; for Redis, failure to + * respond PONG; etc.) before a consecutive 5xx ejection occurs. Defaults to 5. */ 'consecutive_5xx': (_google_protobuf_UInt32Value__Output | null); /** @@ -311,4 +318,11 @@ export interface OutlierDetection__Output { * :ref:`base_ejection_time` value is applied, whatever is larger. */ 'max_ejection_time': (_google_protobuf_Duration__Output | null); + /** + * The maximum amount of jitter to add to the ejection time, in order to prevent + * a 'thundering herd' effect where all proxies try to reconnect to host at the same time. + * See :ref:`max_ejection_time_jitter` + * Defaults to 0s. + */ + 'max_ejection_time_jitter': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts index 483d1072d..cda367641 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamConnectionOptions.ts @@ -7,6 +7,12 @@ export interface UpstreamConnectionOptions { * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. */ 'tcp_keepalive'?: (_envoy_config_core_v3_TcpKeepalive | null); + /** + * If enabled, associates the interface name of the local address with the upstream connection. + * This can be used by extensions during processing of requests. The association mechanism is + * implementation specific. Defaults to false due to performance concerns. + */ + 'set_local_interface_name_on_upstream_connections'?: (boolean); } export interface UpstreamConnectionOptions__Output { @@ -14,4 +20,10 @@ export interface UpstreamConnectionOptions__Output { * If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. */ 'tcp_keepalive': (_envoy_config_core_v3_TcpKeepalive__Output | null); + /** + * If enabled, associates the interface name of the local address with the upstream connection. + * This can be used by extensions during processing of requests. The association mechanism is + * implementation specific. Defaults to false due to performance concerns. + */ + 'set_local_interface_name_on_upstream_connections': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts index 32c483344..5e29cdbf4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Address.ts @@ -13,7 +13,8 @@ export interface Address { 'socket_address'?: (_envoy_config_core_v3_SocketAddress | null); 'pipe'?: (_envoy_config_core_v3_Pipe | null); /** - * [#not-implemented-hide:] + * Specifies a user-space address handled by :ref:`internal listeners + * `. */ 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress | null); 'address'?: "socket_address"|"pipe"|"envoy_internal_address"; @@ -28,7 +29,8 @@ export interface Address__Output { 'socket_address'?: (_envoy_config_core_v3_SocketAddress__Output | null); 'pipe'?: (_envoy_config_core_v3_Pipe__Output | null); /** - * [#not-implemented-hide:] + * Specifies a user-space address handled by :ref:`internal listeners + * `. */ 'envoy_internal_address'?: (_envoy_config_core_v3_EnvoyInternalAddress__Output | null); 'address': "socket_address"|"pipe"|"envoy_internal_address"; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts index 6fb167174..ed3027a0c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/AlternateProtocolsCacheOptions.ts @@ -3,11 +3,56 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +/** + * Allows pre-populating the cache with HTTP/3 alternate protocols entries with a 7 day lifetime. + * This will cause Envoy to attempt HTTP/3 to those upstreams, even if the upstreams have not + * advertised HTTP/3 support. These entries will be overwritten by alt-svc + * response headers or cached values. + * As with regular cached entries, if the origin response would result in clearing an existing + * alternate protocol cache entry, pre-populated entries will also be cleared. + * Adding a cache entry with hostname=foo.com port=123 is the equivalent of getting + * response headers + * alt-svc: h3=:"123"; ma=86400" in a response to a request to foo.com:123 + */ +export interface _envoy_config_core_v3_AlternateProtocolsCacheOptions_AlternateProtocolsCacheEntry { + /** + * The host name for the alternate protocol entry. + */ + 'hostname'?: (string); + /** + * The port for the alternate protocol entry. + */ + 'port'?: (number); +} + +/** + * Allows pre-populating the cache with HTTP/3 alternate protocols entries with a 7 day lifetime. + * This will cause Envoy to attempt HTTP/3 to those upstreams, even if the upstreams have not + * advertised HTTP/3 support. These entries will be overwritten by alt-svc + * response headers or cached values. + * As with regular cached entries, if the origin response would result in clearing an existing + * alternate protocol cache entry, pre-populated entries will also be cleared. + * Adding a cache entry with hostname=foo.com port=123 is the equivalent of getting + * response headers + * alt-svc: h3=:"123"; ma=86400" in a response to a request to foo.com:123 + */ +export interface _envoy_config_core_v3_AlternateProtocolsCacheOptions_AlternateProtocolsCacheEntry__Output { + /** + * The host name for the alternate protocol entry. + */ + 'hostname': (string); + /** + * The port for the alternate protocol entry. + */ + 'port': (number); +} + /** * Configures the alternate protocols cache which tracks alternate protocols that can be used to * make an HTTP connection to an origin server. See https://tools.ietf.org/html/rfc7838 for * HTTP Alternative Services and https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 * for the "HTTPS" DNS resource record. + * [#next-free-field: 6] */ export interface AlternateProtocolsCacheOptions { /** @@ -33,8 +78,25 @@ export interface AlternateProtocolsCacheOptions { * :ref:`key value store ` to flush * alternate protocols entries to disk. * This function is currently only supported if concurrency is 1 + * Cached entries will take precedence over pre-populated entries below. */ 'key_value_store_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Allows pre-populating the cache with entries, as described above. + */ + 'prepopulated_entries'?: (_envoy_config_core_v3_AlternateProtocolsCacheOptions_AlternateProtocolsCacheEntry)[]; + /** + * Optional list of hostnames suffixes for which Alt-Svc entries can be shared. For example, if + * this list contained the value ``.c.example.com``, then an Alt-Svc entry for ``foo.c.example.com`` + * could be shared with ``bar.c.example.com`` but would not be shared with ``baz.example.com``. On + * the other hand, if the list contained the value ``.example.com`` then all three hosts could share + * Alt-Svc entries. Each entry must start with ``.``. If a hostname matches multiple suffixes, the + * first listed suffix will be used. + * + * Since lookup in this list is O(n), it is recommended that the number of suffixes be limited. + * [#not-implemented-hide:] + */ + 'canonical_suffixes'?: (string)[]; } /** @@ -42,6 +104,7 @@ export interface AlternateProtocolsCacheOptions { * make an HTTP connection to an origin server. See https://tools.ietf.org/html/rfc7838 for * HTTP Alternative Services and https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-04 * for the "HTTPS" DNS resource record. + * [#next-free-field: 6] */ export interface AlternateProtocolsCacheOptions__Output { /** @@ -67,6 +130,23 @@ export interface AlternateProtocolsCacheOptions__Output { * :ref:`key value store ` to flush * alternate protocols entries to disk. * This function is currently only supported if concurrency is 1 + * Cached entries will take precedence over pre-populated entries below. */ 'key_value_store_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Allows pre-populating the cache with entries, as described above. + */ + 'prepopulated_entries': (_envoy_config_core_v3_AlternateProtocolsCacheOptions_AlternateProtocolsCacheEntry__Output)[]; + /** + * Optional list of hostnames suffixes for which Alt-Svc entries can be shared. For example, if + * this list contained the value ``.c.example.com``, then an Alt-Svc entry for ``foo.c.example.com`` + * could be shared with ``bar.c.example.com`` but would not be shared with ``baz.example.com``. On + * the other hand, if the list contained the value ``.example.com`` then all three hosts could share + * Alt-Svc entries. Each entry must start with ``.``. If a hostname matches multiple suffixes, the + * first listed suffix will be used. + * + * Since lookup in this list is O(n), it is recommended that the number of suffixes be limited. + * [#not-implemented-hide:] + */ + 'canonical_suffixes': (string)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts index b303a08d8..691ab93ba 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts @@ -4,6 +4,7 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google import type { GrpcService as _envoy_config_core_v3_GrpcService, GrpcService__Output as _envoy_config_core_v3_GrpcService__Output } from '../../../../envoy/config/core/v3/GrpcService'; import type { RateLimitSettings as _envoy_config_core_v3_RateLimitSettings, RateLimitSettings__Output as _envoy_config_core_v3_RateLimitSettings__Output } from '../../../../envoy/config/core/v3/RateLimitSettings'; import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; // Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto @@ -49,7 +50,7 @@ export enum _envoy_config_core_v3_ApiConfigSource_ApiType { /** * API configuration source. This identifies the API type and cluster that Envoy * will use to fetch an xDS API. - * [#next-free-field: 9] + * [#next-free-field: 10] */ export interface ApiConfigSource { /** @@ -94,12 +95,23 @@ export interface ApiConfigSource { * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); + /** + * A list of config validators that will be executed when a new update is + * received from the ApiConfigSource. Note that each validator handles a + * specific xDS service type, and only the validators corresponding to the + * type url (in ``:ref: DiscoveryResponse`` or ``:ref: DeltaDiscoveryResponse``) + * will be invoked. + * If the validator returns false or throws an exception, the config will be rejected by + * the client, and a NACK will be sent. + * [#extension-category: envoy.config.validators] + */ + 'config_validators'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; } /** * API configuration source. This identifies the API type and cluster that Envoy * will use to fetch an xDS API. - * [#next-free-field: 9] + * [#next-free-field: 10] */ export interface ApiConfigSource__Output { /** @@ -144,4 +156,15 @@ export interface ApiConfigSource__Output { * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ 'transport_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); + /** + * A list of config validators that will be executed when a new update is + * received from the ApiConfigSource. Note that each validator handles a + * specific xDS service type, and only the validators corresponding to the + * type url (in ``:ref: DiscoveryResponse`` or ``:ref: DeltaDiscoveryResponse``) + * will be invoked. + * If the validator returns false or throws an exception, the config will be rejected by + * the client, and a NACK will be sent. + * [#extension-category: envoy.config.validators] + */ + 'config_validators': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts index d3b2cc584..733c609b1 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts @@ -3,18 +3,22 @@ import type { SocketAddress as _envoy_config_core_v3_SocketAddress, SocketAddress__Output as _envoy_config_core_v3_SocketAddress__Output } from '../../../../envoy/config/core/v3/SocketAddress'; import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; +import type { ExtraSourceAddress as _envoy_config_core_v3_ExtraSourceAddress, ExtraSourceAddress__Output as _envoy_config_core_v3_ExtraSourceAddress__Output } from '../../../../envoy/config/core/v3/ExtraSourceAddress'; +/** + * [#next-free-field: 6] + */ export interface BindConfig { /** * The address to bind to when creating a socket. */ 'source_address'?: (_envoy_config_core_v3_SocketAddress | null); /** - * Whether to set the *IP_FREEBIND* option when creating the socket. When this + * Whether to set the ``IP_FREEBIND`` option when creating the socket. When this * flag is set to true, allows the :ref:`source_address - * ` to be an IP address + * ` to be an IP address * that is not configured on the system running Envoy. When this flag is set - * to false, the option *IP_FREEBIND* is disabled on the socket. When this + * to false, the option ``IP_FREEBIND`` is disabled on the socket. When this * flag is not set (default), the socket is not modified, i.e. the option is * neither enabled nor disabled. */ @@ -24,19 +28,38 @@ export interface BindConfig { * precompiled binaries. */ 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; + /** + * Deprecated by + * :ref:`extra_source_addresses ` + */ + 'additional_source_addresses'?: (_envoy_config_core_v3_SocketAddress)[]; + /** + * Extra source addresses appended to the address specified in the `source_address` + * field. This enables to specify multiple source addresses. Currently, only one extra + * address can be supported, and the extra address should have a different IP version + * with the address in the `source_address` field. The address which has the same IP + * version with the target host's address IP version will be used as bind address. If more + * than one extra address specified, only the first address matched IP version will be + * returned. If there is no same IP version address found, the address in the `source_address` + * will be returned. + */ + 'extra_source_addresses'?: (_envoy_config_core_v3_ExtraSourceAddress)[]; } +/** + * [#next-free-field: 6] + */ export interface BindConfig__Output { /** * The address to bind to when creating a socket. */ 'source_address': (_envoy_config_core_v3_SocketAddress__Output | null); /** - * Whether to set the *IP_FREEBIND* option when creating the socket. When this + * Whether to set the ``IP_FREEBIND`` option when creating the socket. When this * flag is set to true, allows the :ref:`source_address - * ` to be an IP address + * ` to be an IP address * that is not configured on the system running Envoy. When this flag is set - * to false, the option *IP_FREEBIND* is disabled on the socket. When this + * to false, the option ``IP_FREEBIND`` is disabled on the socket. When this * flag is not set (default), the socket is not modified, i.e. the option is * neither enabled nor disabled. */ @@ -46,4 +69,20 @@ export interface BindConfig__Output { * precompiled binaries. */ 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; + /** + * Deprecated by + * :ref:`extra_source_addresses ` + */ + 'additional_source_addresses': (_envoy_config_core_v3_SocketAddress__Output)[]; + /** + * Extra source addresses appended to the address specified in the `source_address` + * field. This enables to specify multiple source addresses. Currently, only one extra + * address can be supported, and the extra address should have a different IP version + * with the address in the `source_address` field. The address which has the same IP + * version with the target host's address IP version will be used as bind address. If more + * than one extra address specified, only the first address matched IP version will be + * returned. If there is no same IP version address found, the address in the `source_address` + * will be returned. + */ + 'extra_source_addresses': (_envoy_config_core_v3_ExtraSourceAddress__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts index a39c0f077..5438b6e7d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts @@ -6,6 +6,7 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google import type { SelfConfigSource as _envoy_config_core_v3_SelfConfigSource, SelfConfigSource__Output as _envoy_config_core_v3_SelfConfigSource__Output } from '../../../../envoy/config/core/v3/SelfConfigSource'; import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; import type { Authority as _xds_core_v3_Authority, Authority__Output as _xds_core_v3_Authority__Output } from '../../../../xds/core/v3/Authority'; +import type { PathConfigSource as _envoy_config_core_v3_PathConfigSource, PathConfigSource__Output as _envoy_config_core_v3_PathConfigSource__Output } from '../../../../envoy/config/core/v3/PathConfigSource'; /** * Configuration for :ref:`listeners `, :ref:`clusters @@ -14,23 +15,11 @@ import type { Authority as _xds_core_v3_Authority, Authority__Output as _xds_cor * ` etc. may either be sourced from the * filesystem or from an xDS API source. Filesystem configs are watched with * inotify for updates. - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface ConfigSource { /** - * Path on the filesystem to source and watch for configuration updates. - * When sourcing configuration for :ref:`secret `, - * the certificate and key files are also watched for updates. - * - * .. note:: - * - * The path to the source must exist at config load time. - * - * .. note:: - * - * Envoy will only watch the file path for *moves.* This is because in general only moves - * are atomic. The same method of swapping files as is demonstrated in the - * :ref:`runtime documentation ` can be used here also. + * Deprecated in favor of ``path_config_source``. Use that field instead. */ 'path'?: (string); /** @@ -74,12 +63,16 @@ export interface ConfigSource { 'resource_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); /** * Authorities that this config source may be used for. An authority specified in a xdstp:// URL - * is resolved to a *ConfigSource* prior to configuration fetch. This field provides the + * is resolved to a ``ConfigSource`` prior to configuration fetch. This field provides the * association between authority name and configuration source. * [#not-implemented-hide:] */ 'authorities'?: (_xds_core_v3_Authority)[]; - 'config_source_specifier'?: "path"|"api_config_source"|"ads"|"self"; + /** + * Local filesystem path configuration source. + */ + 'path_config_source'?: (_envoy_config_core_v3_PathConfigSource | null); + 'config_source_specifier'?: "path"|"path_config_source"|"api_config_source"|"ads"|"self"; } /** @@ -89,23 +82,11 @@ export interface ConfigSource { * ` etc. may either be sourced from the * filesystem or from an xDS API source. Filesystem configs are watched with * inotify for updates. - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface ConfigSource__Output { /** - * Path on the filesystem to source and watch for configuration updates. - * When sourcing configuration for :ref:`secret `, - * the certificate and key files are also watched for updates. - * - * .. note:: - * - * The path to the source must exist at config load time. - * - * .. note:: - * - * Envoy will only watch the file path for *moves.* This is because in general only moves - * are atomic. The same method of swapping files as is demonstrated in the - * :ref:`runtime documentation ` can be used here also. + * Deprecated in favor of ``path_config_source``. Use that field instead. */ 'path'?: (string); /** @@ -149,10 +130,14 @@ export interface ConfigSource__Output { 'resource_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); /** * Authorities that this config source may be used for. An authority specified in a xdstp:// URL - * is resolved to a *ConfigSource* prior to configuration fetch. This field provides the + * is resolved to a ``ConfigSource`` prior to configuration fetch. This field provides the * association between authority name and configuration source. * [#not-implemented-hide:] */ 'authorities': (_xds_core_v3_Authority__Output)[]; - 'config_source_specifier': "path"|"api_config_source"|"ads"|"self"; + /** + * Local filesystem path configuration source. + */ + 'path_config_source'?: (_envoy_config_core_v3_PathConfigSource__Output | null); + 'config_source_specifier': "path"|"path_config_source"|"api_config_source"|"ads"|"self"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts index cc29e084f..0774fb844 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DataSource.ts @@ -2,7 +2,7 @@ /** - * Data source consisting of either a file or an inline value. + * Data source consisting of a file, an inline value, or an environment variable. */ export interface DataSource { /** @@ -17,11 +17,15 @@ export interface DataSource { * String inlined in the configuration. */ 'inline_string'?: (string); - 'specifier'?: "filename"|"inline_bytes"|"inline_string"; + /** + * Environment variable data source. + */ + 'environment_variable'?: (string); + 'specifier'?: "filename"|"inline_bytes"|"inline_string"|"environment_variable"; } /** - * Data source consisting of either a file or an inline value. + * Data source consisting of a file, an inline value, or an environment variable. */ export interface DataSource__Output { /** @@ -36,5 +40,9 @@ export interface DataSource__Output { * String inlined in the configuration. */ 'inline_string'?: (string); - 'specifier': "filename"|"inline_bytes"|"inline_string"; + /** + * Environment variable data source. + */ + 'environment_variable'?: (string); + 'specifier': "filename"|"inline_bytes"|"inline_string"|"environment_variable"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts index c87be12e2..cf9f7a455 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolutionConfig.ts @@ -11,9 +11,6 @@ export interface DnsResolutionConfig { * A list of dns resolver addresses. If specified, the DNS client library will perform resolution * via the underlying DNS resolvers. Otherwise, the default system resolvers * (e.g., /etc/resolv.conf) will be used. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple's API only allows overriding DNS resolvers via system settings. */ 'resolvers'?: (_envoy_config_core_v3_Address)[]; /** @@ -30,9 +27,6 @@ export interface DnsResolutionConfig__Output { * A list of dns resolver addresses. If specified, the DNS client library will perform resolution * via the underlying DNS resolvers. Otherwise, the default system resolvers * (e.g., /etc/resolv.conf) will be used. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple's API only allows overriding DNS resolvers via system settings. */ 'resolvers': (_envoy_config_core_v3_Address__Output)[]; /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts index 11b68b150..e3f83b72c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/DnsResolverOptions.ts @@ -7,9 +7,6 @@ export interface DnsResolverOptions { /** * Use TCP for all DNS queries instead of the default protocol UDP. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple's API only uses UDP for DNS resolution. */ 'use_tcp_for_dns_lookups'?: (boolean); /** @@ -24,9 +21,6 @@ export interface DnsResolverOptions { export interface DnsResolverOptions__Output { /** * Use TCP for all DNS queries instead of the default protocol UDP. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple's API only uses UDP for DNS resolution. */ 'use_tcp_for_dns_lookups': (boolean); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts index 936e433db..264e65a0a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/EnvoyInternalAddress.ts @@ -2,27 +2,39 @@ /** - * [#not-implemented-hide:] The address represents an envoy internal listener. - * TODO(lambdai): Make this address available for listener and endpoint. - * TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30. + * The address represents an envoy internal listener. + * [#comment: TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30.] */ export interface EnvoyInternalAddress { /** - * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + * Specifies the :ref:`name ` of the + * internal listener. */ 'server_listener_name'?: (string); + /** + * Specifies an endpoint identifier to distinguish between multiple endpoints for the same internal listener in a + * single upstream pool. Only used in the upstream addresses for tracking changes to individual endpoints. This, for + * example, may be set to the final destination IP for the target internal listener. + */ + 'endpoint_id'?: (string); 'address_name_specifier'?: "server_listener_name"; } /** - * [#not-implemented-hide:] The address represents an envoy internal listener. - * TODO(lambdai): Make this address available for listener and endpoint. - * TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30. + * The address represents an envoy internal listener. + * [#comment: TODO(asraa): When address available, remove workaround from test/server/server_fuzz_test.cc:30.] */ export interface EnvoyInternalAddress__Output { /** - * [#not-implemented-hide:] The :ref:`listener name ` of the destination internal listener. + * Specifies the :ref:`name ` of the + * internal listener. */ 'server_listener_name'?: (string); + /** + * Specifies an endpoint identifier to distinguish between multiple endpoints for the same internal listener in a + * single upstream pool. Only used in the upstream addresses for tracking changes to individual endpoints. This, for + * example, may be set to the final destination IP for the target internal listener. + */ + 'endpoint_id': (string); 'address_name_specifier': "server_listener_name"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts index 5f730bd22..b25c15cce 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts @@ -4,7 +4,7 @@ import type { BuildVersion as _envoy_config_core_v3_BuildVersion, BuildVersion__ /** * Version and identification for an Envoy extension. - * [#next-free-field: 6] + * [#next-free-field: 7] */ export interface Extension { /** @@ -36,11 +36,15 @@ export interface Extension { * Indicates that the extension is present but was disabled via dynamic configuration. */ 'disabled'?: (boolean); + /** + * Type URLs of extension configuration protos. + */ + 'type_urls'?: (string)[]; } /** * Version and identification for an Envoy extension. - * [#next-free-field: 6] + * [#next-free-field: 7] */ export interface Extension__Output { /** @@ -72,4 +76,8 @@ export interface Extension__Output { * Indicates that the extension is present but was disabled via dynamic configuration. */ 'disabled': (boolean); + /** + * Type URLs of extension configuration protos. + */ + 'type_urls': (string)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts index 805762ef5..6342f50fc 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtensionConfigSource.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/config/core/v3/extension.proto +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; @@ -21,7 +21,7 @@ export interface ExtensionConfigSource { /** * Optional default configuration to use as the initial configuration if * there is a failure to receive the initial extension configuration or if - * `apply_default_config_without_warming` flag is set. + * ``apply_default_config_without_warming`` flag is set. */ 'default_config'?: (_google_protobuf_Any | null); /** @@ -55,7 +55,7 @@ export interface ExtensionConfigSource__Output { /** * Optional default configuration to use as the initial configuration if * there is a failure to receive the initial extension configuration or if - * `apply_default_config_without_warming` flag is set. + * ``apply_default_config_without_warming`` flag is set. */ 'default_config': (_google_protobuf_Any__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtraSourceAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtraSourceAddress.ts new file mode 100644 index 000000000..78051fafb --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ExtraSourceAddress.ts @@ -0,0 +1,38 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/address.proto + +import type { SocketAddress as _envoy_config_core_v3_SocketAddress, SocketAddress__Output as _envoy_config_core_v3_SocketAddress__Output } from '../../../../envoy/config/core/v3/SocketAddress'; +import type { SocketOptionsOverride as _envoy_config_core_v3_SocketOptionsOverride, SocketOptionsOverride__Output as _envoy_config_core_v3_SocketOptionsOverride__Output } from '../../../../envoy/config/core/v3/SocketOptionsOverride'; + +export interface ExtraSourceAddress { + /** + * The additional address to bind. + */ + 'address'?: (_envoy_config_core_v3_SocketAddress | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. If specified, this will override the + * :ref:`socket_options ` + * in the BindConfig. If specified with no + * :ref:`socket_options ` + * or an empty list of :ref:`socket_options `, + * it means no socket option will apply. + */ + 'socket_options'?: (_envoy_config_core_v3_SocketOptionsOverride | null); +} + +export interface ExtraSourceAddress__Output { + /** + * The additional address to bind. + */ + 'address': (_envoy_config_core_v3_SocketAddress__Output | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. If specified, this will override the + * :ref:`socket_options ` + * in the BindConfig. If specified with no + * :ref:`socket_options ` + * or an empty list of :ref:`socket_options `, + * it means no socket option will apply. + */ + 'socket_options': (_envoy_config_core_v3_SocketOptionsOverride__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts index 0d81f7340..eaeeff52c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/GrpcService.ts @@ -2,6 +2,7 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; import type { HeaderValue as _envoy_config_core_v3_HeaderValue, HeaderValue__Output as _envoy_config_core_v3_HeaderValue__Output } from '../../../../envoy/config/core/v3/HeaderValue'; +import type { RetryPolicy as _envoy_config_core_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_core_v3_RetryPolicy__Output } from '../../../../envoy/config/core/v3/RetryPolicy'; import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../envoy/config/core/v3/DataSource'; @@ -153,10 +154,17 @@ export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc { */ 'cluster_name'?: (string); /** - * The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. + * The ``:authority`` header in the grpc request. If this field is not set, the authority header value will be ``cluster_name``. * Note that this authority does not override the SNI. The SNI is provided by the transport socket of the cluster. */ 'authority'?: (string); + /** + * Indicates the retry policy for re-establishing the gRPC stream + * This field is optional. If max interval is not provided, it will be set to ten times the provided base interval. + * Currently only supported for xDS gRPC streams. + * If not set, xDS gRPC streams default base interval:500ms, maximum interval:30s will be applied. + */ + 'retry_policy'?: (_envoy_config_core_v3_RetryPolicy | null); } export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc__Output { @@ -167,10 +175,17 @@ export interface _envoy_config_core_v3_GrpcService_EnvoyGrpc__Output { */ 'cluster_name': (string); /** - * The `:authority` header in the grpc request. If this field is not set, the authority header value will be `cluster_name`. + * The ``:authority`` header in the grpc request. If this field is not set, the authority header value will be ``cluster_name``. * Note that this authority does not override the SNI. The SNI is provided by the transport socket of the cluster. */ 'authority': (string); + /** + * Indicates the retry policy for re-establishing the gRPC stream + * This field is optional. If max interval is not provided, it will be set to ten times the provided base interval. + * Currently only supported for xDS gRPC streams. + * If not set, xDS gRPC streams default base interval:500ms, maximum interval:30s will be applied. + */ + 'retry_policy': (_envoy_config_core_v3_RetryPolicy__Output | null); } /** @@ -372,7 +387,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_St /** * URI of the token exchange service that handles token exchange requests. * [#comment:TODO(asraa): Add URI validation when implemented. Tracked by - * https://github.com/envoyproxy/protoc-gen-validate/issues/303] + * https://github.com/bufbuild/protoc-gen-validate/issues/303] */ 'token_exchange_service_uri'?: (string); /** @@ -426,7 +441,7 @@ export interface _envoy_config_core_v3_GrpcService_GoogleGrpc_CallCredentials_St /** * URI of the token exchange service that handles token exchange requests. * [#comment:TODO(asraa): Add URI validation when implemented. Tracked by - * https://github.com/envoyproxy/protoc-gen-validate/issues/303] + * https://github.com/bufbuild/protoc-gen-validate/issues/303] */ 'token_exchange_service_uri': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts index a9fb6c07a..1cac90213 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValue.ts @@ -14,7 +14,7 @@ export interface HeaderValue { * * The same :ref:`format specifier ` as used for * :ref:`HTTP access logging ` applies here, however - * unknown header values are replaced with the empty string instead of `-`. + * unknown header values are replaced with the empty string instead of ``-``. */ 'value'?: (string); } @@ -32,7 +32,7 @@ export interface HeaderValue__Output { * * The same :ref:`format specifier ` as used for * :ref:`HTTP access logging ` applies here, however - * unknown header values are replaced with the empty string instead of `-`. + * unknown header values are replaced with the empty string instead of ``-``. */ 'value': (string); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts index 7ba7e9d8d..efb656303 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts @@ -39,13 +39,27 @@ export interface HeaderValueOption { /** * Should the value be appended? If true (default), the value is appended to * existing values. Otherwise it replaces any existing values. + * This field is deprecated and please use + * :ref:`append_action ` as replacement. + * + * .. note:: + * The :ref:`external authorization service ` and + * :ref:`external processor service ` have + * default value (``false``) for this field. */ 'append'?: (_google_protobuf_BoolValue | null); /** - * [#not-implemented-hide:] Describes the action taken to append/overwrite the given value for an existing header - * or to only add this header if it's absent. Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD`. + * Describes the action taken to append/overwrite the given value for an existing header + * or to only add this header if it's absent. + * Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD + * `. */ 'append_action'?: (_envoy_config_core_v3_HeaderValueOption_HeaderAppendAction | keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); + /** + * Is the header value allowed to be empty? If false (default), custom headers with empty values are dropped, + * otherwise they are added. + */ + 'keep_empty_value'?: (boolean); } /** @@ -59,11 +73,25 @@ export interface HeaderValueOption__Output { /** * Should the value be appended? If true (default), the value is appended to * existing values. Otherwise it replaces any existing values. + * This field is deprecated and please use + * :ref:`append_action ` as replacement. + * + * .. note:: + * The :ref:`external authorization service ` and + * :ref:`external processor service ` have + * default value (``false``) for this field. */ 'append': (_google_protobuf_BoolValue__Output | null); /** - * [#not-implemented-hide:] Describes the action taken to append/overwrite the given value for an existing header - * or to only add this header if it's absent. Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD`. + * Describes the action taken to append/overwrite the given value for an existing header + * or to only add this header if it's absent. + * Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD + * `. */ 'append_action': (keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); + /** + * Is the header value allowed to be empty? If false (default), custom headers with empty values are dropped, + * otherwise they are added. + */ + 'keep_empty_value': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts index 8882de614..e0638df7d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts @@ -5,10 +5,13 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { EventServiceConfig as _envoy_config_core_v3_EventServiceConfig, EventServiceConfig__Output as _envoy_config_core_v3_EventServiceConfig__Output } from '../../../../envoy/config/core/v3/EventServiceConfig'; import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; import type { CodecClientType as _envoy_type_v3_CodecClientType } from '../../../../envoy/type/v3/CodecClientType'; import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; +import type { RequestMethod as _envoy_config_core_v3_RequestMethod } from '../../../../envoy/config/core/v3/RequestMethod'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { Long } from '@grpc/proto-loader'; @@ -68,6 +71,13 @@ export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck { * the :ref:`hostname ` field. */ 'authority'?: (string); + /** + * Specifies a list of key-value pairs that should be added to the metadata of each GRPC call + * that is sent to the health checked cluster. For more information, including details on header value syntax, + * see the documentation on :ref:`custom request headers + * `. + */ + 'initial_metadata'?: (_envoy_config_core_v3_HeaderValueOption)[]; } /** @@ -92,10 +102,17 @@ export interface _envoy_config_core_v3_HealthCheck_GrpcHealthCheck__Output { * the :ref:`hostname ` field. */ 'authority': (string); + /** + * Specifies a list of key-value pairs that should be added to the metadata of each GRPC call + * that is sent to the health checked cluster. For more information, including details on header value syntax, + * see the documentation on :ref:`custom request headers + * `. + */ + 'initial_metadata': (_envoy_config_core_v3_HeaderValueOption__Output)[]; } /** - * [#next-free-field: 13] + * [#next-free-field: 15] */ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { /** @@ -107,7 +124,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { 'host'?: (string); /** * Specifies the HTTP path that will be requested during health checking. For example - * * /healthcheck*. + * ``/healthcheck``. */ 'path'?: (string); /** @@ -115,9 +132,22 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { */ 'send'?: (_envoy_config_core_v3_HealthCheck_Payload | null); /** - * [#not-implemented-hide:] HTTP specific response. + * Specifies a list of HTTP expected responses to match in the first ``response_buffer_size`` bytes of the response body. + * If it is set, both the expected response check and status code determine the health check. + * When checking the response, “fuzzy” matching is performed such that each payload block must be found, + * and in the order specified, but not necessarily contiguous. + * + * .. note:: + * + * It is recommended to set ``response_buffer_size`` based on the total Payload size for efficiency. + * The default buffer size is 1024 bytes when it is not set. + */ + 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload)[]; + /** + * Specifies the size of response buffer in bytes that is used to Payload match. + * The default value is 1024. Setting to 0 implies that the Payload will be matched against the entire response. */ - 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload | null); + 'response_buffer_size'?: (_google_protobuf_UInt64Value | null); /** * Specifies a list of HTTP headers that should be added to each request that is sent to the * health checked cluster. For more information, including details on header value syntax, see @@ -161,10 +191,17 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { * ` for more information. */ 'service_name_matcher'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * HTTP Method that will be used for health checking, default is "GET". + * GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, PATCH methods are supported, but making request body is not supported. + * CONNECT method is disallowed because it is not appropriate for health check request. + * If a non-200 response is expected by the method, it needs to be set in :ref:`expected_statuses `. + */ + 'method'?: (_envoy_config_core_v3_RequestMethod | keyof typeof _envoy_config_core_v3_RequestMethod); } /** - * [#next-free-field: 13] + * [#next-free-field: 15] */ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { /** @@ -176,7 +213,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { 'host': (string); /** * Specifies the HTTP path that will be requested during health checking. For example - * * /healthcheck*. + * ``/healthcheck``. */ 'path': (string); /** @@ -184,9 +221,22 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { */ 'send': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); /** - * [#not-implemented-hide:] HTTP specific response. + * Specifies a list of HTTP expected responses to match in the first ``response_buffer_size`` bytes of the response body. + * If it is set, both the expected response check and status code determine the health check. + * When checking the response, “fuzzy” matching is performed such that each payload block must be found, + * and in the order specified, but not necessarily contiguous. + * + * .. note:: + * + * It is recommended to set ``response_buffer_size`` based on the total Payload size for efficiency. + * The default buffer size is 1024 bytes when it is not set. */ - 'receive': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); + 'receive': (_envoy_config_core_v3_HealthCheck_Payload__Output)[]; + /** + * Specifies the size of response buffer in bytes that is used to Payload match. + * The default value is 1024. Setting to 0 implies that the Payload will be matched against the entire response. + */ + 'response_buffer_size': (_google_protobuf_UInt64Value__Output | null); /** * Specifies a list of HTTP headers that should be added to each request that is sent to the * health checked cluster. For more information, including details on header value syntax, see @@ -230,6 +280,13 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { * ` for more information. */ 'service_name_matcher': (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * HTTP Method that will be used for health checking, default is "GET". + * GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, PATCH methods are supported, but making request body is not supported. + * CONNECT method is disallowed because it is not appropriate for health check request. + * If a non-200 response is expected by the method, it needs to be set in :ref:`expected_statuses `. + */ + 'method': (keyof typeof _envoy_config_core_v3_RequestMethod); } /** @@ -241,7 +298,7 @@ export interface _envoy_config_core_v3_HealthCheck_Payload { */ 'text'?: (string); /** - * [#not-implemented-hide:] Binary payload. + * Binary payload. */ 'binary'?: (Buffer | Uint8Array | string); 'payload'?: "text"|"binary"; @@ -256,7 +313,7 @@ export interface _envoy_config_core_v3_HealthCheck_Payload__Output { */ 'text'?: (string); /** - * [#not-implemented-hide:] Binary payload. + * Binary payload. */ 'binary'?: (Buffer); 'payload': "text"|"binary"; @@ -289,7 +346,7 @@ export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck { 'send'?: (_envoy_config_core_v3_HealthCheck_Payload | null); /** * When checking the response, “fuzzy” matching is performed such that each - * binary block must be found, and in the order specified, but not + * payload block must be found, and in the order specified, but not * necessarily contiguous. */ 'receive'?: (_envoy_config_core_v3_HealthCheck_Payload)[]; @@ -302,7 +359,7 @@ export interface _envoy_config_core_v3_HealthCheck_TcpHealthCheck__Output { 'send': (_envoy_config_core_v3_HealthCheck_Payload__Output | null); /** * When checking the response, “fuzzy” matching is performed such that each - * binary block must be found, and in the order specified, but not + * payload block must be found, and in the order specified, but not * necessarily contiguous. */ 'receive': (_envoy_config_core_v3_HealthCheck_Payload__Output)[]; @@ -341,7 +398,7 @@ export interface _envoy_config_core_v3_HealthCheck_TlsOptions__Output { } /** - * [#next-free-field: 25] + * [#next-free-field: 26] */ export interface HealthCheck { /** @@ -360,7 +417,7 @@ export interface HealthCheck { 'interval_jitter'?: (_google_protobuf_Duration | null); /** * The number of unhealthy health checks required before a host is marked - * unhealthy. Note that for *http* health checking if a host responds with a code not in + * unhealthy. Note that for ``http`` health checking if a host responds with a code not in * :ref:`expected_statuses ` * or :ref:`retriable_statuses `, * this threshold is ignored and the host is considered immediately unhealthy. @@ -433,14 +490,19 @@ export interface HealthCheck { */ 'healthy_edge_interval'?: (_google_protobuf_Duration | null); /** + * .. attention:: + * This field is deprecated in favor of the extension + * :ref:`event_logger ` and + * :ref:`event_log_path ` + * in the file sink extension. + * * Specifies the path to the :ref:`health check event log `. - * If empty, no event log will be written. */ 'event_log_path'?: (string); /** * An optional jitter amount as a percentage of interval_ms. If specified, - * during every interval Envoy will add interval_ms * - * interval_jitter_percent / 100 to the wait time. + * during every interval Envoy will add ``interval_ms`` * + * ``interval_jitter_percent`` / 100 to the wait time. * * If interval_jitter_ms and interval_jitter_percent are both set, both of * them will be used to increase the wait time. @@ -490,7 +552,7 @@ export interface HealthCheck { * name: envoy.transport_sockets.tls * config: { ... } # tls socket configuration * - * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the + * If this field is set, then for health checks it will supersede an entry of ``envoy.transport_socket`` in the * :ref:`LbEndpoint.Metadata `. * This allows using different transport socket capabilities for health checking versus proxying to the * endpoint. @@ -507,7 +569,7 @@ export interface HealthCheck { * (including new hosts) when the cluster has received no traffic. * * This is useful for when we want to send frequent health checks with - * `no_traffic_interval` but then revert to lower frequency `no_traffic_healthy_interval` once + * ``no_traffic_interval`` but then revert to lower frequency ``no_traffic_healthy_interval`` once * a host in the cluster is marked as healthy. * * Once a cluster has been used for traffic routing, Envoy will shift back to using the @@ -517,11 +579,16 @@ export interface HealthCheck { * no traffic interval and send that interval regardless of health state. */ 'no_traffic_healthy_interval'?: (_google_protobuf_Duration | null); + /** + * A list of event log sinks to process the health check event. + * [#extension-category: envoy.health_check.event_sinks] + */ + 'event_logger'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; 'health_checker'?: "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; } /** - * [#next-free-field: 25] + * [#next-free-field: 26] */ export interface HealthCheck__Output { /** @@ -540,7 +607,7 @@ export interface HealthCheck__Output { 'interval_jitter': (_google_protobuf_Duration__Output | null); /** * The number of unhealthy health checks required before a host is marked - * unhealthy. Note that for *http* health checking if a host responds with a code not in + * unhealthy. Note that for ``http`` health checking if a host responds with a code not in * :ref:`expected_statuses ` * or :ref:`retriable_statuses `, * this threshold is ignored and the host is considered immediately unhealthy. @@ -613,14 +680,19 @@ export interface HealthCheck__Output { */ 'healthy_edge_interval': (_google_protobuf_Duration__Output | null); /** + * .. attention:: + * This field is deprecated in favor of the extension + * :ref:`event_logger ` and + * :ref:`event_log_path ` + * in the file sink extension. + * * Specifies the path to the :ref:`health check event log `. - * If empty, no event log will be written. */ 'event_log_path': (string); /** * An optional jitter amount as a percentage of interval_ms. If specified, - * during every interval Envoy will add interval_ms * - * interval_jitter_percent / 100 to the wait time. + * during every interval Envoy will add ``interval_ms`` * + * ``interval_jitter_percent`` / 100 to the wait time. * * If interval_jitter_ms and interval_jitter_percent are both set, both of * them will be used to increase the wait time. @@ -670,7 +742,7 @@ export interface HealthCheck__Output { * name: envoy.transport_sockets.tls * config: { ... } # tls socket configuration * - * If this field is set, then for health checks it will supersede an entry of *envoy.transport_socket* in the + * If this field is set, then for health checks it will supersede an entry of ``envoy.transport_socket`` in the * :ref:`LbEndpoint.Metadata `. * This allows using different transport socket capabilities for health checking versus proxying to the * endpoint. @@ -687,7 +759,7 @@ export interface HealthCheck__Output { * (including new hosts) when the cluster has received no traffic. * * This is useful for when we want to send frequent health checks with - * `no_traffic_interval` but then revert to lower frequency `no_traffic_healthy_interval` once + * ``no_traffic_interval`` but then revert to lower frequency ``no_traffic_healthy_interval`` once * a host in the cluster is marked as healthy. * * Once a cluster has been used for traffic routing, Envoy will shift back to using the @@ -697,5 +769,10 @@ export interface HealthCheck__Output { * no traffic interval and send that interval regardless of health state. */ 'no_traffic_healthy_interval': (_google_protobuf_Duration__Output | null); + /** + * A list of event log sinks to process the health check event. + * [#extension-category: envoy.health_check.event_sinks] + */ + 'event_logger': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; 'health_checker': "http_health_check"|"tcp_health_check"|"grpc_health_check"|"custom_health_check"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts index 6ecbca272..7d3d76569 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts @@ -5,7 +5,7 @@ */ export enum HealthStatus { /** - * The health status is not known. This is interpreted by Envoy as *HEALTHY*. + * The health status is not known. This is interpreted by Envoy as ``HEALTHY``. */ UNKNOWN = 0, /** @@ -21,12 +21,12 @@ export enum HealthStatus { * ``_ * or * ``_. - * This is interpreted by Envoy as *UNHEALTHY*. + * This is interpreted by Envoy as ``UNHEALTHY``. */ DRAINING = 3, /** * Health check timed out. This is part of HDS and is interpreted by Envoy as - * *UNHEALTHY*. + * ``UNHEALTHY``. */ TIMEOUT = 4, /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts new file mode 100644 index 000000000..c518192d7 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts @@ -0,0 +1,17 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/health_check.proto + +import type { HealthStatus as _envoy_config_core_v3_HealthStatus } from '../../../../envoy/config/core/v3/HealthStatus'; + +export interface HealthStatusSet { + /** + * An order-independent set of health status. + */ + 'statuses'?: (_envoy_config_core_v3_HealthStatus | keyof typeof _envoy_config_core_v3_HealthStatus)[]; +} + +export interface HealthStatusSet__Output { + /** + * An order-independent set of health status. + */ + 'statuses': (keyof typeof _envoy_config_core_v3_HealthStatus)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts index 40b7408f6..d141a946c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http1ProtocolOptions.ts @@ -52,27 +52,27 @@ export interface _envoy_config_core_v3_Http1ProtocolOptions_HeaderKeyFormat_Prop } /** - * [#next-free-field: 8] + * [#next-free-field: 11] */ export interface Http1ProtocolOptions { /** * Handle HTTP requests with absolute URLs in the requests. These requests * are generally sent by clients to forward/explicit proxies. This allows clients to configure * envoy as their HTTP proxy. In Unix, for example, this is typically done by setting the - * *http_proxy* environment variable. + * ``http_proxy`` environment variable. */ 'allow_absolute_url'?: (_google_protobuf_BoolValue | null); /** * Handle incoming HTTP/1.0 and HTTP 0.9 requests. * This is off by default, and not fully standards compliant. There is support for pre-HTTP/1.1 * style connect logic, dechunking, and handling lack of client host iff - * *default_host_for_http_10* is configured. + * ``default_host_for_http_10`` is configured. */ 'accept_http_10'?: (boolean); /** - * A default host for HTTP/1.0 requests. This is highly suggested if *accept_http_10* is true as + * A default host for HTTP/1.0 requests. This is highly suggested if ``accept_http_10`` is true as * Envoy does not otherwise support HTTP/1.0 without a Host header. - * This is a no-op if *accept_http_10* is not true. + * This is a no-op if ``accept_http_10`` is not true. */ 'default_host_for_http_10'?: (string); /** @@ -93,14 +93,17 @@ export interface Http1ProtocolOptions { */ 'enable_trailers'?: (boolean); /** - * Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding` + * Allows Envoy to process requests/responses with both ``Content-Length`` and ``Transfer-Encoding`` * headers set. By default such messages are rejected, but if option is enabled - Envoy will * remove Content-Length header and process message. - * See `RFC7230, sec. 3.3.3 ` for details. + * See `RFC7230, sec. 3.3.3 `_ for details. * * .. attention:: * Enabling this option might lead to request smuggling vulnerability, especially if traffic * is proxied via multiple layers of proxies. + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'allow_chunked_length'?: (boolean); /** @@ -112,30 +115,60 @@ export interface Http1ProtocolOptions { * `. */ 'override_stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); + /** + * Allows sending fully qualified URLs when proxying the first line of the + * response. By default, Envoy will only send the path components in the first line. + * If this is true, Envoy will create a fully qualified URI composing scheme + * (inferred if not present), host (from the host/:authority header) and path + * (from first line or :path header). + */ + 'send_fully_qualified_url'?: (boolean); + /** + * [#not-implemented-hide:] Hiding so that field can be removed after BalsaParser is rolled out. + * If set, force HTTP/1 parser: BalsaParser if true, http-parser if false. + * If unset, HTTP/1 parser is selected based on + * envoy.reloadable_features.http1_use_balsa_parser. + * See issue #21245. + */ + 'use_balsa_parser'?: (_google_protobuf_BoolValue | null); + /** + * [#not-implemented-hide:] Hiding so that field can be removed. + * If true, and BalsaParser is used (either `use_balsa_parser` above is true, + * or `envoy.reloadable_features.http1_use_balsa_parser` is true and + * `use_balsa_parser` is unset), then every non-empty method with only valid + * characters is accepted. Otherwise, methods not on the hard-coded list are + * rejected. + * Once UHV is enabled, this field should be removed, and BalsaParser should + * allow any method. UHV validates the method, rejecting empty string or + * invalid characters, and provides :ref:`restrict_http_methods + * ` + * to reject custom methods. + */ + 'allow_custom_methods'?: (boolean); } /** - * [#next-free-field: 8] + * [#next-free-field: 11] */ export interface Http1ProtocolOptions__Output { /** * Handle HTTP requests with absolute URLs in the requests. These requests * are generally sent by clients to forward/explicit proxies. This allows clients to configure * envoy as their HTTP proxy. In Unix, for example, this is typically done by setting the - * *http_proxy* environment variable. + * ``http_proxy`` environment variable. */ 'allow_absolute_url': (_google_protobuf_BoolValue__Output | null); /** * Handle incoming HTTP/1.0 and HTTP 0.9 requests. * This is off by default, and not fully standards compliant. There is support for pre-HTTP/1.1 * style connect logic, dechunking, and handling lack of client host iff - * *default_host_for_http_10* is configured. + * ``default_host_for_http_10`` is configured. */ 'accept_http_10': (boolean); /** - * A default host for HTTP/1.0 requests. This is highly suggested if *accept_http_10* is true as + * A default host for HTTP/1.0 requests. This is highly suggested if ``accept_http_10`` is true as * Envoy does not otherwise support HTTP/1.0 without a Host header. - * This is a no-op if *accept_http_10* is not true. + * This is a no-op if ``accept_http_10`` is not true. */ 'default_host_for_http_10': (string); /** @@ -156,14 +189,17 @@ export interface Http1ProtocolOptions__Output { */ 'enable_trailers': (boolean); /** - * Allows Envoy to process requests/responses with both `Content-Length` and `Transfer-Encoding` + * Allows Envoy to process requests/responses with both ``Content-Length`` and ``Transfer-Encoding`` * headers set. By default such messages are rejected, but if option is enabled - Envoy will * remove Content-Length header and process message. - * See `RFC7230, sec. 3.3.3 ` for details. + * See `RFC7230, sec. 3.3.3 `_ for details. * * .. attention:: * Enabling this option might lead to request smuggling vulnerability, especially if traffic * is proxied via multiple layers of proxies. + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'allow_chunked_length': (boolean); /** @@ -175,4 +211,34 @@ export interface Http1ProtocolOptions__Output { * `. */ 'override_stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); + /** + * Allows sending fully qualified URLs when proxying the first line of the + * response. By default, Envoy will only send the path components in the first line. + * If this is true, Envoy will create a fully qualified URI composing scheme + * (inferred if not present), host (from the host/:authority header) and path + * (from first line or :path header). + */ + 'send_fully_qualified_url': (boolean); + /** + * [#not-implemented-hide:] Hiding so that field can be removed after BalsaParser is rolled out. + * If set, force HTTP/1 parser: BalsaParser if true, http-parser if false. + * If unset, HTTP/1 parser is selected based on + * envoy.reloadable_features.http1_use_balsa_parser. + * See issue #21245. + */ + 'use_balsa_parser': (_google_protobuf_BoolValue__Output | null); + /** + * [#not-implemented-hide:] Hiding so that field can be removed. + * If true, and BalsaParser is used (either `use_balsa_parser` above is true, + * or `envoy.reloadable_features.http1_use_balsa_parser` is true and + * `use_balsa_parser` is unset), then every non-empty method with only valid + * characters is accepted. Otherwise, methods not on the hard-coded list are + * rejected. + * Once UHV is enabled, this field should be removed, and BalsaParser should + * allow any method. UHV validates the method, rejecting empty string or + * invalid characters, and provides :ref:`restrict_http_methods + * ` + * to reject custom methods. + */ + 'allow_custom_methods': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts index 786e2a00d..cc22ce44c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts @@ -35,7 +35,7 @@ export interface _envoy_config_core_v3_Http2ProtocolOptions_SettingsParameter__O } /** - * [#next-free-field: 16] + * [#next-free-field: 17] */ export interface Http2ProtocolOptions { /** @@ -74,8 +74,8 @@ export interface Http2ProtocolOptions { */ 'initial_stream_window_size'?: (_google_protobuf_UInt32Value | null); /** - * Similar to *initial_stream_window_size*, but for connection-level flow-control - * window. Currently, this has the same minimum/maximum/default as *initial_stream_window_size*. + * Similar to ``initial_stream_window_size``, but for connection-level flow-control + * window. Currently, this has the same minimum/maximum/default as ``initial_stream_window_size``. */ 'initial_connection_window_size'?: (_google_protobuf_UInt32Value | null); /** @@ -96,8 +96,6 @@ export interface Http2ProtocolOptions { * be written into the socket). Exceeding this limit triggers flood mitigation and connection is * terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due * to flood mitigation. The default limit is 10000. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_frames'?: (_google_protobuf_UInt32Value | null); /** @@ -106,8 +104,6 @@ export interface Http2ProtocolOptions { * this limit triggers flood mitigation and connection is terminated. The * ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood * mitigation. The default limit is 1000. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_control_frames'?: (_google_protobuf_UInt32Value | null); /** @@ -117,8 +113,6 @@ export interface Http2ProtocolOptions { * stat tracks the number of connections terminated due to flood mitigation. * Setting this to 0 will terminate connection upon receiving first frame with an empty payload * and no end stream flag. The default limit is 1. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_consecutive_inbound_frames_with_empty_payload'?: (_google_protobuf_UInt32Value | null); /** @@ -126,15 +120,13 @@ export interface Http2ProtocolOptions { * of PRIORITY frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * max_inbound_priority_frames_per_stream * (1 + opened_streams) + * ``max_inbound_priority_frames_per_stream`` * (1 + ``opened_streams``) * - * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * the connection is terminated. For downstream connections the ``opened_streams`` is incremented when * Envoy receives complete response headers from the upstream server. For upstream connection the - * `opened_streams` is incremented when Envoy send the HEADERS frame for a new stream. The + * ``opened_streams`` is incremented when Envoy send the HEADERS frame for a new stream. The * ``http2.inbound_priority_frames_flood`` stat tracks * the number of connections terminated due to flood mitigation. The default limit is 100. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_priority_frames_per_stream'?: (_google_protobuf_UInt32Value | null); /** @@ -142,18 +134,16 @@ export interface Http2ProtocolOptions { * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * 5 + 2 * (opened_streams + - * max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames) + * 5 + 2 * (``opened_streams`` + + * ``max_inbound_window_update_frames_per_data_frame_sent`` * ``outbound_data_frames``) * - * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * the connection is terminated. For downstream connections the ``opened_streams`` is incremented when * Envoy receives complete response headers from the upstream server. For upstream connections the - * `opened_streams` is incremented when Envoy sends the HEADERS frame for a new stream. The + * ``opened_streams`` is incremented when Envoy sends the HEADERS frame for a new stream. The * ``http2.inbound_priority_frames_flood`` stat tracks the number of connections terminated due to * flood mitigation. The default max_inbound_window_update_frames_per_data_frame_sent value is 10. * Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control, * but more complex implementations that try to estimate available bandwidth require at least 2. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_window_update_frames_per_data_frame_sent'?: (_google_protobuf_UInt32Value | null); /** @@ -216,10 +206,16 @@ export interface Http2ProtocolOptions { * does not respond within the configured timeout, the connection will be aborted. */ 'connection_keepalive'?: (_envoy_config_core_v3_KeepaliveSettings | null); + /** + * [#not-implemented-hide:] Hiding so that the field can be removed after oghttp2 is rolled out. + * If set, force use of a particular HTTP/2 codec: oghttp2 if true, nghttp2 if false. + * If unset, HTTP/2 codec is selected based on envoy.reloadable_features.http2_use_oghttp2. + */ + 'use_oghttp2_codec'?: (_google_protobuf_BoolValue | null); } /** - * [#next-free-field: 16] + * [#next-free-field: 17] */ export interface Http2ProtocolOptions__Output { /** @@ -258,8 +254,8 @@ export interface Http2ProtocolOptions__Output { */ 'initial_stream_window_size': (_google_protobuf_UInt32Value__Output | null); /** - * Similar to *initial_stream_window_size*, but for connection-level flow-control - * window. Currently, this has the same minimum/maximum/default as *initial_stream_window_size*. + * Similar to ``initial_stream_window_size``, but for connection-level flow-control + * window. Currently, this has the same minimum/maximum/default as ``initial_stream_window_size``. */ 'initial_connection_window_size': (_google_protobuf_UInt32Value__Output | null); /** @@ -280,8 +276,6 @@ export interface Http2ProtocolOptions__Output { * be written into the socket). Exceeding this limit triggers flood mitigation and connection is * terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due * to flood mitigation. The default limit is 10000. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_frames': (_google_protobuf_UInt32Value__Output | null); /** @@ -290,8 +284,6 @@ export interface Http2ProtocolOptions__Output { * this limit triggers flood mitigation and connection is terminated. The * ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood * mitigation. The default limit is 1000. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_outbound_control_frames': (_google_protobuf_UInt32Value__Output | null); /** @@ -301,8 +293,6 @@ export interface Http2ProtocolOptions__Output { * stat tracks the number of connections terminated due to flood mitigation. * Setting this to 0 will terminate connection upon receiving first frame with an empty payload * and no end stream flag. The default limit is 1. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_consecutive_inbound_frames_with_empty_payload': (_google_protobuf_UInt32Value__Output | null); /** @@ -310,15 +300,13 @@ export interface Http2ProtocolOptions__Output { * of PRIORITY frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * max_inbound_priority_frames_per_stream * (1 + opened_streams) + * ``max_inbound_priority_frames_per_stream`` * (1 + ``opened_streams``) * - * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * the connection is terminated. For downstream connections the ``opened_streams`` is incremented when * Envoy receives complete response headers from the upstream server. For upstream connection the - * `opened_streams` is incremented when Envoy send the HEADERS frame for a new stream. The + * ``opened_streams`` is incremented when Envoy send the HEADERS frame for a new stream. The * ``http2.inbound_priority_frames_flood`` stat tracks * the number of connections terminated due to flood mitigation. The default limit is 100. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_priority_frames_per_stream': (_google_protobuf_UInt32Value__Output | null); /** @@ -326,18 +314,16 @@ export interface Http2ProtocolOptions__Output { * of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated * using this formula:: * - * 5 + 2 * (opened_streams + - * max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames) + * 5 + 2 * (``opened_streams`` + + * ``max_inbound_window_update_frames_per_data_frame_sent`` * ``outbound_data_frames``) * - * the connection is terminated. For downstream connections the `opened_streams` is incremented when + * the connection is terminated. For downstream connections the ``opened_streams`` is incremented when * Envoy receives complete response headers from the upstream server. For upstream connections the - * `opened_streams` is incremented when Envoy sends the HEADERS frame for a new stream. The + * ``opened_streams`` is incremented when Envoy sends the HEADERS frame for a new stream. The * ``http2.inbound_priority_frames_flood`` stat tracks the number of connections terminated due to * flood mitigation. The default max_inbound_window_update_frames_per_data_frame_sent value is 10. * Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control, * but more complex implementations that try to estimate available bandwidth require at least 2. - * NOTE: flood and abuse mitigation for upstream connections is presently enabled by the - * `envoy.reloadable_features.upstream_http2_flood_checks` flag. */ 'max_inbound_window_update_frames_per_data_frame_sent': (_google_protobuf_UInt32Value__Output | null); /** @@ -400,4 +386,10 @@ export interface Http2ProtocolOptions__Output { * does not respond within the configured timeout, the connection will be aborted. */ 'connection_keepalive': (_envoy_config_core_v3_KeepaliveSettings__Output | null); + /** + * [#not-implemented-hide:] Hiding so that the field can be removed after oghttp2 is rolled out. + * If set, force use of a particular HTTP/2 codec: oghttp2 if true, nghttp2 if false. + * If unset, HTTP/2 codec is selected based on envoy.reloadable_features.http2_use_oghttp2. + */ + 'use_oghttp2_codec': (_google_protobuf_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts index 34a4053dd..a3064110f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts @@ -24,7 +24,7 @@ export enum _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresActi */ REJECT_REQUEST = 1, /** - * Drop the header with name containing underscores. The header is dropped before the filter chain is + * Drop the client header with name containing underscores. The header is dropped before the filter chain is * invoked and as such filters will not see dropped headers. The * "httpN.dropped_headers_with_underscores" is incremented for each dropped header. */ @@ -63,11 +63,10 @@ export interface HttpProtocolOptions { /** * The maximum duration of a connection. The duration is defined as a period since a connection * was established. If not set, there is no max duration. When max_connection_duration is reached - * and if there are no active streams, the connection will be closed. If there are any active streams, - * the drain sequence will kick-in, and the connection will be force-closed after the drain period. - * See :ref:`drain_timeout + * and if there are no active streams, the connection will be closed. If the connection is a + * downstream connection and there are any active streams, the drain sequence will kick-in, + * and the connection will be force-closed after the drain period. See :ref:`drain_timeout * `. - * Note: This feature is not yet implemented for the upstream connections. */ 'max_connection_duration'?: (_google_protobuf_Duration | null); /** @@ -79,6 +78,8 @@ export interface HttpProtocolOptions { * Action to take when a client request with a header name containing underscore characters is received. * If this setting is not specified, the value defaults to ALLOW. * Note: upstream responses are not affected by this setting. + * Note: this only affects client headers. It does not affect headers added + * by Envoy filters and does not have any impact if added to cluster config. */ 'headers_with_underscores_action'?: (_envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction | keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); /** @@ -122,11 +123,10 @@ export interface HttpProtocolOptions__Output { /** * The maximum duration of a connection. The duration is defined as a period since a connection * was established. If not set, there is no max duration. When max_connection_duration is reached - * and if there are no active streams, the connection will be closed. If there are any active streams, - * the drain sequence will kick-in, and the connection will be force-closed after the drain period. - * See :ref:`drain_timeout + * and if there are no active streams, the connection will be closed. If the connection is a + * downstream connection and there are any active streams, the drain sequence will kick-in, + * and the connection will be force-closed after the drain period. See :ref:`drain_timeout * `. - * Note: This feature is not yet implemented for the upstream connections. */ 'max_connection_duration': (_google_protobuf_Duration__Output | null); /** @@ -138,6 +138,8 @@ export interface HttpProtocolOptions__Output { * Action to take when a client request with a header name containing underscore characters is received. * If this setting is not specified, the value defaults to ALLOW. * Note: upstream responses are not affected by this setting. + * Note: this only affects client headers. It does not affect headers added + * by Envoy filters and does not have any impact if added to cluster config. */ 'headers_with_underscores_action': (keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts index 0bac9ac51..9a06ba477 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpUri.ts @@ -32,7 +32,7 @@ export interface HttpUri { */ 'timeout'?: (_google_protobuf_Duration | null); /** - * Specify how `uri` is to be fetched. Today, this requires an explicit + * Specify how ``uri`` is to be fetched. Today, this requires an explicit * cluster, but in the future we may support dynamic cluster creation or * inline DNS resolution. See `issue * `_. @@ -70,7 +70,7 @@ export interface HttpUri__Output { */ 'timeout': (_google_protobuf_Duration__Output | null); /** - * Specify how `uri` is to be fetched. Today, this requires an explicit + * Specify how ``uri`` is to be fetched. Today, this requires an explicit * cluster, but in the future we may support dynamic cluster creation or * inline DNS resolution. See `issue * `_. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts index 2c274c6e1..2d2ef0787 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/KeepaliveSettings.ts @@ -11,7 +11,9 @@ export interface KeepaliveSettings { 'interval'?: (_google_protobuf_Duration | null); /** * How long to wait for a response to a keepalive PING. If a response is not received within this - * time period, the connection will be aborted. + * time period, the connection will be aborted. Note that in order to prevent the influence of + * Head-of-line (HOL) blocking the timeout period is extended when *any* frame is received on + * the connection, under the assumption that if a frame is received the connection is healthy. */ 'timeout'?: (_google_protobuf_Duration | null); /** @@ -26,6 +28,8 @@ export interface KeepaliveSettings { * If this is zero, this type of PING will not be sent. * If an interval ping is outstanding, a second ping will not be sent as the * interval ping will determine if the connection is dead. + * + * The same feature for HTTP/3 is given by inheritance from QUICHE which uses :ref:`connection idle_timeout ` and the current PTO of the connection to decide whether to probe before sending a new request. */ 'connection_idle_interval'?: (_google_protobuf_Duration | null); } @@ -38,7 +42,9 @@ export interface KeepaliveSettings__Output { 'interval': (_google_protobuf_Duration__Output | null); /** * How long to wait for a response to a keepalive PING. If a response is not received within this - * time period, the connection will be aborted. + * time period, the connection will be aborted. Note that in order to prevent the influence of + * Head-of-line (HOL) blocking the timeout period is extended when *any* frame is received on + * the connection, under the assumption that if a frame is received the connection is healthy. */ 'timeout': (_google_protobuf_Duration__Output | null); /** @@ -53,6 +59,8 @@ export interface KeepaliveSettings__Output { * If this is zero, this type of PING will not be sent. * If an interval ping is outstanding, a second ping will not be sent as the * interval ping will determine if the connection is dead. + * + * The same feature for HTTP/3 is given by inheritance from QUICHE which uses :ref:`connection idle_timeout ` and the current PTO of the connection to decide whether to probe before sending a new request. */ 'connection_idle_interval': (_google_protobuf_Duration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts index fb603c2ba..2bcd3ce36 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Metadata.ts @@ -29,21 +29,21 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ */ export interface Metadata { /** - * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` * namespace is reserved for Envoy's built-in filters. - * If both *filter_metadata* and + * If both ``filter_metadata`` and * :ref:`typed_filter_metadata ` * fields are present in the metadata with same keys, - * only *typed_filter_metadata* field will be parsed. + * only ``typed_filter_metadata`` field will be parsed. */ 'filter_metadata'?: ({[key: string]: _google_protobuf_Struct}); /** - * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` * namespace is reserved for Envoy's built-in filters. * The value is encoded as google.protobuf.Any. * If both :ref:`filter_metadata ` - * and *typed_filter_metadata* fields are present in the metadata with same keys, - * only *typed_filter_metadata* field will be parsed. + * and ``typed_filter_metadata`` fields are present in the metadata with same keys, + * only ``typed_filter_metadata`` field will be parsed. */ 'typed_filter_metadata'?: ({[key: string]: _google_protobuf_Any}); } @@ -74,21 +74,21 @@ export interface Metadata { */ export interface Metadata__Output { /** - * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` * namespace is reserved for Envoy's built-in filters. - * If both *filter_metadata* and + * If both ``filter_metadata`` and * :ref:`typed_filter_metadata ` * fields are present in the metadata with same keys, - * only *typed_filter_metadata* field will be parsed. + * only ``typed_filter_metadata`` field will be parsed. */ 'filter_metadata': ({[key: string]: _google_protobuf_Struct__Output}); /** - * Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + * Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` * namespace is reserved for Envoy's built-in filters. * The value is encoded as google.protobuf.Any. * If both :ref:`filter_metadata ` - * and *typed_filter_metadata* fields are present in the metadata with same keys, - * only *typed_filter_metadata* field will be parsed. + * and ``typed_filter_metadata`` fields are present in the metadata with same keys, + * only ``typed_filter_metadata`` field will be parsed. */ 'typed_filter_metadata': ({[key: string]: _google_protobuf_Any__Output}); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts index addd47a68..6aef94d8e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts @@ -68,7 +68,7 @@ export interface Node { /** * Client feature support list. These are well known features described * in the Envoy API repository for a given major version of an API. Client features - * use reverse DNS naming scheme, for example `com.acme.feature`. + * use reverse DNS naming scheme, for example ``com.acme.feature``. * See :ref:`the list of features ` that xDS client may * support. */ @@ -77,7 +77,7 @@ export interface Node { * Known listening ports on the node as a generic hint to the management server * for filtering :ref:`listeners ` to be returned. For example, * if there is a listener bound to port 80, the list can optionally contain the - * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. + * SocketAddress ``(0.0.0.0,80)``. The field is optional and just a hint. */ 'listening_addresses'?: (_envoy_config_core_v3_Address)[]; /** @@ -152,7 +152,7 @@ export interface Node__Output { /** * Client feature support list. These are well known features described * in the Envoy API repository for a given major version of an API. Client features - * use reverse DNS naming scheme, for example `com.acme.feature`. + * use reverse DNS naming scheme, for example ``com.acme.feature``. * See :ref:`the list of features ` that xDS client may * support. */ @@ -161,7 +161,7 @@ export interface Node__Output { * Known listening ports on the node as a generic hint to the management server * for filtering :ref:`listeners ` to be returned. For example, * if there is a listener bound to port 80, the list can optionally contain the - * SocketAddress `(0.0.0.0,80)`. The field is optional and just a hint. + * SocketAddress ``(0.0.0.0,80)``. The field is optional and just a hint. */ 'listening_addresses': (_envoy_config_core_v3_Address__Output)[]; /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/PathConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/PathConfigSource.ts new file mode 100644 index 000000000..e620f8c52 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/PathConfigSource.ts @@ -0,0 +1,85 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto + +import type { WatchedDirectory as _envoy_config_core_v3_WatchedDirectory, WatchedDirectory__Output as _envoy_config_core_v3_WatchedDirectory__Output } from '../../../../envoy/config/core/v3/WatchedDirectory'; + +/** + * Local filesystem path configuration source. + */ +export interface PathConfigSource { + /** + * Path on the filesystem to source and watch for configuration updates. + * When sourcing configuration for a :ref:`secret `, + * the certificate and key files are also watched for updates. + * + * .. note:: + * + * The path to the source must exist at config load time. + * + * .. note:: + * + * If ``watched_directory`` is *not* configured, Envoy will watch the file path for *moves*. + * This is because in general only moves are atomic. The same method of swapping files as is + * demonstrated in the :ref:`runtime documentation ` can be + * used here also. If ``watched_directory`` is configured, no watch will be placed directly on + * this path. Instead, the configured ``watched_directory`` will be used to trigger reloads of + * this path. This is required in certain deployment scenarios. See below for more information. + */ + 'path'?: (string); + /** + * If configured, this directory will be watched for *moves*. When an entry in this directory is + * moved to, the ``path`` will be reloaded. This is required in certain deployment scenarios. + * + * Specifically, if trying to load an xDS resource using a + * `Kubernetes ConfigMap `_, the + * following configuration might be used: + * 1. Store xds.yaml inside a ConfigMap. + * 2. Mount the ConfigMap to ``/config_map/xds`` + * 3. Configure path ``/config_map/xds/xds.yaml`` + * 4. Configure watched directory ``/config_map/xds`` + * + * The above configuration will ensure that Envoy watches the owning directory for moves which is + * required due to how Kubernetes manages ConfigMap symbolic links during atomic updates. + */ + 'watched_directory'?: (_envoy_config_core_v3_WatchedDirectory | null); +} + +/** + * Local filesystem path configuration source. + */ +export interface PathConfigSource__Output { + /** + * Path on the filesystem to source and watch for configuration updates. + * When sourcing configuration for a :ref:`secret `, + * the certificate and key files are also watched for updates. + * + * .. note:: + * + * The path to the source must exist at config load time. + * + * .. note:: + * + * If ``watched_directory`` is *not* configured, Envoy will watch the file path for *moves*. + * This is because in general only moves are atomic. The same method of swapping files as is + * demonstrated in the :ref:`runtime documentation ` can be + * used here also. If ``watched_directory`` is configured, no watch will be placed directly on + * this path. Instead, the configured ``watched_directory`` will be used to trigger reloads of + * this path. This is required in certain deployment scenarios. See below for more information. + */ + 'path': (string); + /** + * If configured, this directory will be watched for *moves*. When an entry in this directory is + * moved to, the ``path`` will be reloaded. This is required in certain deployment scenarios. + * + * Specifically, if trying to load an xDS resource using a + * `Kubernetes ConfigMap `_, the + * following configuration might be used: + * 1. Store xds.yaml inside a ConfigMap. + * 2. Mount the ConfigMap to ``/config_map/xds`` + * 3. Configure path ``/config_map/xds/xds.yaml`` + * 4. Configure watched directory ``/config_map/xds`` + * + * The above configuration will ensure that Envoy watches the owning directory for moves which is + * required due to how Kubernetes manages ConfigMap symbolic links during atomic updates. + */ + 'watched_directory': (_envoy_config_core_v3_WatchedDirectory__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts index a28de2dbe..7da9d569e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts @@ -1,5 +1,6 @@ // Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto +import type { ProxyProtocolPassThroughTLVs as _envoy_config_core_v3_ProxyProtocolPassThroughTLVs, ProxyProtocolPassThroughTLVs__Output as _envoy_config_core_v3_ProxyProtocolPassThroughTLVs__Output } from '../../../../envoy/config/core/v3/ProxyProtocolPassThroughTLVs'; // Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto @@ -19,6 +20,11 @@ export interface ProxyProtocolConfig { * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details */ 'version'?: (_envoy_config_core_v3_ProxyProtocolConfig_Version | keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); + /** + * This config controls which TLVs can be passed to upstream if it is Proxy Protocol + * V2 header. If there is no setting for this field, no TLVs will be passed through. + */ + 'pass_through_tlvs'?: (_envoy_config_core_v3_ProxyProtocolPassThroughTLVs | null); } export interface ProxyProtocolConfig__Output { @@ -26,4 +32,9 @@ export interface ProxyProtocolConfig__Output { * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details */ 'version': (keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); + /** + * This config controls which TLVs can be passed to upstream if it is Proxy Protocol + * V2 header. If there is no setting for this field, no TLVs will be passed through. + */ + 'pass_through_tlvs': (_envoy_config_core_v3_ProxyProtocolPassThroughTLVs__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts new file mode 100644 index 000000000..0dddbf79f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts @@ -0,0 +1,43 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto + + +// Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto + +export enum _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType { + /** + * Pass all TLVs. + */ + INCLUDE_ALL = 0, + /** + * Pass specific TLVs defined in tlv_type. + */ + INCLUDE = 1, +} + +export interface ProxyProtocolPassThroughTLVs { + /** + * The strategy to pass through TLVs. Default is INCLUDE_ALL. + * If INCLUDE_ALL is set, all TLVs will be passed through no matter the tlv_type field. + */ + 'match_type'?: (_envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType | keyof typeof _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType); + /** + * The TLV types that are applied based on match_type. + * TLV type is defined as uint8_t in proxy protocol. See `the spec + * `_ for details. + */ + 'tlv_type'?: (number)[]; +} + +export interface ProxyProtocolPassThroughTLVs__Output { + /** + * The strategy to pass through TLVs. Default is INCLUDE_ALL. + * If INCLUDE_ALL is set, all TLVs will be passed through no matter the tlv_type field. + */ + 'match_type': (keyof typeof _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType); + /** + * The TLV types that are applied based on match_type. + * TLV type is defined as uint8_t in proxy protocol. See `the spec + * `_ for details. + */ + 'tlv_type': (number)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicKeepAliveSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicKeepAliveSettings.ts new file mode 100644 index 000000000..2bb2aa872 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicKeepAliveSettings.ts @@ -0,0 +1,53 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; + +/** + * Config for keepalive probes in a QUIC connection. + * Note that QUIC keep-alive probing packets work differently from HTTP/2 keep-alive PINGs in a sense that the probing packet + * itself doesn't timeout waiting for a probing response. Quic has a shorter idle timeout than TCP, so it doesn't rely on such probing to discover dead connections. If the peer fails to respond, the connection will idle timeout eventually. Thus, they are configured differently from :ref:`connection_keepalive `. + */ +export interface QuicKeepAliveSettings { + /** + * The max interval for a connection to send keep-alive probing packets (with PING or PATH_RESPONSE). The value should be smaller than :ref:`connection idle_timeout ` to prevent idle timeout while not less than 1s to avoid throttling the connection or flooding the peer with probes. + * + * If :ref:`initial_interval ` is absent or zero, a client connection will use this value to start probing. + * + * If zero, disable keepalive probing. + * If absent, use the QUICHE default interval to probe. + */ + 'max_interval'?: (_google_protobuf_Duration | null); + /** + * The interval to send the first few keep-alive probing packets to prevent connection from hitting the idle timeout. Subsequent probes will be sent, each one with an interval exponentially longer than previous one, till it reaches :ref:`max_interval `. And the probes afterwards will always use :ref:`max_interval `. + * + * The value should be smaller than :ref:`connection idle_timeout ` to prevent idle timeout and smaller than max_interval to take effect. + * + * If absent or zero, disable keepalive probing for a server connection. For a client connection, if :ref:`max_interval ` is also zero, do not keepalive, otherwise use max_interval or QUICHE default to probe all the time. + */ + 'initial_interval'?: (_google_protobuf_Duration | null); +} + +/** + * Config for keepalive probes in a QUIC connection. + * Note that QUIC keep-alive probing packets work differently from HTTP/2 keep-alive PINGs in a sense that the probing packet + * itself doesn't timeout waiting for a probing response. Quic has a shorter idle timeout than TCP, so it doesn't rely on such probing to discover dead connections. If the peer fails to respond, the connection will idle timeout eventually. Thus, they are configured differently from :ref:`connection_keepalive `. + */ +export interface QuicKeepAliveSettings__Output { + /** + * The max interval for a connection to send keep-alive probing packets (with PING or PATH_RESPONSE). The value should be smaller than :ref:`connection idle_timeout ` to prevent idle timeout while not less than 1s to avoid throttling the connection or flooding the peer with probes. + * + * If :ref:`initial_interval ` is absent or zero, a client connection will use this value to start probing. + * + * If zero, disable keepalive probing. + * If absent, use the QUICHE default interval to probe. + */ + 'max_interval': (_google_protobuf_Duration__Output | null); + /** + * The interval to send the first few keep-alive probing packets to prevent connection from hitting the idle timeout. Subsequent probes will be sent, each one with an interval exponentially longer than previous one, till it reaches :ref:`max_interval `. And the probes afterwards will always use :ref:`max_interval `. + * + * The value should be smaller than :ref:`connection idle_timeout ` to prevent idle timeout and smaller than max_interval to take effect. + * + * If absent or zero, disable keepalive probing for a server connection. For a client connection, if :ref:`max_interval ` is also zero, do not keepalive, otherwise use max_interval or QUICHE default to probe all the time. + */ + 'initial_interval': (_google_protobuf_Duration__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts index 6a653b54d..b33feab51 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/QuicProtocolOptions.ts @@ -1,9 +1,11 @@ // Original file: deps/envoy-api/envoy/config/core/v3/protocol.proto import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { QuicKeepAliveSettings as _envoy_config_core_v3_QuicKeepAliveSettings, QuicKeepAliveSettings__Output as _envoy_config_core_v3_QuicKeepAliveSettings__Output } from '../../../../envoy/config/core/v3/QuicKeepAliveSettings'; /** * QUIC protocol options which apply to both downstream and upstream connections. + * [#next-free-field: 6] */ export interface QuicProtocolOptions { /** @@ -25,18 +27,31 @@ export interface QuicProtocolOptions { */ 'initial_stream_window_size'?: (_google_protobuf_UInt32Value | null); /** - * Similar to *initial_stream_window_size*, but for connection-level + * Similar to ``initial_stream_window_size``, but for connection-level * flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). - * window. Currently, this has the same minimum/default as *initial_stream_window_size*. + * window. Currently, this has the same minimum/default as ``initial_stream_window_size``. * * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default * window size now, so it's also the minimum. */ 'initial_connection_window_size'?: (_google_protobuf_UInt32Value | null); + /** + * The number of timeouts that can occur before port migration is triggered for QUIC clients. + * This defaults to 1. If set to 0, port migration will not occur on path degrading. + * Timeout here refers to QUIC internal path degrading timeout mechanism, such as PTO. + * This has no effect on server sessions. + */ + 'num_timeouts_to_trigger_port_migration'?: (_google_protobuf_UInt32Value | null); + /** + * Probes the peer at the configured interval to solicit traffic, i.e. ACK or PATH_RESPONSE, from the peer to push back connection idle timeout. + * If absent, use the default keepalive behavior of which a client connection sends PINGs every 15s, and a server connection doesn't do anything. + */ + 'connection_keepalive'?: (_envoy_config_core_v3_QuicKeepAliveSettings | null); } /** * QUIC protocol options which apply to both downstream and upstream connections. + * [#next-free-field: 6] */ export interface QuicProtocolOptions__Output { /** @@ -58,12 +73,24 @@ export interface QuicProtocolOptions__Output { */ 'initial_stream_window_size': (_google_protobuf_UInt32Value__Output | null); /** - * Similar to *initial_stream_window_size*, but for connection-level + * Similar to ``initial_stream_window_size``, but for connection-level * flow-control. Valid values rage from 1 to 25165824 (24MB, maximum supported by QUICHE) and defaults to 65536 (2^16). - * window. Currently, this has the same minimum/default as *initial_stream_window_size*. + * window. Currently, this has the same minimum/default as ``initial_stream_window_size``. * * NOTE: 16384 (2^14) is the minimum window size supported in Google QUIC. We only support increasing the default * window size now, so it's also the minimum. */ 'initial_connection_window_size': (_google_protobuf_UInt32Value__Output | null); + /** + * The number of timeouts that can occur before port migration is triggered for QUIC clients. + * This defaults to 1. If set to 0, port migration will not occur on path degrading. + * Timeout here refers to QUIC internal path degrading timeout mechanism, such as PTO. + * This has no effect on server sessions. + */ + 'num_timeouts_to_trigger_port_migration': (_google_protobuf_UInt32Value__Output | null); + /** + * Probes the peer at the configured interval to solicit traffic, i.e. ACK or PATH_RESPONSE, from the peer to push back connection idle timeout. + * If absent, use the default keepalive behavior of which a client connection sends PINGs every 15s, and a server connection doesn't do anything. + */ + 'connection_keepalive': (_envoy_config_core_v3_QuicKeepAliveSettings__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts index 3a2073294..d976b4375 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RuntimeFractionalPercent.ts @@ -12,7 +12,7 @@ import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalP * :ref:`FractionalPercent ` proto represented as JSON/YAML * and may also be represented as an integer with the assumption that the value is an integral * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse - * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. + * as a ``FractionalPercent`` whose numerator is 42 and denominator is HUNDRED. */ export interface RuntimeFractionalPercent { /** @@ -35,7 +35,7 @@ export interface RuntimeFractionalPercent { * :ref:`FractionalPercent ` proto represented as JSON/YAML * and may also be represented as an integer with the assumption that the value is an integral * percentage out of 100. For instance, a runtime key lookup returning the value "42" would parse - * as a `FractionalPercent` whose numerator is 42 and denominator is HUNDRED. + * as a ``FractionalPercent`` whose numerator is 42 and denominator is HUNDRED. */ export interface RuntimeFractionalPercent__Output { /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts index 3966dc04c..ae87b9edd 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts @@ -22,8 +22,8 @@ export interface SocketAddress { * within an upstream :ref:`BindConfig `, the address * controls the source address of outbound connections. For :ref:`clusters * `, the cluster type determines whether the - * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS - * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized + * address must be an IP (``STATIC`` or ``EDS`` clusters) or a hostname resolved by DNS + * (``STRICT_DNS`` or ``LOGICAL_DNS`` clusters). Address resolution can be customized * via :ref:`resolver_name `. */ 'address'?: (string); @@ -39,7 +39,7 @@ export interface SocketAddress { * this is empty, a context dependent default applies. If the address is a concrete * IP address, no resolution will occur. If address is a hostname this * should be set for resolution other than DNS. Specifying a custom resolver with - * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. + * ``STRICT_DNS`` or ``LOGICAL_DNS`` will generate an error at runtime. */ 'resolver_name'?: (string); /** @@ -66,8 +66,8 @@ export interface SocketAddress__Output { * within an upstream :ref:`BindConfig `, the address * controls the source address of outbound connections. For :ref:`clusters * `, the cluster type determines whether the - * address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS - * (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized + * address must be an IP (``STATIC`` or ``EDS`` clusters) or a hostname resolved by DNS + * (``STRICT_DNS`` or ``LOGICAL_DNS`` clusters). Address resolution can be customized * via :ref:`resolver_name `. */ 'address': (string); @@ -83,7 +83,7 @@ export interface SocketAddress__Output { * this is empty, a context dependent default applies. If the address is a concrete * IP address, no resolution will occur. If address is a hostname this * should be set for resolution other than DNS. Specifying a custom resolver with - * *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. + * ``STRICT_DNS`` or ``LOGICAL_DNS`` will generate an error at runtime. */ 'resolver_name': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts index 56f13c339..edff50a63 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts @@ -22,6 +22,26 @@ export enum _envoy_config_core_v3_SocketOption_SocketState { /** * Generic socket option message. This would be used to set socket options that * might not exist in upstream kernels or precompiled Envoy binaries. + * + * For example: + * + * .. code-block:: json + * + * { + * "description": "support tcp keep alive", + * "state": 0, + * "level": 1, + * "name": 9, + * "int_value": 1, + * } + * + * 1 means SOL_SOCKET and 9 means SO_KEEPALIVE on Linux. + * With the above configuration, `TCP Keep-Alives `_ + * can be enabled in socket with Linux, which can be used in + * :ref:`listener's` or + * :ref:`admin's ` socket_options etc. + * + * It should be noted that the name or level may have different values on different platforms. * [#next-free-field: 7] */ export interface SocketOption { @@ -57,6 +77,26 @@ export interface SocketOption { /** * Generic socket option message. This would be used to set socket options that * might not exist in upstream kernels or precompiled Envoy binaries. + * + * For example: + * + * .. code-block:: json + * + * { + * "description": "support tcp keep alive", + * "state": 0, + * "level": 1, + * "name": 9, + * "int_value": 1, + * } + * + * 1 means SOL_SOCKET and 9 means SO_KEEPALIVE on Linux. + * With the above configuration, `TCP Keep-Alives `_ + * can be enabled in socket with Linux, which can be used in + * :ref:`listener's` or + * :ref:`admin's ` socket_options etc. + * + * It should be noted that the name or level may have different values on different platforms. * [#next-free-field: 7] */ export interface SocketOption__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOptionsOverride.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOptionsOverride.ts new file mode 100644 index 000000000..5df984579 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOptionsOverride.ts @@ -0,0 +1,11 @@ +// Original file: deps/envoy-api/envoy/config/core/v3/socket_option.proto + +import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; + +export interface SocketOptionsOverride { + 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; +} + +export interface SocketOptionsOverride__Output { + 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts index a935fc1ec..be0237e38 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts @@ -63,9 +63,9 @@ export interface SubstitutionFormatString { */ 'omit_empty_values'?: (boolean); /** - * Specify a *content_type* field. - * If this field is not set then ``text/plain`` is used for *text_format* and - * ``application/json`` is used for *json_format*. + * Specify a ``content_type`` field. + * If this field is not set then ``text/plain`` is used for ``text_format`` and + * ``application/json`` is used for ``json_format``. * * .. validated-code-block:: yaml * :type-name: envoy.config.core.v3.SubstitutionFormatString @@ -160,9 +160,9 @@ export interface SubstitutionFormatString__Output { */ 'omit_empty_values': (boolean); /** - * Specify a *content_type* field. - * If this field is not set then ``text/plain`` is used for *text_format* and - * ``application/json`` is used for *json_format*. + * Specify a ``content_type`` field. + * If this field is not set then ``text/plain`` is used for ``text_format`` and + * ``application/json`` is used for ``json_format``. * * .. validated-code-block:: yaml * :type-name: envoy.config.core.v3.SubstitutionFormatString diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts index d653f9373..d46751d4f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TypedExtensionConfig.ts @@ -14,8 +14,9 @@ export interface TypedExtensionConfig { 'name'?: (string); /** * The typed config for the extension. The type URL will be used to identify - * the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*, - * the inner type URL of *TypedStruct* will be utilized. See the + * the extension. In the case that the type URL is ``xds.type.v3.TypedStruct`` + * (or, for historical reasons, ``udpa.type.v1.TypedStruct``), the inner type + * URL of ``TypedStruct`` will be utilized. See the * :ref:`extension configuration overview * ` for further details. */ @@ -34,8 +35,9 @@ export interface TypedExtensionConfig__Output { 'name': (string); /** * The typed config for the extension. The type URL will be used to identify - * the extension. In the case that the type URL is *udpa.type.v1.TypedStruct*, - * the inner type URL of *TypedStruct* will be utilized. See the + * the extension. In the case that the type URL is ``xds.type.v3.TypedStruct`` + * (or, for historical reasons, ``udpa.type.v1.TypedStruct``), the inner type + * URL of ``TypedStruct`` will be utilized. See the * :ref:`extension configuration overview * ` for further details. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts index c0da4159f..91e9f0b53 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UpstreamHttpProtocolOptions.ts @@ -7,13 +7,15 @@ export interface UpstreamHttpProtocolOptions { * upstream connections based on the downstream HTTP host/authority header or any other arbitrary * header when :ref:`override_auto_sni_header ` * is set, as seen by the :ref:`router filter `. + * Does nothing if a filter before the http router filter sets the corresponding metadata. */ 'auto_sni'?: (boolean); /** * Automatic validate upstream presented certificate for new upstream connections based on the * downstream HTTP host/authority header or any other arbitrary header when :ref:`override_auto_sni_header ` * is set, as seen by the :ref:`router filter `. - * This field is intended to be set with `auto_sni` field. + * This field is intended to be set with ``auto_sni`` field. + * Does nothing if a filter before the http router filter sets the corresponding metadata. */ 'auto_san_validation'?: (boolean); /** @@ -22,8 +24,9 @@ export interface UpstreamHttpProtocolOptions { * :ref:`router filter `. * If unset, host/authority header will be used for populating the SNI. If the specified header * is not found or the value is empty, host/authority header will be used instead. - * This field is intended to be set with `auto_sni` and/or `auto_san_validation` fields. + * This field is intended to be set with ``auto_sni`` and/or ``auto_san_validation`` fields. * If none of these fields are set then setting this would be a no-op. + * Does nothing if a filter before the http router filter sets the corresponding metadata. */ 'override_auto_sni_header'?: (string); } @@ -34,13 +37,15 @@ export interface UpstreamHttpProtocolOptions__Output { * upstream connections based on the downstream HTTP host/authority header or any other arbitrary * header when :ref:`override_auto_sni_header ` * is set, as seen by the :ref:`router filter `. + * Does nothing if a filter before the http router filter sets the corresponding metadata. */ 'auto_sni': (boolean); /** * Automatic validate upstream presented certificate for new upstream connections based on the * downstream HTTP host/authority header or any other arbitrary header when :ref:`override_auto_sni_header ` * is set, as seen by the :ref:`router filter `. - * This field is intended to be set with `auto_sni` field. + * This field is intended to be set with ``auto_sni`` field. + * Does nothing if a filter before the http router filter sets the corresponding metadata. */ 'auto_san_validation': (boolean); /** @@ -49,8 +54,9 @@ export interface UpstreamHttpProtocolOptions__Output { * :ref:`router filter `. * If unset, host/authority header will be used for populating the SNI. If the specified header * is not found or the value is empty, host/authority header will be used instead. - * This field is intended to be set with `auto_sni` and/or `auto_san_validation` fields. + * This field is intended to be set with ``auto_sni`` and/or ``auto_san_validation`` fields. * If none of these fields are set then setting this would be a no-op. + * Does nothing if a filter before the http router filter sets the corresponding metadata. */ 'override_auto_sni_header': (string); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts index c160333b2..d65383885 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/ClusterStats.ts @@ -52,9 +52,9 @@ export interface ClusterStats { 'total_dropped_requests'?: (number | string | Long); /** * Period over which the actual load report occurred. This will be guaranteed to include every - * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy - * and the *LoadStatsResponse* message sent from the management server, this may be longer than - * the requested load reporting interval in the *LoadStatsResponse*. + * request reported. Due to system load and delays between the ``LoadStatsRequest`` sent from Envoy + * and the ``LoadStatsResponse`` message sent from the management server, this may be longer than + * the requested load reporting interval in the ``LoadStatsResponse``. */ 'load_report_interval'?: (_google_protobuf_Duration | null); /** @@ -96,9 +96,9 @@ export interface ClusterStats__Output { 'total_dropped_requests': (string); /** * Period over which the actual load report occurred. This will be guaranteed to include every - * request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy - * and the *LoadStatsResponse* message sent from the management server, this may be longer than - * the requested load reporting interval in the *LoadStatsResponse*. + * request reported. Due to system load and delays between the ``LoadStatsRequest`` sent from Envoy + * and the ``LoadStatsResponse`` message sent from the management server, this may be longer than + * the requested load reporting interval in the ``LoadStatsResponse``. */ 'load_report_interval': (_google_protobuf_Duration__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts index 31eb09055..ef56d7068 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/Endpoint.ts @@ -23,6 +23,19 @@ export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig { * endpoint. */ 'hostname'?: (string); + /** + * Optional alternative health check host address. + * + * .. attention:: + * + * The form of the health check host address is expected to be a direct IP address. + */ + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * Optional flag to control if perform active health check for this endpoint. + * Active health check is enabled by default if there is a health checker. + */ + 'disable_active_health_check'?: (boolean); } /** @@ -46,6 +59,19 @@ export interface _envoy_config_endpoint_v3_Endpoint_HealthCheckConfig__Output { * endpoint. */ 'hostname': (string); + /** + * Optional alternative health check host address. + * + * .. attention:: + * + * The form of the health check host address is expected to be a direct IP address. + */ + 'address': (_envoy_config_core_v3_Address__Output | null); + /** + * Optional flag to control if perform active health check for this endpoint. + * Active health check is enabled by default if there is a health checker. + */ + 'disable_active_health_check': (boolean); } /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts index 6025ae3a7..8d184b8a0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts @@ -18,8 +18,8 @@ export interface LbEndpoint { /** * The endpoint metadata specifies values that may be used by the load * balancer to select endpoints in a cluster for a given request. The filter - * name should be specified as *envoy.lb*. An example boolean key-value pair - * is *canary*, providing the optional canary status of the upstream host. + * name should be specified as ``envoy.lb``. An example boolean key-value pair + * is ``canary``, providing the optional canary status of the upstream host. * This may be matched against in a route's * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. @@ -32,9 +32,9 @@ export interface LbEndpoint { * of the weights of all endpoints in the endpoint's locality to produce a * percentage of traffic for the endpoint. This percentage is then further * weighted by the endpoint's locality's load balancing weight from - * LocalityLbEndpoints. If unspecified, each host is presumed to have equal - * weight in a locality. The sum of the weights of all endpoints in the - * endpoint's locality must not exceed uint32_t maximal value (4294967295). + * LocalityLbEndpoints. If unspecified, will be treated as 1. The sum + * of the weights of all endpoints in the endpoint's locality must not + * exceed uint32_t maximal value (4294967295). */ 'load_balancing_weight'?: (_google_protobuf_UInt32Value | null); /** @@ -60,8 +60,8 @@ export interface LbEndpoint__Output { /** * The endpoint metadata specifies values that may be used by the load * balancer to select endpoints in a cluster for a given request. The filter - * name should be specified as *envoy.lb*. An example boolean key-value pair - * is *canary*, providing the optional canary status of the upstream host. + * name should be specified as ``envoy.lb``. An example boolean key-value pair + * is ``canary``, providing the optional canary status of the upstream host. * This may be matched against in a route's * :ref:`RouteAction ` metadata_match field * to subset the endpoints considered in cluster load balancing. @@ -74,9 +74,9 @@ export interface LbEndpoint__Output { * of the weights of all endpoints in the endpoint's locality to produce a * percentage of traffic for the endpoint. This percentage is then further * weighted by the endpoint's locality's load balancing weight from - * LocalityLbEndpoints. If unspecified, each host is presumed to have equal - * weight in a locality. The sum of the weights of all endpoints in the - * endpoint's locality must not exceed uint32_t maximal value (4294967295). + * LocalityLbEndpoints. If unspecified, will be treated as 1. The sum + * of the weights of all endpoints in the endpoint's locality must not + * exceed uint32_t maximal value (4294967295). */ 'load_balancing_weight': (_google_protobuf_UInt32Value__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts index 182e27c9c..4540792d6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints.ts @@ -23,9 +23,8 @@ export interface _envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList__O /** * A group of endpoints belonging to a Locality. - * One can have multiple LocalityLbEndpoints for a locality, but this is - * generally only done if the different groups need to have different load - * balancing weights or different priorities. + * One can have multiple LocalityLbEndpoints for a locality, but only if + * they have different priorities. * [#next-free-field: 9] */ export interface LocalityLbEndpoints { @@ -36,7 +35,7 @@ export interface LocalityLbEndpoints { /** * The group of endpoints belonging to the locality specified. * [#comment:TODO(adisuissa): Once LEDS is implemented this field needs to be - * deprecated and replaced by *load_balancer_endpoints*.] + * deprecated and replaced by ``load_balancer_endpoints``.] */ 'lb_endpoints'?: (_envoy_config_endpoint_v3_LbEndpoint)[]; /** @@ -76,7 +75,7 @@ export interface LocalityLbEndpoints { 'proximity'?: (_google_protobuf_UInt32Value | null); /** * The group of endpoints belonging to the locality. - * [#comment:TODO(adisuissa): Once LEDS is implemented the *lb_endpoints* field + * [#comment:TODO(adisuissa): Once LEDS is implemented the ``lb_endpoints`` field * needs to be deprecated.] */ 'load_balancer_endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList | null); @@ -92,9 +91,8 @@ export interface LocalityLbEndpoints { /** * A group of endpoints belonging to a Locality. - * One can have multiple LocalityLbEndpoints for a locality, but this is - * generally only done if the different groups need to have different load - * balancing weights or different priorities. + * One can have multiple LocalityLbEndpoints for a locality, but only if + * they have different priorities. * [#next-free-field: 9] */ export interface LocalityLbEndpoints__Output { @@ -105,7 +103,7 @@ export interface LocalityLbEndpoints__Output { /** * The group of endpoints belonging to the locality specified. * [#comment:TODO(adisuissa): Once LEDS is implemented this field needs to be - * deprecated and replaced by *load_balancer_endpoints*.] + * deprecated and replaced by ``load_balancer_endpoints``.] */ 'lb_endpoints': (_envoy_config_endpoint_v3_LbEndpoint__Output)[]; /** @@ -145,7 +143,7 @@ export interface LocalityLbEndpoints__Output { 'proximity': (_google_protobuf_UInt32Value__Output | null); /** * The group of endpoints belonging to the locality. - * [#comment:TODO(adisuissa): Once LEDS is implemented the *lb_endpoints* field + * [#comment:TODO(adisuissa): Once LEDS is implemented the ``lb_endpoints`` field * needs to be deprecated.] */ 'load_balancer_endpoints'?: (_envoy_config_endpoint_v3_LocalityLbEndpoints_LbEndpointList__Output | null); diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/AdditionalAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/AdditionalAddress.ts new file mode 100644 index 000000000..cc1f5e42f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/AdditionalAddress.ts @@ -0,0 +1,38 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { SocketOptionsOverride as _envoy_config_core_v3_SocketOptionsOverride, SocketOptionsOverride__Output as _envoy_config_core_v3_SocketOptionsOverride__Output } from '../../../../envoy/config/core/v3/SocketOptionsOverride'; + +/** + * The additional address the listener is listening on. + */ +export interface AdditionalAddress { + 'address'?: (_envoy_config_core_v3_Address | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. If specified, this will override the + * :ref:`socket_options ` + * in the listener. If specified with no + * :ref:`socket_options ` + * or an empty list of :ref:`socket_options `, + * it means no socket option will apply. + */ + 'socket_options'?: (_envoy_config_core_v3_SocketOptionsOverride | null); +} + +/** + * The additional address the listener is listening on. + */ +export interface AdditionalAddress__Output { + 'address': (_envoy_config_core_v3_Address__Output | null); + /** + * Additional socket options that may not be present in Envoy source code or + * precompiled binaries. If specified, this will override the + * :ref:`socket_options ` + * in the listener. If specified with no + * :ref:`socket_options ` + * or an empty list of :ref:`socket_options `, + * it means no socket option will apply. + */ + 'socket_options': (_envoy_config_core_v3_SocketOptionsOverride__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListenerManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListenerManager.ts new file mode 100644 index 000000000..125875161 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ApiListenerManager.ts @@ -0,0 +1,18 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + + +/** + * A placeholder proto so that users can explicitly configure the API + * Listener Manager via the bootstrap's :ref:`listener_manager `. + * [#not-implemented-hide:] + */ +export interface ApiListenerManager { +} + +/** + * A placeholder proto so that users can explicitly configure the API + * Listener Manager via the bootstrap's :ref:`listener_manager `. + * [#not-implemented-hide:] + */ +export interface ApiListenerManager__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts index 66e903b28..b95b36418 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Filter.ts @@ -8,8 +8,7 @@ import type { ExtensionConfigSource as _envoy_config_core_v3_ExtensionConfigSour */ export interface Filter { /** - * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. + * The name of the filter configuration. */ 'name'?: (string); /** @@ -33,8 +32,7 @@ export interface Filter { */ export interface Filter__Output { /** - * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. + * The name of the filter configuration. */ 'name': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts index e65f433c4..f27000d71 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts @@ -60,6 +60,12 @@ export interface FilterChain { * connections established with the listener. Order matters as the filters are * processed sequentially as connection events happen. Note: If the filter * list is empty, the connection will close by default. + * + * For QUIC listeners, network filters other than HTTP Connection Manager (HCM) + * can be created, but due to differences in the connection implementation compared + * to TCP, the onData() method will never be called. Therefore, network filters + * for QUIC listeners should only expect to do work at the start of a new connection + * (i.e. in onNewConnection()). HCM must be the last (or only) filter in the chain. */ 'filters'?: (_envoy_config_listener_v3_Filter)[]; /** @@ -81,17 +87,18 @@ export interface FilterChain { 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * Optional custom transport socket implementation to use for downstream connections. - * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and - * :ref:`DownstreamTlsContext ` in the `typed_config`. + * To setup TLS, set a transport socket with name ``envoy.transport_sockets.tls`` and + * :ref:`DownstreamTlsContext ` in the ``typed_config``. * If no transport socket configuration is specified, new connections * will be set up with plaintext. * [#extension-category: envoy.transport_sockets.downstream] */ 'transport_socket'?: (_envoy_config_core_v3_TransportSocket | null); /** - * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no - * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter - * chain is to be dynamically updated or removed via FCDS a unique name must be provided. + * The unique name (or empty) by which this filter chain is known. + * Note: :ref:`filter_chain_matcher + * ` + * requires that filter chains are uniquely named within a listener. */ 'name'?: (string); /** @@ -123,6 +130,12 @@ export interface FilterChain__Output { * connections established with the listener. Order matters as the filters are * processed sequentially as connection events happen. Note: If the filter * list is empty, the connection will close by default. + * + * For QUIC listeners, network filters other than HTTP Connection Manager (HCM) + * can be created, but due to differences in the connection implementation compared + * to TCP, the onData() method will never be called. Therefore, network filters + * for QUIC listeners should only expect to do work at the start of a new connection + * (i.e. in onNewConnection()). HCM must be the last (or only) filter in the chain. */ 'filters': (_envoy_config_listener_v3_Filter__Output)[]; /** @@ -144,17 +157,18 @@ export interface FilterChain__Output { 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * Optional custom transport socket implementation to use for downstream connections. - * To setup TLS, set a transport socket with name `envoy.transport_sockets.tls` and - * :ref:`DownstreamTlsContext ` in the `typed_config`. + * To setup TLS, set a transport socket with name ``envoy.transport_sockets.tls`` and + * :ref:`DownstreamTlsContext ` in the ``typed_config``. * If no transport socket configuration is specified, new connections * will be set up with plaintext. * [#extension-category: envoy.transport_sockets.downstream] */ 'transport_socket': (_envoy_config_core_v3_TransportSocket__Output | null); /** - * [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no - * name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter - * chain is to be dynamically updated or removed via FCDS a unique name must be provided. + * The unique name (or empty) by which this filter chain is known. + * Note: :ref:`filter_chain_matcher + * ` + * requires that filter chains are uniquely named within a listener. */ 'name': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts index 259888217..87b1503cb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts @@ -143,6 +143,7 @@ export interface FilterChainMatch { * will be first matched against ``www.example.com``, then ``*.example.com``, then ``*.com``. * * Note that partial wildcards are not supported, and values like ``*w.example.com`` are invalid. + * The value ``*`` is also not supported, and ``server_names`` should be omitted instead. * * .. attention:: * @@ -285,6 +286,7 @@ export interface FilterChainMatch__Output { * will be first matched against ``www.example.com``, then ``*.example.com``, then ``*.com``. * * Note that partial wildcards are not supported, and values like ``*w.example.com`` are invalid. + * The value ``*`` is also not supported, and ``server_names`` should be omitted instead. * * .. attention:: * diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts index 3df1006b7..ca12fc6de 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts @@ -12,6 +12,9 @@ import type { TrafficDirection as _envoy_config_core_v3_TrafficDirection } from import type { UdpListenerConfig as _envoy_config_listener_v3_UdpListenerConfig, UdpListenerConfig__Output as _envoy_config_listener_v3_UdpListenerConfig__Output } from '../../../../envoy/config/listener/v3/UdpListenerConfig'; import type { ApiListener as _envoy_config_listener_v3_ApiListener, ApiListener__Output as _envoy_config_listener_v3_ApiListener__Output } from '../../../../envoy/config/listener/v3/ApiListener'; import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; +import type { Matcher as _xds_type_matcher_v3_Matcher, Matcher__Output as _xds_type_matcher_v3_Matcher__Output } from '../../../../xds/type/matcher/v3/Matcher'; +import type { AdditionalAddress as _envoy_config_listener_v3_AdditionalAddress, AdditionalAddress__Output as _envoy_config_listener_v3_AdditionalAddress__Output } from '../../../../envoy/config/listener/v3/AdditionalAddress'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; /** * Configuration for listener connection balancing. @@ -21,7 +24,13 @@ export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig { * If specified, the listener will use the exact connection balancer. */ 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance | null); - 'balance_type'?: "exact_balance"; + /** + * The listener will use the connection balancer according to ``type_url``. If ``type_url`` is invalid, + * Envoy will not attempt to balance active connections between worker threads. + * [#extension-category: envoy.network.connection_balance] + */ + 'extend_balance'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'balance_type'?: "exact_balance"|"extend_balance"; } /** @@ -32,7 +41,13 @@ export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Out * If specified, the listener will use the exact connection balancer. */ 'exact_balance'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig_ExactBalance__Output | null); - 'balance_type': "exact_balance"; + /** + * The listener will use the connection balancer according to ``type_url``. If ``type_url`` is invalid, + * Envoy will not attempt to balance active connections between worker threads. + * [#extension-category: envoy.network.connection_balance] + */ + 'extend_balance'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'balance_type': "exact_balance"|"extend_balance"; } /** @@ -105,20 +120,18 @@ export interface _envoy_config_listener_v3_Listener_ConnectionBalanceConfig_Exac /** * Configuration for envoy internal listener. All the future internal listener features should be added here. - * [#not-implemented-hide:] */ export interface _envoy_config_listener_v3_Listener_InternalListenerConfig { } /** * Configuration for envoy internal listener. All the future internal listener features should be added here. - * [#not-implemented-hide:] */ export interface _envoy_config_listener_v3_Listener_InternalListenerConfig__Output { } /** - * [#next-free-field: 30] + * [#next-free-field: 34] */ export interface Listener { /** @@ -131,6 +144,7 @@ export interface Listener { * The address that the listener should listen on. In general, the address must be unique, though * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on * Linux as the actual port will be allocated by the OS. + * Required unless ``api_listener`` or ``listener_specifier`` is populated. */ 'address'?: (_envoy_config_core_v3_Address | null); /** @@ -144,7 +158,7 @@ export interface Listener { */ 'filter_chains'?: (_envoy_config_listener_v3_FilterChain)[]; /** - * If a connection is redirected using *iptables*, the port on which the proxy + * If a connection is redirected using ``iptables``, the port on which the proxy * receives it might be different from the original destination address. When this flag is set to * true, the listener hands off redirected connections to the listener associated with the * original destination address. If there is no listener associated with the original destination @@ -177,31 +191,30 @@ export interface Listener { * UDP Listener filters can be specified when the protocol in the listener socket address in * :ref:`protocol ` is :ref:`UDP * `. - * UDP listeners currently support a single filter. */ 'listener_filters'?: (_envoy_config_listener_v3_ListenerFilter)[]; /** * Whether the listener should be set as a transparent socket. * When this flag is set to true, connections can be redirected to the listener using an - * *iptables* *TPROXY* target, in which case the original source and destination addresses and + * ``iptables`` ``TPROXY`` target, in which case the original source and destination addresses and * ports are preserved on accepted connections. This flag should be used in combination with * :ref:`an original_dst ` :ref:`listener filter * ` to mark the connections' local addresses as * "restored." This can be used to hand off each redirected connection to another listener * associated with the connection's destination address. Direct connections to the socket without - * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are + * using ``TPROXY`` cannot be distinguished from connections redirected using ``TPROXY`` and are * therefore treated as if they were redirected. * When this flag is set to false, the listener's socket is explicitly reset as non-transparent. - * Setting this flag requires Envoy to run with the *CAP_NET_ADMIN* capability. + * Setting this flag requires Envoy to run with the ``CAP_NET_ADMIN`` capability. * When this flag is not set (default), the socket is not modified, i.e. the transparent option * is neither set nor reset. */ 'transparent'?: (_google_protobuf_BoolValue | null); /** - * Whether the listener should set the *IP_FREEBIND* socket option. When this + * Whether the listener should set the ``IP_FREEBIND`` socket option. When this * flag is set to true, listeners can be bound to an IP address that is not * configured on the system running Envoy. When this flag is set to false, the - * option *IP_FREEBIND* is disabled on the socket. When this flag is not set + * option ``IP_FREEBIND`` is disabled on the socket. When this flag is not set * (default), the socket is not modified, i.e. the option is neither enabled * nor disabled. */ @@ -225,13 +238,16 @@ export interface Listener { 'tcp_fast_open_queue_length'?: (_google_protobuf_UInt32Value | null); /** * Additional socket options that may not be present in Envoy source code or - * precompiled binaries. + * precompiled binaries. The socket options can be updated for a listener when + * :ref:`enable_reuse_port ` + * is `true`. Otherwise, if socket options change during a listener update the update will be rejected + * to make it clear that the options were not updated. */ 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; /** * The timeout to wait for all listener filters to complete operation. If the timeout is reached, * the accepted socket is closed without a connection being created unless - * `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the + * ``continue_on_listener_filters_timeout`` is set to true. Specify 0 to disable the * timeout. If not specified, a default timeout of 15s is used. */ 'listener_filters_timeout'?: (_google_protobuf_Duration | null); @@ -290,7 +306,7 @@ export interface Listener { */ 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig | null); /** - * Deprecated. Use `enable_reuse_port` instead. + * Deprecated. Use ``enable_reuse_port`` instead. */ 'reuse_port'?: (boolean); /** @@ -318,32 +334,34 @@ export interface Listener { /** * Used to represent an internal listener which does not listen on OSI L4 address but can be used by the * :ref:`envoy cluster ` to create a user space connection to. - * The internal listener acts as a tcp listener. It supports listener filters and network filter chains. - * The internal listener require :ref:`address ` has - * field `envoy_internal_address`. + * The internal listener acts as a TCP listener. It supports listener filters and network filter chains. + * Upstream clusters refer to the internal listeners by their :ref:`name + * `. :ref:`Address + * ` must not be set on the internal listeners. * - * There are some limitations are derived from the implementation. The known limitations include + * There are some limitations that are derived from the implementation. The known limitations include: * * * :ref:`ConnectionBalanceConfig ` is not - * allowed because both cluster connection and listener connection must be owned by the same dispatcher. + * allowed because both the cluster connection and the listener connection must be owned by the same dispatcher. * * :ref:`tcp_backlog_size ` * * :ref:`freebind ` * * :ref:`transparent ` - * [#not-implemented-hide:] */ 'internal_listener'?: (_envoy_config_listener_v3_Listener_InternalListenerConfig | null); /** * Optional prefix to use on listener stats. If empty, the stats will be rooted at - * `listener.
.`. If non-empty, stats will be rooted at - * `listener..`. + * ``listener.
.``. If non-empty, stats will be rooted at + * ``listener..``. */ 'stat_prefix'?: (string); /** - * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and + * When this flag is set to true, listeners set the ``SO_REUSEPORT`` socket option and * create one socket for each worker thread. This makes inbound connections * distribute among worker threads roughly evenly in cases where there are a high number * of connections. When this flag is set to false, all worker threads share one socket. This field - * defaults to true. + * defaults to true. The change of field will be rejected during an listener update when the + * runtime flag ``envoy.reloadable_features.enable_update_listener_socket_options`` is enabled. + * Otherwise, the update of this field will be ignored quietly. * * .. attention:: * @@ -361,17 +379,49 @@ export interface Listener { * is warned similar to macOS. It is left enabled for UDP with undefined behavior currently. */ 'enable_reuse_port'?: (_google_protobuf_BoolValue | null); + /** + * Enable MPTCP (multi-path TCP) on this listener. Clients will be allowed to establish + * MPTCP connections. Non-MPTCP clients will fall back to regular TCP. + */ + 'enable_mptcp'?: (boolean); + /** + * Whether the listener should limit connections based upon the value of + * :ref:`global_downstream_max_connections `. + */ + 'ignore_global_conn_limit'?: (boolean); + /** + * :ref:`Matcher API ` resolving the filter chain name from the + * network properties. This matcher is used as a replacement for the filter chain match condition + * :ref:`filter_chain_match + * `. If specified, all + * :ref:`filter_chains ` must have a + * non-empty and unique :ref:`name ` field + * and not specify :ref:`filter_chain_match + * ` field. + * + * .. note:: + * + * Once matched, each connection is permanently bound to its filter chain. + * If the matcher changes but the filter chain remains the same, the + * connections bound to the filter chain are not drained. If, however, the + * filter chain is removed or structurally modified, then the drain for its + * connections is initiated. + */ + 'filter_chain_matcher'?: (_xds_type_matcher_v3_Matcher | null); + /** + * The additional addresses the listener should listen on. The addresses must be unique across all + * listeners. Multiple addresses with port 0 can be supplied. When using multiple addresses in a single listener, + * all addresses use the same protocol, and multiple internal addresses are not supported. + */ + 'additional_addresses'?: (_envoy_config_listener_v3_AdditionalAddress)[]; /** * The exclusive listener type and the corresponding config. - * TODO(lambdai): https://github.com/envoyproxy/envoy/issues/15372 - * Will create and add TcpListenerConfig. Will add UdpListenerConfig and ApiListener. - * [#not-implemented-hide:] */ 'listener_specifier'?: "internal_listener"; } /** - * [#next-free-field: 30] + * [#next-free-field: 34] */ export interface Listener__Output { /** @@ -384,6 +434,7 @@ export interface Listener__Output { * The address that the listener should listen on. In general, the address must be unique, though * that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on * Linux as the actual port will be allocated by the OS. + * Required unless ``api_listener`` or ``listener_specifier`` is populated. */ 'address': (_envoy_config_core_v3_Address__Output | null); /** @@ -397,7 +448,7 @@ export interface Listener__Output { */ 'filter_chains': (_envoy_config_listener_v3_FilterChain__Output)[]; /** - * If a connection is redirected using *iptables*, the port on which the proxy + * If a connection is redirected using ``iptables``, the port on which the proxy * receives it might be different from the original destination address. When this flag is set to * true, the listener hands off redirected connections to the listener associated with the * original destination address. If there is no listener associated with the original destination @@ -430,31 +481,30 @@ export interface Listener__Output { * UDP Listener filters can be specified when the protocol in the listener socket address in * :ref:`protocol ` is :ref:`UDP * `. - * UDP listeners currently support a single filter. */ 'listener_filters': (_envoy_config_listener_v3_ListenerFilter__Output)[]; /** * Whether the listener should be set as a transparent socket. * When this flag is set to true, connections can be redirected to the listener using an - * *iptables* *TPROXY* target, in which case the original source and destination addresses and + * ``iptables`` ``TPROXY`` target, in which case the original source and destination addresses and * ports are preserved on accepted connections. This flag should be used in combination with * :ref:`an original_dst ` :ref:`listener filter * ` to mark the connections' local addresses as * "restored." This can be used to hand off each redirected connection to another listener * associated with the connection's destination address. Direct connections to the socket without - * using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are + * using ``TPROXY`` cannot be distinguished from connections redirected using ``TPROXY`` and are * therefore treated as if they were redirected. * When this flag is set to false, the listener's socket is explicitly reset as non-transparent. - * Setting this flag requires Envoy to run with the *CAP_NET_ADMIN* capability. + * Setting this flag requires Envoy to run with the ``CAP_NET_ADMIN`` capability. * When this flag is not set (default), the socket is not modified, i.e. the transparent option * is neither set nor reset. */ 'transparent': (_google_protobuf_BoolValue__Output | null); /** - * Whether the listener should set the *IP_FREEBIND* socket option. When this + * Whether the listener should set the ``IP_FREEBIND`` socket option. When this * flag is set to true, listeners can be bound to an IP address that is not * configured on the system running Envoy. When this flag is set to false, the - * option *IP_FREEBIND* is disabled on the socket. When this flag is not set + * option ``IP_FREEBIND`` is disabled on the socket. When this flag is not set * (default), the socket is not modified, i.e. the option is neither enabled * nor disabled. */ @@ -478,13 +528,16 @@ export interface Listener__Output { 'tcp_fast_open_queue_length': (_google_protobuf_UInt32Value__Output | null); /** * Additional socket options that may not be present in Envoy source code or - * precompiled binaries. + * precompiled binaries. The socket options can be updated for a listener when + * :ref:`enable_reuse_port ` + * is `true`. Otherwise, if socket options change during a listener update the update will be rejected + * to make it clear that the options were not updated. */ 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; /** * The timeout to wait for all listener filters to complete operation. If the timeout is reached, * the accepted socket is closed without a connection being created unless - * `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the + * ``continue_on_listener_filters_timeout`` is set to true. Specify 0 to disable the * timeout. If not specified, a default timeout of 15s is used. */ 'listener_filters_timeout': (_google_protobuf_Duration__Output | null); @@ -543,7 +596,7 @@ export interface Listener__Output { */ 'connection_balance_config': (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output | null); /** - * Deprecated. Use `enable_reuse_port` instead. + * Deprecated. Use ``enable_reuse_port`` instead. */ 'reuse_port': (boolean); /** @@ -571,32 +624,34 @@ export interface Listener__Output { /** * Used to represent an internal listener which does not listen on OSI L4 address but can be used by the * :ref:`envoy cluster ` to create a user space connection to. - * The internal listener acts as a tcp listener. It supports listener filters and network filter chains. - * The internal listener require :ref:`address ` has - * field `envoy_internal_address`. + * The internal listener acts as a TCP listener. It supports listener filters and network filter chains. + * Upstream clusters refer to the internal listeners by their :ref:`name + * `. :ref:`Address + * ` must not be set on the internal listeners. * - * There are some limitations are derived from the implementation. The known limitations include + * There are some limitations that are derived from the implementation. The known limitations include: * * * :ref:`ConnectionBalanceConfig ` is not - * allowed because both cluster connection and listener connection must be owned by the same dispatcher. + * allowed because both the cluster connection and the listener connection must be owned by the same dispatcher. * * :ref:`tcp_backlog_size ` * * :ref:`freebind ` * * :ref:`transparent ` - * [#not-implemented-hide:] */ 'internal_listener'?: (_envoy_config_listener_v3_Listener_InternalListenerConfig__Output | null); /** * Optional prefix to use on listener stats. If empty, the stats will be rooted at - * `listener.
.`. If non-empty, stats will be rooted at - * `listener..`. + * ``listener.
.``. If non-empty, stats will be rooted at + * ``listener..``. */ 'stat_prefix': (string); /** - * When this flag is set to true, listeners set the *SO_REUSEPORT* socket option and + * When this flag is set to true, listeners set the ``SO_REUSEPORT`` socket option and * create one socket for each worker thread. This makes inbound connections * distribute among worker threads roughly evenly in cases where there are a high number * of connections. When this flag is set to false, all worker threads share one socket. This field - * defaults to true. + * defaults to true. The change of field will be rejected during an listener update when the + * runtime flag ``envoy.reloadable_features.enable_update_listener_socket_options`` is enabled. + * Otherwise, the update of this field will be ignored quietly. * * .. attention:: * @@ -614,11 +669,43 @@ export interface Listener__Output { * is warned similar to macOS. It is left enabled for UDP with undefined behavior currently. */ 'enable_reuse_port': (_google_protobuf_BoolValue__Output | null); + /** + * Enable MPTCP (multi-path TCP) on this listener. Clients will be allowed to establish + * MPTCP connections. Non-MPTCP clients will fall back to regular TCP. + */ + 'enable_mptcp': (boolean); + /** + * Whether the listener should limit connections based upon the value of + * :ref:`global_downstream_max_connections `. + */ + 'ignore_global_conn_limit': (boolean); + /** + * :ref:`Matcher API ` resolving the filter chain name from the + * network properties. This matcher is used as a replacement for the filter chain match condition + * :ref:`filter_chain_match + * `. If specified, all + * :ref:`filter_chains ` must have a + * non-empty and unique :ref:`name ` field + * and not specify :ref:`filter_chain_match + * ` field. + * + * .. note:: + * + * Once matched, each connection is permanently bound to its filter chain. + * If the matcher changes but the filter chain remains the same, the + * connections bound to the filter chain are not drained. If, however, the + * filter chain is removed or structurally modified, then the drain for its + * connections is initiated. + */ + 'filter_chain_matcher': (_xds_type_matcher_v3_Matcher__Output | null); + /** + * The additional addresses the listener should listen on. The addresses must be unique across all + * listeners. Multiple addresses with port 0 can be supplied. When using multiple addresses in a single listener, + * all addresses use the same protocol, and multiple internal addresses are not supported. + */ + 'additional_addresses': (_envoy_config_listener_v3_AdditionalAddress__Output)[]; /** * The exclusive listener type and the corresponding config. - * TODO(lambdai): https://github.com/envoyproxy/envoy/issues/15372 - * Will create and add TcpListenerConfig. Will add UdpListenerConfig and ApiListener. - * [#not-implemented-hide:] */ 'listener_specifier': "internal_listener"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts index 590ca8ed3..a1e7a10ca 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerCollection.ts @@ -3,7 +3,7 @@ import type { CollectionEntry as _xds_core_v3_CollectionEntry, CollectionEntry__Output as _xds_core_v3_CollectionEntry__Output } from '../../../../xds/core/v3/CollectionEntry'; /** - * Listener list collections. Entries are *Listener* resources or references. + * Listener list collections. Entries are ``Listener`` resources or references. * [#not-implemented-hide:] */ export interface ListenerCollection { @@ -11,7 +11,7 @@ export interface ListenerCollection { } /** - * Listener list collections. Entries are *Listener* resources or references. + * Listener list collections. Entries are ``Listener`` resources or references. * [#not-implemented-hide:] */ export interface ListenerCollection__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts index be7242849..5844c4bbe 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerFilter.ts @@ -2,11 +2,14 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { ListenerFilterChainMatchPredicate as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate, ListenerFilterChainMatchPredicate__Output as _envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output } from '../../../../envoy/config/listener/v3/ListenerFilterChainMatchPredicate'; +import type { ExtensionConfigSource as _envoy_config_core_v3_ExtensionConfigSource, ExtensionConfigSource__Output as _envoy_config_core_v3_ExtensionConfigSource__Output } from '../../../../envoy/config/core/v3/ExtensionConfigSource'; +/** + * [#next-free-field: 6] + */ export interface ListenerFilter { /** - * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. + * The name of the filter configuration. */ 'name'?: (string); /** @@ -21,13 +24,21 @@ export interface ListenerFilter { * for further examples. */ 'filter_disabled'?: (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate | null); - 'config_type'?: "typed_config"; + /** + * Configuration source specifier for an extension configuration discovery + * service. In case of a failure and without the default configuration, the + * listener closes the connections. + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource | null); + 'config_type'?: "typed_config"|"config_discovery"; } +/** + * [#next-free-field: 6] + */ export interface ListenerFilter__Output { /** - * The name of the filter to instantiate. The name must match a - * :ref:`supported filter `. + * The name of the filter configuration. */ 'name': (string); /** @@ -42,5 +53,11 @@ export interface ListenerFilter__Output { * for further examples. */ 'filter_disabled': (_envoy_config_listener_v3_ListenerFilterChainMatchPredicate__Output | null); - 'config_type': "typed_config"; + /** + * Configuration source specifier for an extension configuration discovery + * service. In case of a failure and without the default configuration, the + * listener closes the connections. + */ + 'config_discovery'?: (_envoy_config_core_v3_ExtensionConfigSource__Output | null); + 'config_type': "typed_config"|"config_discovery"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerManager.ts new file mode 100644 index 000000000..d27a10c68 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ListenerManager.ts @@ -0,0 +1,18 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + + +/** + * A placeholder proto so that users can explicitly configure the standard + * Listener Manager via the bootstrap's :ref:`listener_manager `. + * [#not-implemented-hide:] + */ +export interface ListenerManager { +} + +/** + * A placeholder proto so that users can explicitly configure the standard + * Listener Manager via the bootstrap's :ref:`listener_manager `. + * [#not-implemented-hide:] + */ +export interface ListenerManager__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts index 5e01a772f..e88ab26a9 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/QuicProtocolOptions.ts @@ -8,18 +8,21 @@ import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig /** * Configuration specific to the UDP QUIC listener. - * [#next-free-field: 8] + * [#next-free-field: 10] */ export interface QuicProtocolOptions { 'quic_protocol_options'?: (_envoy_config_core_v3_QuicProtocolOptions | null); /** * Maximum number of milliseconds that connection will be alive when there is - * no network activity. 300000ms if not specified. + * no network activity. + * + * If it is less than 1ms, Envoy will use 1ms. 300000ms if not specified. */ 'idle_timeout'?: (_google_protobuf_Duration | null); /** * Connection timeout in milliseconds before the crypto handshake is finished. - * 20000ms if not specified. + * + * If it is less than 5000ms, Envoy will use 5000ms. 20000ms if not specified. */ 'crypto_handshake_timeout'?: (_google_protobuf_Duration | null); /** @@ -38,33 +41,49 @@ export interface QuicProtocolOptions { */ 'packets_to_read_to_connection_count_ratio'?: (_google_protobuf_UInt32Value | null); /** - * Configure which implementation of `quic::QuicCryptoClientStreamBase` to be used for this listener. + * Configure which implementation of ``quic::QuicCryptoClientStreamBase`` to be used for this listener. * If not specified the :ref:`QUICHE default one configured by ` will be used. * [#extension-category: envoy.quic.server.crypto_stream] */ 'crypto_stream_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); /** - * Configure which implementation of `quic::ProofSource` to be used for this listener. + * Configure which implementation of ``quic::ProofSource`` to be used for this listener. * If not specified the :ref:`default one configured by ` will be used. * [#extension-category: envoy.quic.proof_source] */ 'proof_source_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Config which implementation of ``quic::ConnectionIdGeneratorInterface`` to be used for this listener. + * If not specified the :ref:`default one configured by ` will be used. + * [#extension-category: envoy.quic.connection_id_generator] + */ + 'connection_id_generator_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Configure the server's preferred address to advertise so that client can migrate to it. See :ref:`example ` which configures a pair of v4 and v6 preferred addresses. + * The current QUICHE implementation will advertise only one of the preferred IPv4 and IPv6 addresses based on the address family the client initially connects with, and only if the client is also QUICHE-based. + * If not specified, Envoy will not advertise any server's preferred address. + * [#extension-category: envoy.quic.server_preferred_address] + */ + 'server_preferred_address_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); } /** * Configuration specific to the UDP QUIC listener. - * [#next-free-field: 8] + * [#next-free-field: 10] */ export interface QuicProtocolOptions__Output { 'quic_protocol_options': (_envoy_config_core_v3_QuicProtocolOptions__Output | null); /** * Maximum number of milliseconds that connection will be alive when there is - * no network activity. 300000ms if not specified. + * no network activity. + * + * If it is less than 1ms, Envoy will use 1ms. 300000ms if not specified. */ 'idle_timeout': (_google_protobuf_Duration__Output | null); /** * Connection timeout in milliseconds before the crypto handshake is finished. - * 20000ms if not specified. + * + * If it is less than 5000ms, Envoy will use 5000ms. 20000ms if not specified. */ 'crypto_handshake_timeout': (_google_protobuf_Duration__Output | null); /** @@ -83,15 +102,28 @@ export interface QuicProtocolOptions__Output { */ 'packets_to_read_to_connection_count_ratio': (_google_protobuf_UInt32Value__Output | null); /** - * Configure which implementation of `quic::QuicCryptoClientStreamBase` to be used for this listener. + * Configure which implementation of ``quic::QuicCryptoClientStreamBase`` to be used for this listener. * If not specified the :ref:`QUICHE default one configured by ` will be used. * [#extension-category: envoy.quic.server.crypto_stream] */ 'crypto_stream_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); /** - * Configure which implementation of `quic::ProofSource` to be used for this listener. + * Configure which implementation of ``quic::ProofSource`` to be used for this listener. * If not specified the :ref:`default one configured by ` will be used. * [#extension-category: envoy.quic.proof_source] */ 'proof_source_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Config which implementation of ``quic::ConnectionIdGeneratorInterface`` to be used for this listener. + * If not specified the :ref:`default one configured by ` will be used. + * [#extension-category: envoy.quic.connection_id_generator] + */ + 'connection_id_generator_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Configure the server's preferred address to advertise so that client can migrate to it. See :ref:`example ` which configures a pair of v4 and v6 preferred addresses. + * The current QUICHE implementation will advertise only one of the preferred IPv4 and IPv6 addresses based on the address family the client initially connects with, and only if the client is also QUICHE-based. + * If not specified, Envoy will not advertise any server's preferred address. + * [#extension-category: envoy.quic.server_preferred_address] + */ + 'server_preferred_address_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts index f4c220e20..63f9666e0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/UdpListenerConfig.ts @@ -2,9 +2,10 @@ import type { UdpSocketConfig as _envoy_config_core_v3_UdpSocketConfig, UdpSocketConfig__Output as _envoy_config_core_v3_UdpSocketConfig__Output } from '../../../../envoy/config/core/v3/UdpSocketConfig'; import type { QuicProtocolOptions as _envoy_config_listener_v3_QuicProtocolOptions, QuicProtocolOptions__Output as _envoy_config_listener_v3_QuicProtocolOptions__Output } from '../../../../envoy/config/listener/v3/QuicProtocolOptions'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; /** - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface UdpListenerConfig { /** @@ -17,16 +18,21 @@ export interface UdpListenerConfig { /** * Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set * to the default object to enable QUIC without modifying any additional options. - * - * .. warning:: - * QUIC support is currently alpha and should be used with caution. Please - * see :ref:`here ` for details. */ 'quic_options'?: (_envoy_config_listener_v3_QuicProtocolOptions | null); + /** + * Configuration for the UDP packet writer. If empty, HTTP/3 will use GSO if available + * (:ref:`UdpDefaultWriterFactory `) + * or the default kernel sendmsg if not, + * (:ref:`UdpDefaultWriterFactory `) + * and raw UDP will use kernel sendmsg. + * [#extension-category: envoy.udp_packet_writer] + */ + 'udp_packet_packet_writer_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); } /** - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface UdpListenerConfig__Output { /** @@ -39,10 +45,15 @@ export interface UdpListenerConfig__Output { /** * Configuration for QUIC protocol. If empty, QUIC will not be enabled on this listener. Set * to the default object to enable QUIC without modifying any additional options. - * - * .. warning:: - * QUIC support is currently alpha and should be used with caution. Please - * see :ref:`here ` for details. */ 'quic_options': (_envoy_config_listener_v3_QuicProtocolOptions__Output | null); + /** + * Configuration for the UDP packet writer. If empty, HTTP/3 will use GSO if available + * (:ref:`UdpDefaultWriterFactory `) + * or the default kernel sendmsg if not, + * (:ref:`UdpDefaultWriterFactory `) + * and raw UDP will use kernel sendmsg. + * [#extension-category: envoy.udp_packet_writer] + */ + 'udp_packet_packet_writer_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ValidationListenerManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ValidationListenerManager.ts new file mode 100644 index 000000000..3b6ccc3a6 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/ValidationListenerManager.ts @@ -0,0 +1,18 @@ +// Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto + + +/** + * A placeholder proto so that users can explicitly configure the standard + * Validation Listener Manager via the bootstrap's :ref:`listener_manager `. + * [#not-implemented-hide:] + */ +export interface ValidationListenerManager { +} + +/** + * A placeholder proto so that users can explicitly configure the standard + * Validation Listener Manager via the bootstrap's :ref:`listener_manager `. + * [#not-implemented-hide:] + */ +export interface ValidationListenerManager__Output { +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts index 14724412a..3742eeb6b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ClusterSpecifierPlugin.ts @@ -1,4 +1,4 @@ -// Original file: deps/envoy-api/envoy/config/route/v3/route.proto +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; @@ -10,6 +10,14 @@ export interface ClusterSpecifierPlugin { * The name of the plugin and its opaque configuration. */ 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * If is_optional is not set or is set to false and the plugin defined by this message is not a + * supported type, the containing resource is NACKed. If is_optional is set to true, the resource + * would not be NACKed for this reason. In this case, routes referencing this plugin's name would + * not be treated as an illegal configuration, but would result in a failure if the route is + * selected. + */ + 'is_optional'?: (boolean); } /** @@ -20,4 +28,12 @@ export interface ClusterSpecifierPlugin__Output { * The name of the plugin and its opaque configuration. */ 'extension': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * If is_optional is not set or is set to false and the plugin defined by this message is not a + * supported type, the containing resource is NACKed. If is_optional is set to true, the resource + * would not be NACKed for this reason. In this case, routes referencing this plugin's name would + * not be treated as an illegal configuration, but would result in a failure if the route is + * selected. + */ + 'is_optional': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts index 40634448a..8a74b0658 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/CorsPolicy.ts @@ -5,23 +5,31 @@ import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractiona import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; /** - * [#next-free-field: 12] + * Cors policy configuration. + * + * .. attention:: + * + * This message has been deprecated. Please use + * :ref:`CorsPolicy in filter extension ` + * as as alternative. + * + * [#next-free-field: 13] */ export interface CorsPolicy { /** - * Specifies the content for the *access-control-allow-methods* header. + * Specifies the content for the ``access-control-allow-methods`` header. */ 'allow_methods'?: (string); /** - * Specifies the content for the *access-control-allow-headers* header. + * Specifies the content for the ``access-control-allow-headers`` header. */ 'allow_headers'?: (string); /** - * Specifies the content for the *access-control-expose-headers* header. + * Specifies the content for the ``access-control-expose-headers`` header. */ 'expose_headers'?: (string); /** - * Specifies the content for the *access-control-max-age* header. + * Specifies the content for the ``access-control-max-age`` header. */ 'max_age'?: (string); /** @@ -47,7 +55,7 @@ export interface CorsPolicy { * * If :ref:`runtime_key ` is specified, * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate - * and track the request's *Origin* to determine if it's valid but will not enforce any policies. + * and track the request's ``Origin`` to determine if it's valid but will not enforce any policies. */ 'shadow_enabled'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); /** @@ -55,27 +63,42 @@ export interface CorsPolicy { * string matchers match. */ 'allow_origin_string_match'?: (_envoy_type_matcher_v3_StringMatcher)[]; + /** + * Specify whether allow requests whose target server's IP address is more private than that from + * which the request initiator was fetched. + * + * More details refer to https://developer.chrome.com/blog/private-network-access-preflight. + */ + 'allow_private_network_access'?: (_google_protobuf_BoolValue | null); 'enabled_specifier'?: "filter_enabled"; } /** - * [#next-free-field: 12] + * Cors policy configuration. + * + * .. attention:: + * + * This message has been deprecated. Please use + * :ref:`CorsPolicy in filter extension ` + * as as alternative. + * + * [#next-free-field: 13] */ export interface CorsPolicy__Output { /** - * Specifies the content for the *access-control-allow-methods* header. + * Specifies the content for the ``access-control-allow-methods`` header. */ 'allow_methods': (string); /** - * Specifies the content for the *access-control-allow-headers* header. + * Specifies the content for the ``access-control-allow-headers`` header. */ 'allow_headers': (string); /** - * Specifies the content for the *access-control-expose-headers* header. + * Specifies the content for the ``access-control-expose-headers`` header. */ 'expose_headers': (string); /** - * Specifies the content for the *access-control-max-age* header. + * Specifies the content for the ``access-control-max-age`` header. */ 'max_age': (string); /** @@ -101,7 +124,7 @@ export interface CorsPolicy__Output { * * If :ref:`runtime_key ` is specified, * Envoy will lookup the runtime key to get the percentage of requests for which it will evaluate - * and track the request's *Origin* to determine if it's valid but will not enforce any policies. + * and track the request's ``Origin`` to determine if it's valid but will not enforce any policies. */ 'shadow_enabled': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); /** @@ -109,5 +132,12 @@ export interface CorsPolicy__Output { * string matchers match. */ 'allow_origin_string_match': (_envoy_type_matcher_v3_StringMatcher__Output)[]; + /** + * Specify whether allow requests whose target server's IP address is more private than that from + * which the request initiator was fetched. + * + * More details refer to https://developer.chrome.com/blog/private-network-access-preflight. + */ + 'allow_private_network_access': (_google_protobuf_BoolValue__Output | null); 'enabled_specifier': "filter_enabled"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts index 794ae510a..7c0f9ee67 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/DirectResponseAction.ts @@ -13,7 +13,7 @@ export interface DirectResponseAction { * * .. note:: * - * Headers can be specified using *response_headers_to_add* in the enclosing + * Headers can be specified using ``response_headers_to_add`` in the enclosing * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. */ @@ -31,7 +31,7 @@ export interface DirectResponseAction__Output { * * .. note:: * - * Headers can be specified using *response_headers_to_add* in the enclosing + * Headers can be specified using ``response_headers_to_add`` in the enclosing * :ref:`envoy_v3_api_msg_config.route.v3.Route`, :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` or * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts index 2c960419c..e5a3b5778 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/FilterConfig.ts @@ -9,7 +9,6 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ * :ref:`Route.typed_per_filter_config`, * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` * to add additional flags to the filter. - * [#not-implemented-hide:] */ export interface FilterConfig { /** @@ -22,6 +21,23 @@ export interface FilterConfig { * than rejecting the config. */ 'is_optional'?: (boolean); + /** + * If true, the filter is disabled in the route or virtual host and the ``config`` field is ignored. + * + * .. note:: + * + * This field will take effect when the request arrive and filter chain is created for the request. + * If initial route is selected for the request and a filter is disabled in the initial route, then + * the filter will not be added to the filter chain. + * And if the request is mutated later and re-match to another route, the disabled filter by the + * initial route will not be added back to the filter chain because the filter chain is already + * created and it is too late to change the chain. + * + * This field only make sense for the downstream HTTP filters for now. + * + * [#not-implemented-hide:] + */ + 'disabled'?: (boolean); } /** @@ -31,7 +47,6 @@ export interface FilterConfig { * :ref:`Route.typed_per_filter_config`, * or :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` * to add additional flags to the filter. - * [#not-implemented-hide:] */ export interface FilterConfig__Output { /** @@ -44,4 +59,21 @@ export interface FilterConfig__Output { * than rejecting the config. */ 'is_optional': (boolean); + /** + * If true, the filter is disabled in the route or virtual host and the ``config`` field is ignored. + * + * .. note:: + * + * This field will take effect when the request arrive and filter chain is created for the request. + * If initial route is selected for the request and a filter is disabled in the initial route, then + * the filter will not be added to the filter chain. + * And if the request is mutated later and re-match to another route, the disabled filter by the + * initial route will not be added back to the filter chain because the filter chain is already + * created and it is too late to change the chain. + * + * This field only make sense for the downstream HTTP filters for now. + * + * [#not-implemented-hide:] + */ + 'disabled': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts index bde8f28cd..e073a8f13 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts @@ -8,19 +8,21 @@ import type { Long } from '@grpc/proto-loader'; /** * .. attention:: * - * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 *Host* - * header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 ``Host`` + * header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. * * .. attention:: * - * To route on HTTP method, use the special HTTP/2 *:method* header. This works for both + * To route on HTTP method, use the special HTTP/2 ``:method`` header. This works for both * HTTP/1 and HTTP/2 as Envoy normalizes headers. E.g., * * .. code-block:: json * * { * "name": ":method", - * "exact_match": "POST" + * "string_match": { + * "exact": "POST" + * } * } * * .. attention:: @@ -30,7 +32,7 @@ import type { Long } from '@grpc/proto-loader'; * value. * * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] - * [#next-free-field: 14] + * [#next-free-field: 15] */ export interface HeaderMatcher { /** @@ -52,8 +54,8 @@ export interface HeaderMatcher { * * Examples: * - * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, - * "-1somestring" + * * For range [-10,0), route will match for header value -1, but not for 0, ``somestring``, 10.9, + * ``-1somestring`` */ 'range_match'?: (_envoy_type_v3_Int64Range | null); /** @@ -66,7 +68,7 @@ export interface HeaderMatcher { * * Examples: * - * * The regex ``\d{3}`` does not match the value *1234*, so it will match when inverted. + * * The regex ``\d{3}`` does not match the value ``1234``, so it will match when inverted. * * The range [-10,0) will match the value -1, so it will not match when inverted. */ 'invert_match'?: (boolean); @@ -77,7 +79,7 @@ export interface HeaderMatcher { * * Examples: * - * * The prefix *abcd* matches the value *abcdxyz*, but not for *abcxyz*. + * * The prefix ``abcd`` matches the value ``abcdxyz``, but not for ``abcxyz``. */ 'prefix_match'?: (string); /** @@ -87,7 +89,7 @@ export interface HeaderMatcher { * * Examples: * - * * The suffix *abcd* matches the value *xyzabcd*, but not for *xyzbcd*. + * * The suffix ``abcd`` matches the value ``xyzabcd``, but not for ``xyzbcd``. */ 'suffix_match'?: (string); /** @@ -105,13 +107,42 @@ export interface HeaderMatcher { * * Examples: * - * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. + * * The value ``abcd`` matches the value ``xyzabcdpqr``, but not for ``xyzbcdpqr``. */ 'contains_match'?: (string); /** * If specified, header match will be performed based on the string match of the header value. */ 'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null); + /** + * If specified, for any header match rule, if the header match rule specified header + * does not exist, this header value will be treated as empty. Defaults to false. + * + * Examples: + * + * * The header match rule specified header "header1" to range match of [0, 10], + * :ref:`invert_match ` + * is set to true and :ref:`treat_missing_header_as_empty ` + * is set to true; The "header1" header is not present. The match rule will + * treat the "header1" as an empty header. The empty header does not match the range, + * so it will match when inverted. + * * The header match rule specified header "header2" to range match of [0, 10], + * :ref:`invert_match ` + * is set to true and :ref:`treat_missing_header_as_empty ` + * is set to false; The "header2" header is not present and the header + * matcher rule for "header2" will be ignored so it will not match. + * * The header match rule specified header "header3" to a string regex match + * ``^$`` which means an empty string, and + * :ref:`treat_missing_header_as_empty ` + * is set to true; The "header3" header is not present. + * The match rule will treat the "header3" header as an empty header so it will match. + * * The header match rule specified header "header4" to a string regex match + * ``^$`` which means an empty string, and + * :ref:`treat_missing_header_as_empty ` + * is set to false; The "header4" header is not present. + * The match rule for "header4" will be ignored so it will not match. + */ + 'treat_missing_header_as_empty'?: (boolean); /** * Specifies how the header match will be performed to route the request. */ @@ -121,19 +152,21 @@ export interface HeaderMatcher { /** * .. attention:: * - * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 *Host* - * header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 ``Host`` + * header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. * * .. attention:: * - * To route on HTTP method, use the special HTTP/2 *:method* header. This works for both + * To route on HTTP method, use the special HTTP/2 ``:method`` header. This works for both * HTTP/1 and HTTP/2 as Envoy normalizes headers. E.g., * * .. code-block:: json * * { * "name": ":method", - * "exact_match": "POST" + * "string_match": { + * "exact": "POST" + * } * } * * .. attention:: @@ -143,7 +176,7 @@ export interface HeaderMatcher { * value. * * [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] - * [#next-free-field: 14] + * [#next-free-field: 15] */ export interface HeaderMatcher__Output { /** @@ -165,8 +198,8 @@ export interface HeaderMatcher__Output { * * Examples: * - * * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, - * "-1somestring" + * * For range [-10,0), route will match for header value -1, but not for 0, ``somestring``, 10.9, + * ``-1somestring`` */ 'range_match'?: (_envoy_type_v3_Int64Range__Output | null); /** @@ -179,7 +212,7 @@ export interface HeaderMatcher__Output { * * Examples: * - * * The regex ``\d{3}`` does not match the value *1234*, so it will match when inverted. + * * The regex ``\d{3}`` does not match the value ``1234``, so it will match when inverted. * * The range [-10,0) will match the value -1, so it will not match when inverted. */ 'invert_match': (boolean); @@ -190,7 +223,7 @@ export interface HeaderMatcher__Output { * * Examples: * - * * The prefix *abcd* matches the value *abcdxyz*, but not for *abcxyz*. + * * The prefix ``abcd`` matches the value ``abcdxyz``, but not for ``abcxyz``. */ 'prefix_match'?: (string); /** @@ -200,7 +233,7 @@ export interface HeaderMatcher__Output { * * Examples: * - * * The suffix *abcd* matches the value *xyzabcd*, but not for *xyzbcd*. + * * The suffix ``abcd`` matches the value ``xyzabcd``, but not for ``xyzbcd``. */ 'suffix_match'?: (string); /** @@ -218,13 +251,42 @@ export interface HeaderMatcher__Output { * * Examples: * - * * The value *abcd* matches the value *xyzabcdpqr*, but not for *xyzbcdpqr*. + * * The value ``abcd`` matches the value ``xyzabcdpqr``, but not for ``xyzbcdpqr``. */ 'contains_match'?: (string); /** * If specified, header match will be performed based on the string match of the header value. */ 'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null); + /** + * If specified, for any header match rule, if the header match rule specified header + * does not exist, this header value will be treated as empty. Defaults to false. + * + * Examples: + * + * * The header match rule specified header "header1" to range match of [0, 10], + * :ref:`invert_match ` + * is set to true and :ref:`treat_missing_header_as_empty ` + * is set to true; The "header1" header is not present. The match rule will + * treat the "header1" as an empty header. The empty header does not match the range, + * so it will match when inverted. + * * The header match rule specified header "header2" to range match of [0, 10], + * :ref:`invert_match ` + * is set to true and :ref:`treat_missing_header_as_empty ` + * is set to false; The "header2" header is not present and the header + * matcher rule for "header2" will be ignored so it will not match. + * * The header match rule specified header "header3" to a string regex match + * ``^$`` which means an empty string, and + * :ref:`treat_missing_header_as_empty ` + * is set to true; The "header3" header is not present. + * The match rule will treat the "header3" header as an empty header so it will match. + * * The header match rule specified header "header4" to a string regex match + * ``^$`` which means an empty string, and + * :ref:`treat_missing_header_as_empty ` + * is set to false; The "header4" header is not present. + * The match rule for "header4" will be ignored so it will not match. + */ + 'treat_missing_header_as_empty': (boolean); /** * Specifies how the header match will be performed to route the request. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts index d511259b0..b98b6329b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/QueryParameterMatcher.ts @@ -10,7 +10,7 @@ import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatch export interface QueryParameterMatcher { /** * Specifies the name of a key that must be present in the requested - * *path*'s query string. + * ``path``'s query string. */ 'name'?: (string); /** @@ -32,7 +32,7 @@ export interface QueryParameterMatcher { export interface QueryParameterMatcher__Output { /** * Specifies the name of a key that must be present in the requested - * *path*'s query string. + * ``path``'s query string. */ 'name': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts index f1d49537c..cd47e471a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts @@ -5,9 +5,10 @@ import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher'; import type { MetadataKey as _envoy_type_metadata_v3_MetadataKey, MetadataKey__Output as _envoy_type_metadata_v3_MetadataKey__Output } from '../../../../envoy/type/metadata/v3/MetadataKey'; +import type { QueryParameterMatcher as _envoy_config_route_v3_QueryParameterMatcher, QueryParameterMatcher__Output as _envoy_config_route_v3_QueryParameterMatcher__Output } from '../../../../envoy/config/route/v3/QueryParameterMatcher'; /** - * [#next-free-field: 10] + * [#next-free-field: 12] */ export interface _envoy_config_route_v3_RateLimit_Action { /** @@ -47,14 +48,28 @@ export interface _envoy_config_route_v3_RateLimit_Action { 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData | null); /** * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + * + * :ref:`HTTP matching input functions ` are + * permitted as descriptor extensions. The input functions are only + * looked up if there is no rate limit descriptor extension matching + * the type URL. + * * [#extension-category: envoy.rate_limit_descriptors] */ 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig | null); - 'action_specifier'?: "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; + /** + * Rate limit on masked remote address. + */ + 'masked_remote_address'?: (_envoy_config_route_v3_RateLimit_Action_MaskedRemoteAddress | null); + /** + * Rate limit on the existence of query parameters. + */ + 'query_parameter_value_match'?: (_envoy_config_route_v3_RateLimit_Action_QueryParameterValueMatch | null); + 'action_specifier'?: "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"|"masked_remote_address"|"query_parameter_value_match"; } /** - * [#next-free-field: 10] + * [#next-free-field: 12] */ export interface _envoy_config_route_v3_RateLimit_Action__Output { /** @@ -94,10 +109,24 @@ export interface _envoy_config_route_v3_RateLimit_Action__Output { 'metadata'?: (_envoy_config_route_v3_RateLimit_Action_MetaData__Output | null); /** * Rate limit descriptor extension. See the rate limit descriptor extensions documentation. + * + * :ref:`HTTP matching input functions ` are + * permitted as descriptor extensions. The input functions are only + * looked up if there is no rate limit descriptor extension matching + * the type URL. + * * [#extension-category: envoy.rate_limit_descriptors] */ 'extension'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); - 'action_specifier': "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"; + /** + * Rate limit on masked remote address. + */ + 'masked_remote_address'?: (_envoy_config_route_v3_RateLimit_Action_MaskedRemoteAddress__Output | null); + /** + * Rate limit on the existence of query parameters. + */ + 'query_parameter_value_match'?: (_envoy_config_route_v3_RateLimit_Action_QueryParameterValueMatch__Output | null); + 'action_specifier': "source_cluster"|"destination_cluster"|"request_headers"|"remote_address"|"generic_key"|"header_value_match"|"dynamic_metadata"|"metadata"|"extension"|"masked_remote_address"|"query_parameter_value_match"; } /** @@ -164,7 +193,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData { */ 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); /** - * An optional value to use if *metadata_key* is empty. If not set and + * An optional value to use if ``metadata_key`` is empty. If not set and * no value is present under the metadata_key then no descriptor is generated. */ 'default_value'?: (string); @@ -192,7 +221,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output */ 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); /** - * An optional value to use if *metadata_key* is empty. If not set and + * An optional value to use if ``metadata_key`` is empty. If not set and * no value is present under the metadata_key then no descriptor is generated. */ 'default_value': (string); @@ -270,6 +299,10 @@ export interface _envoy_config_route_v3_RateLimit_Action_GenericKey__Output { * ("header_match", "") */ export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch { + /** + * The key to use in the descriptor entry. Defaults to ``header_match``. + */ + 'descriptor_key'?: (string); /** * The value to use in the descriptor entry. */ @@ -299,6 +332,10 @@ export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch { * ("header_match", "") */ export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Output { + /** + * The key to use in the descriptor entry. Defaults to ``header_match``. + */ + 'descriptor_key': (string); /** * The value to use in the descriptor entry. */ @@ -320,12 +357,67 @@ export interface _envoy_config_route_v3_RateLimit_Action_HeaderValueMatch__Outpu 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; } +/** + * The following descriptor entry is appended to the descriptor and is populated using the + * masked address from :ref:`x-forwarded-for `: + * + * .. code-block:: cpp + * + * ("masked_remote_address", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_MaskedRemoteAddress { + /** + * Length of prefix mask len for IPv4 (e.g. 0, 32). + * Defaults to 32 when unset. + * For example, trusted address from x-forwarded-for is ``192.168.1.1``, + * the descriptor entry is ("masked_remote_address", "192.168.1.1/32"); + * if mask len is 24, the descriptor entry is ("masked_remote_address", "192.168.1.0/24"). + */ + 'v4_prefix_mask_len'?: (_google_protobuf_UInt32Value | null); + /** + * Length of prefix mask len for IPv6 (e.g. 0, 128). + * Defaults to 128 when unset. + * For example, trusted address from x-forwarded-for is ``2001:abcd:ef01:2345:6789:abcd:ef01:234``, + * the descriptor entry is ("masked_remote_address", "2001:abcd:ef01:2345:6789:abcd:ef01:234/128"); + * if mask len is 64, the descriptor entry is ("masked_remote_address", "2001:abcd:ef01:2345::/64"). + */ + 'v6_prefix_mask_len'?: (_google_protobuf_UInt32Value | null); +} + +/** + * The following descriptor entry is appended to the descriptor and is populated using the + * masked address from :ref:`x-forwarded-for `: + * + * .. code-block:: cpp + * + * ("masked_remote_address", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_MaskedRemoteAddress__Output { + /** + * Length of prefix mask len for IPv4 (e.g. 0, 32). + * Defaults to 32 when unset. + * For example, trusted address from x-forwarded-for is ``192.168.1.1``, + * the descriptor entry is ("masked_remote_address", "192.168.1.1/32"); + * if mask len is 24, the descriptor entry is ("masked_remote_address", "192.168.1.0/24"). + */ + 'v4_prefix_mask_len': (_google_protobuf_UInt32Value__Output | null); + /** + * Length of prefix mask len for IPv6 (e.g. 0, 128). + * Defaults to 128 when unset. + * For example, trusted address from x-forwarded-for is ``2001:abcd:ef01:2345:6789:abcd:ef01:234``, + * the descriptor entry is ("masked_remote_address", "2001:abcd:ef01:2345:6789:abcd:ef01:234/128"); + * if mask len is 64, the descriptor entry is ("masked_remote_address", "2001:abcd:ef01:2345::/64"). + */ + 'v6_prefix_mask_len': (_google_protobuf_UInt32Value__Output | null); +} + /** * The following descriptor entry is appended when the metadata contains a key value: * * .. code-block:: cpp * * ("", "") + * [#next-free-field: 6] */ export interface _envoy_config_route_v3_RateLimit_Action_MetaData { /** @@ -338,14 +430,21 @@ export interface _envoy_config_route_v3_RateLimit_Action_MetaData { */ 'metadata_key'?: (_envoy_type_metadata_v3_MetadataKey | null); /** - * An optional value to use if *metadata_key* is empty. If not set and - * no value is present under the metadata_key then no descriptor is generated. + * An optional value to use if ``metadata_key`` is empty. If not set and + * no value is present under the metadata_key then ``skip_if_absent`` is followed to + * skip calling the rate limiting service or skip the descriptor. */ 'default_value'?: (string); /** * Source of metadata */ 'source'?: (_envoy_config_route_v3_RateLimit_Action_MetaData_Source | keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); + /** + * If set to true, Envoy skips the descriptor while calling rate limiting service + * when ``metadata_key`` is empty and ``default_value`` is not set. By default it skips calling the + * rate limiting service in that case. + */ + 'skip_if_absent'?: (boolean); } /** @@ -354,6 +453,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_MetaData { * .. code-block:: cpp * * ("", "") + * [#next-free-field: 6] */ export interface _envoy_config_route_v3_RateLimit_Action_MetaData__Output { /** @@ -366,14 +466,21 @@ export interface _envoy_config_route_v3_RateLimit_Action_MetaData__Output { */ 'metadata_key': (_envoy_type_metadata_v3_MetadataKey__Output | null); /** - * An optional value to use if *metadata_key* is empty. If not set and - * no value is present under the metadata_key then no descriptor is generated. + * An optional value to use if ``metadata_key`` is empty. If not set and + * no value is present under the metadata_key then ``skip_if_absent`` is followed to + * skip calling the rate limiting service or skip the descriptor. */ 'default_value': (string); /** * Source of metadata */ 'source': (keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); + /** + * If set to true, Envoy skips the descriptor while calling rate limiting service + * when ``metadata_key`` is empty and ``default_value`` is not set. By default it skips calling the + * rate limiting service in that case. + */ + 'skip_if_absent': (boolean); } export interface _envoy_config_route_v3_RateLimit_Override { @@ -392,6 +499,72 @@ export interface _envoy_config_route_v3_RateLimit_Override__Output { 'override_specifier': "dynamic_metadata"; } +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("query_match", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_QueryParameterValueMatch { + /** + * The key to use in the descriptor entry. Defaults to ``query_match``. + */ + 'descriptor_key'?: (string); + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value'?: (string); + /** + * If set to true, the action will append a descriptor entry when the + * request matches the headers. If set to false, the action will append a + * descriptor entry when the request does not match the headers. The + * default value is true. + */ + 'expect_match'?: (_google_protobuf_BoolValue | null); + /** + * Specifies a set of query parameters that the rate limit action should match + * on. The action will check the request’s query parameters against all the + * specified query parameters in the config. A match will happen if all the + * query parameters in the config are present in the request with the same values + * (or based on presence if the value field is not in the config). + */ + 'query_parameters'?: (_envoy_config_route_v3_QueryParameterMatcher)[]; +} + +/** + * The following descriptor entry is appended to the descriptor: + * + * .. code-block:: cpp + * + * ("query_match", "") + */ +export interface _envoy_config_route_v3_RateLimit_Action_QueryParameterValueMatch__Output { + /** + * The key to use in the descriptor entry. Defaults to ``query_match``. + */ + 'descriptor_key': (string); + /** + * The value to use in the descriptor entry. + */ + 'descriptor_value': (string); + /** + * If set to true, the action will append a descriptor entry when the + * request matches the headers. If set to false, the action will append a + * descriptor entry when the request does not match the headers. The + * default value is true. + */ + 'expect_match': (_google_protobuf_BoolValue__Output | null); + /** + * Specifies a set of query parameters that the rate limit action should match + * on. The action will check the request’s query parameters against all the + * specified query parameters in the config. A match will happen if all the + * query parameters in the config are present in the request with the same values + * (or based on presence if the value field is not in the config). + */ + 'query_parameters': (_envoy_config_route_v3_QueryParameterMatcher__Output)[]; +} + /** * The following descriptor entry is appended to the descriptor and is populated using the * trusted address from :ref:`x-forwarded-for `: @@ -416,7 +589,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_RemoteAddress__Output { /** * The following descriptor entry is appended when a header contains a key that matches the - * *header_name*: + * ``header_name``: * * .. code-block:: cpp * @@ -443,7 +616,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_RequestHeaders { /** * The following descriptor entry is appended when a header contains a key that matches the - * *header_name*: + * ``header_name``: * * .. code-block:: cpp * diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts index e6d41fd7b..fd11a681b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts @@ -115,10 +115,10 @@ export interface RedirectAction { 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); /** * When the scheme redirection take place, the following rules apply: - * 1. If the source URI scheme is `http` and the port is explicitly - * set to `:80`, the port will be removed after the redirection - * 2. If the source URI scheme is `https` and the port is explicitly - * set to `:443`, the port will be removed after the redirection + * 1. If the source URI scheme is ``http`` and the port is explicitly + * set to ``:80``, the port will be removed after the redirection + * 2. If the source URI scheme is ``https`` and the port is explicitly + * set to ``:443``, the port will be removed after the redirection */ 'scheme_rewrite_specifier'?: "https_redirect"|"scheme_redirect"; 'path_rewrite_specifier'?: "path_redirect"|"prefix_rewrite"|"regex_rewrite"; @@ -212,10 +212,10 @@ export interface RedirectAction__Output { 'regex_rewrite'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); /** * When the scheme redirection take place, the following rules apply: - * 1. If the source URI scheme is `http` and the port is explicitly - * set to `:80`, the port will be removed after the redirection - * 2. If the source URI scheme is `https` and the port is explicitly - * set to `:443`, the port will be removed after the redirection + * 1. If the source URI scheme is ``http`` and the port is explicitly + * set to ``:80``, the port will be removed after the redirection + * 2. If the source URI scheme is ``https`` and the port is explicitly + * set to ``:443``, the port will be removed after the redirection */ 'scheme_rewrite_specifier': "https_redirect"|"scheme_redirect"; 'path_rewrite_specifier': "path_redirect"|"prefix_rewrite"|"regex_rewrite"; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts index 0d523b52e..d60458728 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts @@ -176,8 +176,8 @@ export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff { 'base_interval'?: (_google_protobuf_Duration | null); /** * Specifies the maximum interval between retries. This parameter is optional, but must be - * greater than or equal to the `base_interval` if set. The default is 10 times the - * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion + * greater than or equal to the ``base_interval`` if set. The default is 10 times the + * ``base_interval``. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion * of Envoy's back-off algorithm. */ 'max_interval'?: (_google_protobuf_Duration | null); @@ -193,8 +193,8 @@ export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff__Output { 'base_interval': (_google_protobuf_Duration__Output | null); /** * Specifies the maximum interval between retries. This parameter is optional, but must be - * greater than or equal to the `base_interval` if set. The default is 10 times the - * `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion + * greater than or equal to the ``base_interval`` if set. The default is 10 times the + * ``base_interval``. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion * of Envoy's back-off algorithm. */ 'max_interval': (_google_protobuf_Duration__Output | null); @@ -293,7 +293,7 @@ export interface RetryPolicy { /** * Specifies parameters that control exponential retry back off. This parameter is optional, in which case the * default base interval is 25 milliseconds or, if set, the current value of the - * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times + * ``upstream.base_retry_backoff_ms`` runtime parameter. The default maximum interval is 10 times * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` * describes Envoy's back-off algorithm. */ @@ -314,7 +314,7 @@ export interface RetryPolicy { * return a response header like ``Retry-After`` or ``X-RateLimit-Reset`` to * provide feedback to the client on how long to wait before retrying. If * configured, this back-off strategy will be used instead of the - * default exponential back off strategy (configured using `retry_back_off`) + * default exponential back off strategy (configured using ``retry_back_off``) * whenever a response includes the matching headers. */ 'rate_limited_retry_back_off'?: (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff | null); @@ -405,7 +405,7 @@ export interface RetryPolicy__Output { /** * Specifies parameters that control exponential retry back off. This parameter is optional, in which case the * default base interval is 25 milliseconds or, if set, the current value of the - * `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times + * ``upstream.base_retry_backoff_ms`` runtime parameter. The default maximum interval is 10 times * the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` * describes Envoy's back-off algorithm. */ @@ -426,7 +426,7 @@ export interface RetryPolicy__Output { * return a response header like ``Retry-After`` or ``X-RateLimit-Reset`` to * provide feedback to the client on how long to wait before retrying. If * configured, this back-off strategy will be used instead of the - * default exponential back off strategy (configured using `retry_back_off`) + * default exponential back off strategy (configured using ``retry_back_off``) * whenever a response includes the matching headers. */ 'rate_limited_retry_back_off': (_envoy_config_route_v3_RetryPolicy_RateLimitedRetryBackOff__Output | null); diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts index d48b554d0..beda9395d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Route.ts @@ -21,7 +21,7 @@ import type { NonForwardingAction as _envoy_config_route_v3_NonForwardingAction, * * Envoy supports routing on HTTP method via :ref:`header matching * `. - * [#next-free-field: 19] + * [#next-free-field: 20] */ export interface Route { /** @@ -41,7 +41,7 @@ export interface Route { * about the route. It can be used for configuration, stats, and logging. * The metadata should go under the filter namespace that will need it. * For instance, if the metadata is intended for the Router filter, - * the filter name should be specified as *envoy.filters.http.router*. + * the filter name should be specified as ``envoy.filters.http.router``. */ 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** @@ -81,11 +81,15 @@ export interface Route { */ 'request_headers_to_remove'?: (string)[]; /** - * The typed_per_filter_config field can be used to provide route-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` for - * if and how it is utilized. + * The per_filter_config field can be used to provide route-specific configurations for filters. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. * [#comment: An entry's value may be wrapped in a * :ref:`FilterConfig` * message to specify additional options.] @@ -121,6 +125,22 @@ export interface Route { * in Envoy for a filter that directly generates responses for requests. */ 'non_forwarding_action'?: (_envoy_config_route_v3_NonForwardingAction | null); + /** + * The human readable prefix to use when emitting statistics for this endpoint. + * The statistics are rooted at vhost..route.. + * This should be set for highly critical + * endpoints that one wishes to get “per-route” statistics on. + * If not set, endpoint statistics are not generated. + * + * The emitted statistics are the same as those documented for :ref:`virtual clusters `. + * + * .. warning:: + * + * We do not recommend setting up a stat prefix for + * every application endpoint. This is both not easily maintainable and + * statistics use a non-trivial amount of memory(approximately 1KiB per route). + */ + 'stat_prefix'?: (string); 'action'?: "route"|"redirect"|"direct_response"|"filter_action"|"non_forwarding_action"; } @@ -132,7 +152,7 @@ export interface Route { * * Envoy supports routing on HTTP method via :ref:`header matching * `. - * [#next-free-field: 19] + * [#next-free-field: 20] */ export interface Route__Output { /** @@ -152,7 +172,7 @@ export interface Route__Output { * about the route. It can be used for configuration, stats, and logging. * The metadata should go under the filter namespace that will need it. * For instance, if the metadata is intended for the Router filter, - * the filter name should be specified as *envoy.filters.http.router*. + * the filter name should be specified as ``envoy.filters.http.router``. */ 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** @@ -192,11 +212,15 @@ export interface Route__Output { */ 'request_headers_to_remove': (string)[]; /** - * The typed_per_filter_config field can be used to provide route-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` for - * if and how it is utilized. + * The per_filter_config field can be used to provide route-specific configurations for filters. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. * [#comment: An entry's value may be wrapped in a * :ref:`FilterConfig` * message to specify additional options.] @@ -232,5 +256,21 @@ export interface Route__Output { * in Envoy for a filter that directly generates responses for requests. */ 'non_forwarding_action'?: (_envoy_config_route_v3_NonForwardingAction__Output | null); + /** + * The human readable prefix to use when emitting statistics for this endpoint. + * The statistics are rooted at vhost..route.. + * This should be set for highly critical + * endpoints that one wishes to get “per-route” statistics on. + * If not set, endpoint statistics are not generated. + * + * The emitted statistics are the same as those documented for :ref:`virtual clusters `. + * + * .. warning:: + * + * We do not recommend setting up a stat prefix for + * every application endpoint. This is both not easily maintainable and + * statistics use a non-trivial amount of memory(approximately 1KiB per route). + */ + 'stat_prefix': (string); 'action': "route"|"redirect"|"direct_response"|"filter_action"|"non_forwarding_action"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts index 43bd51723..9dd8b7c2c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts @@ -13,6 +13,8 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a import type { RegexMatchAndSubstitute as _envoy_type_matcher_v3_RegexMatchAndSubstitute, RegexMatchAndSubstitute__Output as _envoy_type_matcher_v3_RegexMatchAndSubstitute__Output } from '../../../../envoy/type/matcher/v3/RegexMatchAndSubstitute'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { InternalRedirectPolicy as _envoy_config_route_v3_InternalRedirectPolicy, InternalRedirectPolicy__Output as _envoy_config_route_v3_InternalRedirectPolicy__Output } from '../../../../envoy/config/route/v3/InternalRedirectPolicy'; +import type { ClusterSpecifierPlugin as _envoy_config_route_v3_ClusterSpecifierPlugin, ClusterSpecifierPlugin__Output as _envoy_config_route_v3_ClusterSpecifierPlugin__Output } from '../../../../envoy/config/route/v3/ClusterSpecifierPlugin'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; import type { ProxyProtocolConfig as _envoy_config_core_v3_ProxyProtocolConfig, ProxyProtocolConfig__Output as _envoy_config_core_v3_ProxyProtocolConfig__Output } from '../../../../envoy/config/core/v3/ProxyProtocolConfig'; @@ -27,6 +29,10 @@ export enum _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode { * HTTP status code - 404 Not Found. */ NOT_FOUND = 1, + /** + * HTTP status code - 500 Internal Server Error. + */ + INTERNAL_SERVER_ERROR = 2, } /** @@ -148,8 +154,8 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_Cookie__Output { export interface _envoy_config_route_v3_RouteAction_HashPolicy_FilterState { /** * The name of the Object in the per-request filterState, which is an - * Envoy::Http::Hashable object. If there is no data associated with the key, - * or the stored object is not Envoy::Http::Hashable, no hash will be produced. + * Envoy::Hashable object. If there is no data associated with the key, + * or the stored object is not Envoy::Hashable, no hash will be produced. */ 'key'?: (string); } @@ -157,8 +163,8 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_FilterState { export interface _envoy_config_route_v3_RouteAction_HashPolicy_FilterState__Output { /** * The name of the Object in the per-request filterState, which is an - * Envoy::Http::Hashable object. If there is no data associated with the key, - * or the stored object is not Envoy::Http::Hashable, no hash will be produced. + * Envoy::Hashable object. If there is no data associated with the key, + * or the stored object is not Envoy::Hashable, no hash will be produced. */ 'key': (string); } @@ -317,12 +323,12 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration { /** * If present, and the request contains a `grpc-timeout header * `_, use that value as the - * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. - * If set to 0, the `grpc-timeout` header is used without modification. + * ``max_stream_duration``, but limit the applied timeout to the maximum value specified here. + * If set to 0, the ``grpc-timeout`` header is used without modification. */ 'grpc_timeout_header_max'?: (_google_protobuf_Duration | null); /** - * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by + * If present, Envoy will adjust the timeout provided by the ``grpc-timeout`` header by * subtracting the provided duration from the header. This is useful for allowing Envoy to set * its global timeout to be less than that of the deadline imposed by the calling client, which * makes it more likely that Envoy will handle the timeout instead of having the call canceled @@ -347,12 +353,12 @@ export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration__Output { /** * If present, and the request contains a `grpc-timeout header * `_, use that value as the - * *max_stream_duration*, but limit the applied timeout to the maximum value specified here. - * If set to 0, the `grpc-timeout` header is used without modification. + * ``max_stream_duration``, but limit the applied timeout to the maximum value specified here. + * If set to 0, the ``grpc-timeout`` header is used without modification. */ 'grpc_timeout_header_max': (_google_protobuf_Duration__Output | null); /** - * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by + * If present, Envoy will adjust the timeout provided by the ``grpc-timeout`` header by * subtracting the provided duration from the header. This is useful for allowing Envoy to set * its global timeout to be less than that of the deadline imposed by the calling client, which * makes it more likely that Envoy will handle the timeout instead of having the call canceled @@ -386,23 +392,47 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_QueryParameter__O * respond before returning the response from the primary cluster. All normal statistics are * collected for the shadow cluster making this feature useful for testing. * - * During shadowing, the host/authority header is altered such that *-shadow* is appended. This is - * useful for logging. For example, *cluster1* becomes *cluster1-shadow*. + * During shadowing, the host/authority header is altered such that ``-shadow`` is appended. This is + * useful for logging. For example, ``cluster1`` becomes ``cluster1-shadow``. * * .. note:: * * Shadowing will not be triggered if the primary cluster does not exist. + * + * .. note:: + * + * Shadowing doesn't support Http CONNECT and upgrades. + * [#next-free-field: 6] */ export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy { /** + * Only one of ``cluster`` and ``cluster_header`` can be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] * Specifies the cluster that requests will be mirrored to. The cluster must * exist in the cluster manager configuration. */ 'cluster'?: (string); + /** + * Only one of ``cluster`` and ``cluster_header`` can be specified. + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. Only the first value in header is used, + * and no shadow request will happen if the value is not found in headers. Envoy will not wait for + * the shadow cluster to respond before returning the response from the primary cluster. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 + * ``Host`` header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header'?: (string); /** * If not specified, all requests to the target cluster will be mirrored. * - * If specified, this field takes precedence over the `runtime_key` field and requests must also + * If specified, this field takes precedence over the ``runtime_key`` field and requests must also * fall under the percentage of matches indicated by this field. * * For some fraction N/D, a random number in the range [0,D) is selected. If the @@ -422,23 +452,47 @@ export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy { * respond before returning the response from the primary cluster. All normal statistics are * collected for the shadow cluster making this feature useful for testing. * - * During shadowing, the host/authority header is altered such that *-shadow* is appended. This is - * useful for logging. For example, *cluster1* becomes *cluster1-shadow*. + * During shadowing, the host/authority header is altered such that ``-shadow`` is appended. This is + * useful for logging. For example, ``cluster1`` becomes ``cluster1-shadow``. * * .. note:: * * Shadowing will not be triggered if the primary cluster does not exist. + * + * .. note:: + * + * Shadowing doesn't support Http CONNECT and upgrades. + * [#next-free-field: 6] */ export interface _envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output { /** + * Only one of ``cluster`` and ``cluster_header`` can be specified. + * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] * Specifies the cluster that requests will be mirrored to. The cluster must * exist in the cluster manager configuration. */ 'cluster': (string); + /** + * Only one of ``cluster`` and ``cluster_header`` can be specified. + * Envoy will determine the cluster to route to by reading the value of the + * HTTP header named by cluster_header from the request headers. Only the first value in header is used, + * and no shadow request will happen if the value is not found in headers. Envoy will not wait for + * the shadow cluster to respond before returning the response from the primary cluster. + * + * .. attention:: + * + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 + * ``Host`` header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. + * + * .. note:: + * + * If the header appears multiple times only the first value is used. + */ + 'cluster_header': (string); /** * If not specified, all requests to the target cluster will be mirrored. * - * If specified, this field takes precedence over the `runtime_key` field and requests must also + * If specified, this field takes precedence over the ``runtime_key`` field and requests must also * fall under the percentage of matches indicated by this field. * * For some fraction N/D, a random number in the range [0,D) is selected. If the @@ -509,7 +563,7 @@ export interface _envoy_config_route_v3_RouteAction_UpgradeConfig__Output { } /** - * [#next-free-field: 38] + * [#next-free-field: 42] */ export interface RouteAction { /** @@ -525,8 +579,8 @@ export interface RouteAction { * * .. attention:: * - * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 - * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 + * ``Host`` header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. * * .. note:: * @@ -546,7 +600,7 @@ export interface RouteAction { * in the upstream cluster with metadata matching what's set in this field will be considered * for load balancing. If using :ref:`weighted_clusters * `, metadata will be merged, with values - * provided there taking precedence. The filter name should be specified as *envoy.lb*. + * provided there taking precedence. The filter name should be specified as ``envoy.lb``. */ 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); /** @@ -556,16 +610,16 @@ export interface RouteAction { * place the original path before rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of *prefix_rewrite* or - * :ref:`regex_rewrite ` - * may be specified. + * Only one of :ref:`regex_rewrite ` + * :ref:`path_rewrite_policy `, + * or :ref:`prefix_rewrite ` may be specified. * * .. attention:: * * Pay careful attention to the use of trailing slashes in the * :ref:`route's match ` prefix value. * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, - * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single + * rewriting ``/prefix`` to ``/`` and ``/prefix/etc`` to ``/etc`` cannot be done in a single * :ref:`Route `, as shown by the below config entries: * * .. code-block:: yaml @@ -579,21 +633,27 @@ export interface RouteAction { * route: * prefix_rewrite: "/" * - * Having above entries in the config, requests to * /prefix* will be stripped to * /*, while - * requests to * /prefix/etc* will be stripped to * /etc*. + * Having above entries in the config, requests to ``/prefix`` will be stripped to ``/``, while + * requests to ``/prefix/etc`` will be stripped to ``/etc``. */ 'prefix_rewrite'?: (string); /** * Indicates that during forwarding, the host header will be swapped with - * this value. + * this value. Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. */ 'host_rewrite_literal'?: (string); /** * Indicates that during forwarding, the host header will be swapped with * the hostname of the upstream host chosen by the cluster manager. This * option is applicable only when the destination cluster for a route is of - * type *strict_dns* or *logical_dns*. Setting this to true with other cluster - * types has no effect. + * type ``strict_dns`` or ``logical_dns``. Setting this to true with other cluster types + * has no effect. Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. */ 'auto_host_rewrite'?: (_google_protobuf_BoolValue | null); /** @@ -650,7 +710,16 @@ export interface RouteAction { */ 'hash_policy'?: (_envoy_config_route_v3_RouteAction_HashPolicy)[]; /** - * Indicates that the route has a CORS policy. + * Indicates that the route has a CORS policy. This field is ignored if related cors policy is + * found in the :ref:`Route.typed_per_filter_config` or + * :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config`. + * + * .. attention:: + * + * This option has been deprecated. Please use + * :ref:`Route.typed_per_filter_config` or + * :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * to configure the CORS HTTP filter. */ 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); /** @@ -665,7 +734,7 @@ export interface RouteAction { * or its default value (infinity) instead of * :ref:`timeout `, but limit the applied timeout * to the maximum value specified here. If configured as 0, the maximum allowed timeout for - * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used + * gRPC requests is infinity. If not configured at all, the ``grpc-timeout`` header is not used * and gRPC requests time out like any other requests using * :ref:`timeout ` or its default. * This can be used to prevent unexpected upstream request timeouts due to potentially long @@ -716,7 +785,7 @@ export interface RouteAction { 'hedge_policy'?: (_envoy_config_route_v3_HedgePolicy | null); /** * Deprecated by :ref:`grpc_timeout_header_offset `. - * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting + * If present, Envoy will adjust the timeout provided by the ``grpc-timeout`` header by subtracting * the provided duration from the header. This is useful in allowing Envoy to set its global * timeout to be less than that of the deadline imposed by the calling client, which makes it more * likely that Envoy will handle the timeout instead of having the call canceled by the client. @@ -728,7 +797,10 @@ export interface RouteAction { /** * Indicates that during forwarding, the host header will be swapped with the content of given * downstream or :ref:`custom ` header. - * If header value is empty, host header is left intact. + * If header value is empty, host header is left intact. Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. * * .. attention:: * @@ -741,7 +813,9 @@ export interface RouteAction { */ 'host_rewrite_header'?: (string); /** - * Indicates that the route has request mirroring policies. + * Specify a set of route request mirroring policies. + * It takes precedence over the virtual host and route config mirror policy entirely. + * That is, policies are not merged, the most specific non-empty one becomes the mirror policies. */ 'request_mirror_policies'?: (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy)[]; /** @@ -771,8 +845,10 @@ export interface RouteAction { * before the rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of :ref:`prefix_rewrite ` - * or *regex_rewrite* may be specified. + * Only one of :ref:`regex_rewrite `, + * :ref:`prefix_rewrite `, or + * :ref:`path_rewrite_policy `] + * may be specified. * * Examples using Google's `RE2 `_ engine: * @@ -811,6 +887,10 @@ export interface RouteAction { * Indicates that during forwarding, the host header will be swapped with * the result of the regex substitution executed on path value with query and fragment removed. * This is useful for transitioning variable content between path segment and subdomain. + * Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. * * For example with the following config: * @@ -822,7 +902,7 @@ export interface RouteAction { * regex: "^/(.+)/.+$" * substitution: \1 * - * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. + * Would rewrite the host header to ``envoyproxy.io`` given the path ``/envoyproxy.io/some/path``. */ 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute | null); /** @@ -830,20 +910,44 @@ export interface RouteAction { */ 'max_stream_duration'?: (_envoy_config_route_v3_RouteAction_MaxStreamDuration | null); /** - * [#not-implemented-hide:] - * Name of the cluster specifier plugin to use to determine the cluster for - * requests on this route. The plugin name must be defined in the associated - * :ref:`envoy_v3_api_field_config.route.v3.RouteConfiguration.cluster_specifier_plugins` - * in the - * :ref:`envoy_v3_api_field_config.core.v3.TypedExtensionConfig.name` field. + * Name of the cluster specifier plugin to use to determine the cluster for requests on this route. + * The cluster specifier plugin name must be defined in the associated + * :ref:`cluster specifier plugins ` + * in the :ref:`name ` field. */ 'cluster_specifier_plugin'?: (string); - 'cluster_specifier'?: "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"; + /** + * If set, then a host rewrite action (one of + * :ref:`host_rewrite_literal `, + * :ref:`auto_host_rewrite `, + * :ref:`host_rewrite_header `, or + * :ref:`host_rewrite_path_regex `) + * causes the original value of the host header, if any, to be appended to the + * :ref:`config_http_conn_man_headers_x-forwarded-host` HTTP header if it is different to the last value appended. + * This can be disabled by setting the runtime guard `envoy_reloadable_features_append_xfh_idempotent` to false. + */ + 'append_x_forwarded_host'?: (boolean); + /** + * Custom cluster specifier plugin configuration to use to determine the cluster for requests + * on this route. + */ + 'inline_cluster_specifier_plugin'?: (_envoy_config_route_v3_ClusterSpecifierPlugin | null); + /** + * Specifies how to send request over TLS early data. + * If absent, allows `safe HTTP requests `_ to be sent on early data. + * [#extension-category: envoy.route.early_data_policy] + */ + 'early_data_policy'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * [#extension-category: envoy.path.rewrite] + */ + 'path_rewrite_policy'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'cluster_specifier'?: "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"|"inline_cluster_specifier_plugin"; 'host_rewrite_specifier'?: "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } /** - * [#next-free-field: 38] + * [#next-free-field: 42] */ export interface RouteAction__Output { /** @@ -859,8 +963,8 @@ export interface RouteAction__Output { * * .. attention:: * - * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 - * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 + * ``Host`` header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. * * .. note:: * @@ -880,7 +984,7 @@ export interface RouteAction__Output { * in the upstream cluster with metadata matching what's set in this field will be considered * for load balancing. If using :ref:`weighted_clusters * `, metadata will be merged, with values - * provided there taking precedence. The filter name should be specified as *envoy.lb*. + * provided there taking precedence. The filter name should be specified as ``envoy.lb``. */ 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); /** @@ -890,16 +994,16 @@ export interface RouteAction__Output { * place the original path before rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of *prefix_rewrite* or - * :ref:`regex_rewrite ` - * may be specified. + * Only one of :ref:`regex_rewrite ` + * :ref:`path_rewrite_policy `, + * or :ref:`prefix_rewrite ` may be specified. * * .. attention:: * * Pay careful attention to the use of trailing slashes in the * :ref:`route's match ` prefix value. * Stripping a prefix from a path requires multiple Routes to handle all cases. For example, - * rewriting * /prefix* to * /* and * /prefix/etc* to * /etc* cannot be done in a single + * rewriting ``/prefix`` to ``/`` and ``/prefix/etc`` to ``/etc`` cannot be done in a single * :ref:`Route `, as shown by the below config entries: * * .. code-block:: yaml @@ -913,21 +1017,27 @@ export interface RouteAction__Output { * route: * prefix_rewrite: "/" * - * Having above entries in the config, requests to * /prefix* will be stripped to * /*, while - * requests to * /prefix/etc* will be stripped to * /etc*. + * Having above entries in the config, requests to ``/prefix`` will be stripped to ``/``, while + * requests to ``/prefix/etc`` will be stripped to ``/etc``. */ 'prefix_rewrite': (string); /** * Indicates that during forwarding, the host header will be swapped with - * this value. + * this value. Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. */ 'host_rewrite_literal'?: (string); /** * Indicates that during forwarding, the host header will be swapped with * the hostname of the upstream host chosen by the cluster manager. This * option is applicable only when the destination cluster for a route is of - * type *strict_dns* or *logical_dns*. Setting this to true with other cluster - * types has no effect. + * type ``strict_dns`` or ``logical_dns``. Setting this to true with other cluster types + * has no effect. Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. */ 'auto_host_rewrite'?: (_google_protobuf_BoolValue__Output | null); /** @@ -984,7 +1094,16 @@ export interface RouteAction__Output { */ 'hash_policy': (_envoy_config_route_v3_RouteAction_HashPolicy__Output)[]; /** - * Indicates that the route has a CORS policy. + * Indicates that the route has a CORS policy. This field is ignored if related cors policy is + * found in the :ref:`Route.typed_per_filter_config` or + * :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config`. + * + * .. attention:: + * + * This option has been deprecated. Please use + * :ref:`Route.typed_per_filter_config` or + * :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` + * to configure the CORS HTTP filter. */ 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); /** @@ -999,7 +1118,7 @@ export interface RouteAction__Output { * or its default value (infinity) instead of * :ref:`timeout `, but limit the applied timeout * to the maximum value specified here. If configured as 0, the maximum allowed timeout for - * gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used + * gRPC requests is infinity. If not configured at all, the ``grpc-timeout`` header is not used * and gRPC requests time out like any other requests using * :ref:`timeout ` or its default. * This can be used to prevent unexpected upstream request timeouts due to potentially long @@ -1050,7 +1169,7 @@ export interface RouteAction__Output { 'hedge_policy': (_envoy_config_route_v3_HedgePolicy__Output | null); /** * Deprecated by :ref:`grpc_timeout_header_offset `. - * If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting + * If present, Envoy will adjust the timeout provided by the ``grpc-timeout`` header by subtracting * the provided duration from the header. This is useful in allowing Envoy to set its global * timeout to be less than that of the deadline imposed by the calling client, which makes it more * likely that Envoy will handle the timeout instead of having the call canceled by the client. @@ -1062,7 +1181,10 @@ export interface RouteAction__Output { /** * Indicates that during forwarding, the host header will be swapped with the content of given * downstream or :ref:`custom ` header. - * If header value is empty, host header is left intact. + * If header value is empty, host header is left intact. Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. * * .. attention:: * @@ -1075,7 +1197,9 @@ export interface RouteAction__Output { */ 'host_rewrite_header'?: (string); /** - * Indicates that the route has request mirroring policies. + * Specify a set of route request mirroring policies. + * It takes precedence over the virtual host and route config mirror policy entirely. + * That is, policies are not merged, the most specific non-empty one becomes the mirror policies. */ 'request_mirror_policies': (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output)[]; /** @@ -1105,8 +1229,10 @@ export interface RouteAction__Output { * before the rewrite into the :ref:`x-envoy-original-path * ` header. * - * Only one of :ref:`prefix_rewrite ` - * or *regex_rewrite* may be specified. + * Only one of :ref:`regex_rewrite `, + * :ref:`prefix_rewrite `, or + * :ref:`path_rewrite_policy `] + * may be specified. * * Examples using Google's `RE2 `_ engine: * @@ -1145,6 +1271,10 @@ export interface RouteAction__Output { * Indicates that during forwarding, the host header will be swapped with * the result of the regex substitution executed on path value with query and fragment removed. * This is useful for transitioning variable content between path segment and subdomain. + * Using this option will append the + * :ref:`config_http_conn_man_headers_x-forwarded-host` header if + * :ref:`append_x_forwarded_host ` + * is set. * * For example with the following config: * @@ -1156,7 +1286,7 @@ export interface RouteAction__Output { * regex: "^/(.+)/.+$" * substitution: \1 * - * Would rewrite the host header to `envoyproxy.io` given the path `/envoyproxy.io/some/path`. + * Would rewrite the host header to ``envoyproxy.io`` given the path ``/envoyproxy.io/some/path``. */ 'host_rewrite_path_regex'?: (_envoy_type_matcher_v3_RegexMatchAndSubstitute__Output | null); /** @@ -1164,14 +1294,38 @@ export interface RouteAction__Output { */ 'max_stream_duration': (_envoy_config_route_v3_RouteAction_MaxStreamDuration__Output | null); /** - * [#not-implemented-hide:] - * Name of the cluster specifier plugin to use to determine the cluster for - * requests on this route. The plugin name must be defined in the associated - * :ref:`envoy_v3_api_field_config.route.v3.RouteConfiguration.cluster_specifier_plugins` - * in the - * :ref:`envoy_v3_api_field_config.core.v3.TypedExtensionConfig.name` field. + * Name of the cluster specifier plugin to use to determine the cluster for requests on this route. + * The cluster specifier plugin name must be defined in the associated + * :ref:`cluster specifier plugins ` + * in the :ref:`name ` field. */ 'cluster_specifier_plugin'?: (string); - 'cluster_specifier': "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"; + /** + * If set, then a host rewrite action (one of + * :ref:`host_rewrite_literal `, + * :ref:`auto_host_rewrite `, + * :ref:`host_rewrite_header `, or + * :ref:`host_rewrite_path_regex `) + * causes the original value of the host header, if any, to be appended to the + * :ref:`config_http_conn_man_headers_x-forwarded-host` HTTP header if it is different to the last value appended. + * This can be disabled by setting the runtime guard `envoy_reloadable_features_append_xfh_idempotent` to false. + */ + 'append_x_forwarded_host': (boolean); + /** + * Custom cluster specifier plugin configuration to use to determine the cluster for requests + * on this route. + */ + 'inline_cluster_specifier_plugin'?: (_envoy_config_route_v3_ClusterSpecifierPlugin__Output | null); + /** + * Specifies how to send request over TLS early data. + * If absent, allows `safe HTTP requests `_ to be sent on early data. + * [#extension-category: envoy.route.early_data_policy] + */ + 'early_data_policy': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * [#extension-category: envoy.path.rewrite] + */ + 'path_rewrite_policy': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'cluster_specifier': "cluster"|"cluster_header"|"weighted_clusters"|"cluster_specifier_plugin"|"inline_cluster_specifier_plugin"; 'host_rewrite_specifier': "host_rewrite_literal"|"auto_host_rewrite"|"host_rewrite_header"|"host_rewrite_path_regex"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts index 516f4b06b..1eddc6528 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteConfiguration.ts @@ -6,9 +6,11 @@ import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _goo import type { Vhds as _envoy_config_route_v3_Vhds, Vhds__Output as _envoy_config_route_v3_Vhds__Output } from '../../../../envoy/config/route/v3/Vhds'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { ClusterSpecifierPlugin as _envoy_config_route_v3_ClusterSpecifierPlugin, ClusterSpecifierPlugin__Output as _envoy_config_route_v3_ClusterSpecifierPlugin__Output } from '../../../../envoy/config/route/v3/ClusterSpecifierPlugin'; +import type { _envoy_config_route_v3_RouteAction_RequestMirrorPolicy, _envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output } from '../../../../envoy/config/route/v3/RouteAction'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; /** - * [#next-free-field: 13] + * [#next-free-field: 17] */ export interface RouteConfiguration { /** @@ -75,10 +77,10 @@ export interface RouteConfiguration { 'request_headers_to_remove'?: (string)[]; /** * An array of virtual hosts will be dynamically loaded via the VHDS API. - * Both *virtual_hosts* and *vhds* fields will be used when present. *virtual_hosts* can be used - * for a base routing table or for infrequently changing virtual hosts. *vhds* is used for + * Both ``virtual_hosts`` and ``vhds`` fields will be used when present. ``virtual_hosts`` can be used + * for a base routing table or for infrequently changing virtual hosts. ``vhds`` is used for * on-demand discovery of virtual hosts. The contents of these two fields will be merged to - * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration + * generate a routing table for a given RouteConfiguration, with ``vhds`` derived configuration * taking precedence. */ 'vhds'?: (_envoy_config_route_v3_Vhds | null); @@ -91,8 +93,6 @@ export interface RouteConfiguration { * * To allow setting overrides at the route or virtual host level, this order can be reversed * by setting this option to true. Defaults to false. - * - * [#next-major-version: In the v3 API, this will default to true.] */ 'most_specific_header_mutations_wins'?: (boolean); /** @@ -109,16 +109,49 @@ export interface RouteConfiguration { */ 'max_direct_response_body_size_bytes'?: (_google_protobuf_UInt32Value | null); /** - * [#not-implemented-hide:] * A list of plugins and their configurations which may be used by a - * :ref:`envoy_v3_api_field_config.route.v3.RouteAction.cluster_specifier_plugin` - * within the route. All *extension.name* fields in this list must be unique. + * :ref:`cluster specifier plugin name ` + * within the route. All ``extension.name`` fields in this list must be unique. */ 'cluster_specifier_plugins'?: (_envoy_config_route_v3_ClusterSpecifierPlugin)[]; + /** + * Specify a set of default request mirroring policies which apply to all routes under its virtual hosts. + * Note that policies are not merged, the most specific non-empty one becomes the mirror policies. + */ + 'request_mirror_policies'?: (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy)[]; + /** + * By default, port in :authority header (if any) is used in host matching. + * With this option enabled, Envoy will ignore the port number in the :authority header (if any) when picking VirtualHost. + * NOTE: this option will not strip the port number (if any) contained in route config + * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`.domains field. + */ + 'ignore_port_in_host_matching'?: (boolean); + /** + * Ignore path-parameters in path-matching. + * Before RFC3986, URI were like(RFC1808): :///;?# + * Envoy by default takes ":path" as ";". + * For users who want to only match path on the "" portion, this option should be true. + */ + 'ignore_path_parameters_in_path_matching'?: (boolean); + /** + * The typed_per_filter_config field can be used to provide RouteConfiguration level per filter config. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config'?: ({[key: string]: _google_protobuf_Any}); } /** - * [#next-free-field: 13] + * [#next-free-field: 17] */ export interface RouteConfiguration__Output { /** @@ -185,10 +218,10 @@ export interface RouteConfiguration__Output { 'request_headers_to_remove': (string)[]; /** * An array of virtual hosts will be dynamically loaded via the VHDS API. - * Both *virtual_hosts* and *vhds* fields will be used when present. *virtual_hosts* can be used - * for a base routing table or for infrequently changing virtual hosts. *vhds* is used for + * Both ``virtual_hosts`` and ``vhds`` fields will be used when present. ``virtual_hosts`` can be used + * for a base routing table or for infrequently changing virtual hosts. ``vhds`` is used for * on-demand discovery of virtual hosts. The contents of these two fields will be merged to - * generate a routing table for a given RouteConfiguration, with *vhds* derived configuration + * generate a routing table for a given RouteConfiguration, with ``vhds`` derived configuration * taking precedence. */ 'vhds': (_envoy_config_route_v3_Vhds__Output | null); @@ -201,8 +234,6 @@ export interface RouteConfiguration__Output { * * To allow setting overrides at the route or virtual host level, this order can be reversed * by setting this option to true. Defaults to false. - * - * [#next-major-version: In the v3 API, this will default to true.] */ 'most_specific_header_mutations_wins': (boolean); /** @@ -219,10 +250,43 @@ export interface RouteConfiguration__Output { */ 'max_direct_response_body_size_bytes': (_google_protobuf_UInt32Value__Output | null); /** - * [#not-implemented-hide:] * A list of plugins and their configurations which may be used by a - * :ref:`envoy_v3_api_field_config.route.v3.RouteAction.cluster_specifier_plugin` - * within the route. All *extension.name* fields in this list must be unique. + * :ref:`cluster specifier plugin name ` + * within the route. All ``extension.name`` fields in this list must be unique. */ 'cluster_specifier_plugins': (_envoy_config_route_v3_ClusterSpecifierPlugin__Output)[]; + /** + * Specify a set of default request mirroring policies which apply to all routes under its virtual hosts. + * Note that policies are not merged, the most specific non-empty one becomes the mirror policies. + */ + 'request_mirror_policies': (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output)[]; + /** + * By default, port in :authority header (if any) is used in host matching. + * With this option enabled, Envoy will ignore the port number in the :authority header (if any) when picking VirtualHost. + * NOTE: this option will not strip the port number (if any) contained in route config + * :ref:`envoy_v3_api_msg_config.route.v3.VirtualHost`.domains field. + */ + 'ignore_port_in_host_matching': (boolean); + /** + * Ignore path-parameters in path-matching. + * Before RFC3986, URI were like(RFC1808): :///;?# + * Envoy by default takes ":path" as ";". + * For users who want to only match path on the "" portion, this option should be true. + */ + 'ignore_path_parameters_in_path_matching': (boolean); + /** + * The typed_per_filter_config field can be used to provide RouteConfiguration level per filter config. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. + * [#comment: An entry's value may be wrapped in a + * :ref:`FilterConfig` + * message to specify additional options.] + */ + 'typed_per_filter_config': ({[key: string]: _google_protobuf_Any__Output}); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteList.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteList.ts new file mode 100644 index 000000000..676d38554 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteList.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto + +import type { Route as _envoy_config_route_v3_Route, Route__Output as _envoy_config_route_v3_Route__Output } from '../../../../envoy/config/route/v3/Route'; + +/** + * This can be used in route matcher :ref:`VirtualHost.matcher `. + * When the matcher matches, routes will be matched and run. + */ +export interface RouteList { + /** + * The list of routes that will be matched and run, in order. The first route that matches will be used. + */ + 'routes'?: (_envoy_config_route_v3_Route)[]; +} + +/** + * This can be used in route matcher :ref:`VirtualHost.matcher `. + * When the matcher matches, routes will be matched and run. + */ +export interface RouteList__Output { + /** + * The list of routes that will be matched and run, in order. The first route that matches will be used. + */ + 'routes': (_envoy_config_route_v3_Route__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts index 9d872ed18..06982a82f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteMatch.ts @@ -6,6 +6,7 @@ import type { QueryParameterMatcher as _envoy_config_route_v3_QueryParameterMatc import type { RuntimeFractionalPercent as _envoy_config_core_v3_RuntimeFractionalPercent, RuntimeFractionalPercent__Output as _envoy_config_core_v3_RuntimeFractionalPercent__Output } from '../../../../envoy/config/core/v3/RuntimeFractionalPercent'; import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; import type { MetadataMatcher as _envoy_type_matcher_v3_MetadataMatcher, MetadataMatcher__Output as _envoy_type_matcher_v3_MetadataMatcher__Output } from '../../../../envoy/type/matcher/v3/MetadataMatcher'; +import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; /** * An extensible message for matching CONNECT requests. @@ -52,17 +53,17 @@ export interface _envoy_config_route_v3_RouteMatch_TlsContextMatchOptions__Outpu } /** - * [#next-free-field: 14] + * [#next-free-field: 16] */ export interface RouteMatch { /** * If specified, the route is a prefix rule meaning that the prefix must - * match the beginning of the *:path* header. + * match the beginning of the ``:path`` header. */ 'prefix'?: (string); /** * If specified, the route is an exact path rule meaning that the path must - * exactly match the *:path* header once the query string is removed. + * exactly match the ``:path`` header once the query string is removed. */ 'path'?: (string); /** @@ -80,9 +81,9 @@ export interface RouteMatch { 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; /** * Specifies a set of URL query parameters on which the route should - * match. The router will check the query string from the *path* header + * match. The router will check the query string from the ``path`` header * against all the specified query parameters. If the number of specified - * query parameters is nonzero, they all must match the *path* header's + * query parameters is nonzero, they all must match the ``path`` header's * query string for a match to occur. * * .. note:: @@ -121,9 +122,9 @@ export interface RouteMatch { 'runtime_fraction'?: (_envoy_config_core_v3_RuntimeFractionalPercent | null); /** * If specified, the route is a regular expression rule meaning that the - * regex must match the *:path* header once the query string is removed. The entire path + * regex must match the ``:path`` header once the query string is removed. The entire path * (without the query string) must match the regex. The rule will not match if only a - * subsequence of the *:path* header matches the regex. + * subsequence of the ``:path`` header matches the regex. * * [#next-major-version: In the v3 API we should redo how path specification works such * that we utilize StringMatcher, and additionally have consistent options around whether we @@ -160,21 +161,37 @@ export interface RouteMatch { * dynamic metadata for a match to occur. */ 'dynamic_metadata'?: (_envoy_type_matcher_v3_MetadataMatcher)[]; - 'path_specifier'?: "prefix"|"path"|"safe_regex"|"connect_matcher"; + /** + * If specified, the route is a path-separated prefix rule meaning that the + * ``:path`` header (without the query string) must either exactly match the + * ``path_separated_prefix`` or have it as a prefix, followed by ``/`` + * + * For example, ``/api/dev`` would match + * ``/api/dev``, ``/api/dev/``, ``/api/dev/v1``, and ``/api/dev?param=true`` + * but would not match ``/api/developer`` + * + * Expect the value to not contain ``?`` or ``#`` and not to end in ``/`` + */ + 'path_separated_prefix'?: (string); + /** + * [#extension-category: envoy.path.match] + */ + 'path_match_policy'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + 'path_specifier'?: "prefix"|"path"|"safe_regex"|"connect_matcher"|"path_separated_prefix"|"path_match_policy"; } /** - * [#next-free-field: 14] + * [#next-free-field: 16] */ export interface RouteMatch__Output { /** * If specified, the route is a prefix rule meaning that the prefix must - * match the beginning of the *:path* header. + * match the beginning of the ``:path`` header. */ 'prefix'?: (string); /** * If specified, the route is an exact path rule meaning that the path must - * exactly match the *:path* header once the query string is removed. + * exactly match the ``:path`` header once the query string is removed. */ 'path'?: (string); /** @@ -192,9 +209,9 @@ export interface RouteMatch__Output { 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; /** * Specifies a set of URL query parameters on which the route should - * match. The router will check the query string from the *path* header + * match. The router will check the query string from the ``path`` header * against all the specified query parameters. If the number of specified - * query parameters is nonzero, they all must match the *path* header's + * query parameters is nonzero, they all must match the ``path`` header's * query string for a match to occur. * * .. note:: @@ -233,9 +250,9 @@ export interface RouteMatch__Output { 'runtime_fraction': (_envoy_config_core_v3_RuntimeFractionalPercent__Output | null); /** * If specified, the route is a regular expression rule meaning that the - * regex must match the *:path* header once the query string is removed. The entire path + * regex must match the ``:path`` header once the query string is removed. The entire path * (without the query string) must match the regex. The rule will not match if only a - * subsequence of the *:path* header matches the regex. + * subsequence of the ``:path`` header matches the regex. * * [#next-major-version: In the v3 API we should redo how path specification works such * that we utilize StringMatcher, and additionally have consistent options around whether we @@ -272,5 +289,21 @@ export interface RouteMatch__Output { * dynamic metadata for a match to occur. */ 'dynamic_metadata': (_envoy_type_matcher_v3_MetadataMatcher__Output)[]; - 'path_specifier': "prefix"|"path"|"safe_regex"|"connect_matcher"; + /** + * If specified, the route is a path-separated prefix rule meaning that the + * ``:path`` header (without the query string) must either exactly match the + * ``path_separated_prefix`` or have it as a prefix, followed by ``/`` + * + * For example, ``/api/dev`` would match + * ``/api/dev``, ``/api/dev/``, ``/api/dev/v1``, and ``/api/dev?param=true`` + * but would not match ``/api/developer`` + * + * Expect the value to not contain ``?`` or ``#`` and not to end in ``/`` + */ + 'path_separated_prefix'?: (string); + /** + * [#extension-category: envoy.path.match] + */ + 'path_match_policy'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + 'path_specifier': "prefix"|"path"|"safe_regex"|"connect_matcher"|"path_separated_prefix"|"path_match_policy"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts index 5865eadd3..e13c537d4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/ScopedRouteConfiguration.ts @@ -1,5 +1,6 @@ // Original file: deps/envoy-api/envoy/config/route/v3/scoped_route.proto +import type { RouteConfiguration as _envoy_config_route_v3_RouteConfiguration, RouteConfiguration__Output as _envoy_config_route_v3_RouteConfiguration__Output } from '../../../../envoy/config/route/v3/RouteConfiguration'; export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key_Fragment { /** @@ -52,7 +53,10 @@ export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key__Output { /** * Specifies a routing scope, which associates a * :ref:`Key` to a - * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. + * The :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` can be obtained dynamically + * via RDS (:ref:`route_configuration_name`) + * or specified inline (:ref:`route_configuration`). * * The HTTP connection manager builds up a table consisting of these Key to * RouteConfiguration mappings, and looks up the RouteConfiguration to use per @@ -106,8 +110,10 @@ export interface _envoy_config_route_v3_ScopedRouteConfiguration_Key__Output { * Host: foo.com * X-Route-Selector: vip=172.10.10.20 * - * would result in the routing table defined by the `route-config1` + * would result in the routing table defined by the ``route-config1`` * RouteConfiguration being assigned to the HTTP request/stream. + * + * [#next-free-field: 6] */ export interface ScopedRouteConfiguration { /** @@ -128,12 +134,19 @@ export interface ScopedRouteConfiguration { * Whether the RouteConfiguration should be loaded on demand. */ 'on_demand'?: (boolean); + /** + * The :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated with the scope. + */ + 'route_configuration'?: (_envoy_config_route_v3_RouteConfiguration | null); } /** * Specifies a routing scope, which associates a * :ref:`Key` to a - * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` (identified by its resource name). + * :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration`. + * The :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` can be obtained dynamically + * via RDS (:ref:`route_configuration_name`) + * or specified inline (:ref:`route_configuration`). * * The HTTP connection manager builds up a table consisting of these Key to * RouteConfiguration mappings, and looks up the RouteConfiguration to use per @@ -187,8 +200,10 @@ export interface ScopedRouteConfiguration { * Host: foo.com * X-Route-Selector: vip=172.10.10.20 * - * would result in the routing table defined by the `route-config1` + * would result in the routing table defined by the ``route-config1`` * RouteConfiguration being assigned to the HTTP request/stream. + * + * [#next-free-field: 6] */ export interface ScopedRouteConfiguration__Output { /** @@ -209,4 +224,8 @@ export interface ScopedRouteConfiguration__Output { * Whether the RouteConfiguration should be loaded on demand. */ 'on_demand': (boolean); + /** + * The :ref:`envoy_v3_api_msg_config.route.v3.RouteConfiguration` associated with the scope. + */ + 'route_configuration': (_envoy_config_route_v3_RouteConfiguration__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts index 962e9d51a..29995243b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/Tracing.ts @@ -8,7 +8,7 @@ export interface Tracing { * Target percentage of requests managed by this HTTP connection manager that will be force * traced if the :ref:`x-client-trace-id ` * header is set. This field is a direct analog for the runtime variable - * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * 'tracing.client_enabled' in the :ref:`HTTP Connection Manager * `. * Default: 100% */ @@ -48,7 +48,7 @@ export interface Tracing__Output { * Target percentage of requests managed by this HTTP connection manager that will be force * traced if the :ref:`x-client-trace-id ` * header is set. This field is a direct analog for the runtime variable - * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * 'tracing.client_enabled' in the :ref:`HTTP Connection Manager * `. * Default: 100% */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts index 7674da733..3c65d6a34 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualCluster.ts @@ -30,7 +30,7 @@ export interface VirtualCluster { 'name'?: (string); /** * Specifies a list of header matchers to use for matching requests. Each specified header must - * match. The pseudo-headers `:path` and `:method` can be used to match the request path and + * match. The pseudo-headers ``:path`` and ``:method`` can be used to match the request path and * method, respectively. */ 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; @@ -64,7 +64,7 @@ export interface VirtualCluster__Output { 'name': (string); /** * Specifies a list of header matchers to use for matching requests. Each specified header must - * match. The pseudo-headers `:path` and `:method` can be used to match the request path and + * match. The pseudo-headers ``:path`` and ``:method`` can be used to match the request path and * method, respectively. */ 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts index 017900ed9..b2c344fff 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts @@ -9,6 +9,8 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ import type { RetryPolicy as _envoy_config_route_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_route_v3_RetryPolicy__Output } from '../../../../envoy/config/route/v3/RetryPolicy'; import type { HedgePolicy as _envoy_config_route_v3_HedgePolicy, HedgePolicy__Output as _envoy_config_route_v3_HedgePolicy__Output } from '../../../../envoy/config/route/v3/HedgePolicy'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Matcher as _xds_type_matcher_v3_Matcher, Matcher__Output as _xds_type_matcher_v3_Matcher__Output } from '../../../../xds/type/matcher/v3/Matcher'; +import type { _envoy_config_route_v3_RouteAction_RequestMirrorPolicy, _envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output } from '../../../../envoy/config/route/v3/RouteAction'; // Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto @@ -35,7 +37,7 @@ export enum _envoy_config_route_v3_VirtualHost_TlsRequirementType { * host header. This allows a single listener to service multiple top level domain path trees. Once * a virtual host is selected based on the domain, the routes are processed in order to see which * upstream cluster to route to or whether to perform a redirect. - * [#next-free-field: 21] + * [#next-free-field: 24] */ export interface VirtualHost { /** @@ -67,6 +69,7 @@ export interface VirtualHost { /** * The list of routes that will be matched, in order, for incoming requests. * The first route that matches will be used. + * Only one of this and ``matcher`` can be specified. */ 'routes'?: (_envoy_config_route_v3_Route)[]; /** @@ -94,7 +97,15 @@ export interface VirtualHost { */ 'request_headers_to_add'?: (_envoy_config_core_v3_HeaderValueOption)[]; /** - * Indicates that the virtual host has a CORS policy. + * Indicates that the virtual host has a CORS policy. This field is ignored if related cors policy is + * found in the + * :ref:`VirtualHost.typed_per_filter_config`. + * + * .. attention:: + * + * This option has been deprecated. Please use + * :ref:`VirtualHost.typed_per_filter_config` + * to configure the CORS HTTP filter. */ 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); /** @@ -130,11 +141,15 @@ export interface VirtualHost { */ 'include_request_attempt_count'?: (boolean); /** - * The per_filter_config field can be used to provide virtual host-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. + * The per_filter_config field can be used to provide virtual host-specific configurations for filters. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. * [#comment: An entry's value may be wrapped in a * :ref:`FilterConfig` * message to specify additional options.] @@ -177,6 +192,23 @@ export interface VirtualHost { * set if this field is used. */ 'retry_policy_typed_config'?: (_google_protobuf_Any | null); + /** + * [#next-major-version: This should be included in a oneof with routes wrapped in a message.] + * The match tree to use when resolving route actions for incoming requests. Only one of this and ``routes`` + * can be specified. + */ + 'matcher'?: (_xds_type_matcher_v3_Matcher | null); + /** + * Specify a set of default request mirroring policies for every route under this virtual host. + * It takes precedence over the route config mirror policy entirely. + * That is, policies are not merged, the most specific non-empty one becomes the mirror policies. + */ + 'request_mirror_policies'?: (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy)[]; + /** + * Decides whether to include the :ref:`x-envoy-is-timeout-retry ` + * request header in retries initiated by per try timeouts. + */ + 'include_is_timeout_retry_header'?: (boolean); } /** @@ -185,7 +217,7 @@ export interface VirtualHost { * host header. This allows a single listener to service multiple top level domain path trees. Once * a virtual host is selected based on the domain, the routes are processed in order to see which * upstream cluster to route to or whether to perform a redirect. - * [#next-free-field: 21] + * [#next-free-field: 24] */ export interface VirtualHost__Output { /** @@ -217,6 +249,7 @@ export interface VirtualHost__Output { /** * The list of routes that will be matched, in order, for incoming requests. * The first route that matches will be used. + * Only one of this and ``matcher`` can be specified. */ 'routes': (_envoy_config_route_v3_Route__Output)[]; /** @@ -244,7 +277,15 @@ export interface VirtualHost__Output { */ 'request_headers_to_add': (_envoy_config_core_v3_HeaderValueOption__Output)[]; /** - * Indicates that the virtual host has a CORS policy. + * Indicates that the virtual host has a CORS policy. This field is ignored if related cors policy is + * found in the + * :ref:`VirtualHost.typed_per_filter_config`. + * + * .. attention:: + * + * This option has been deprecated. Please use + * :ref:`VirtualHost.typed_per_filter_config` + * to configure the CORS HTTP filter. */ 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); /** @@ -280,11 +321,15 @@ export interface VirtualHost__Output { */ 'include_request_attempt_count': (boolean); /** - * The per_filter_config field can be used to provide virtual host-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. + * The per_filter_config field can be used to provide virtual host-specific configurations for filters. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. * [#comment: An entry's value may be wrapped in a * :ref:`FilterConfig` * message to specify additional options.] @@ -327,4 +372,21 @@ export interface VirtualHost__Output { * set if this field is used. */ 'retry_policy_typed_config': (_google_protobuf_Any__Output | null); + /** + * [#next-major-version: This should be included in a oneof with routes wrapped in a message.] + * The match tree to use when resolving route actions for incoming requests. Only one of this and ``routes`` + * can be specified. + */ + 'matcher': (_xds_type_matcher_v3_Matcher__Output | null); + /** + * Specify a set of default request mirroring policies for every route under this virtual host. + * It takes precedence over the route config mirror policy entirely. + * That is, policies are not merged, the most specific non-empty one becomes the mirror policies. + */ + 'request_mirror_policies': (_envoy_config_route_v3_RouteAction_RequestMirrorPolicy__Output)[]; + /** + * Decides whether to include the :ref:`x-envoy-is-timeout-retry ` + * request header in retries initiated by per try timeouts. + */ + 'include_is_timeout_retry_header': (boolean); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts index e734073be..cc820654d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts @@ -10,14 +10,14 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__ */ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { /** - * Only one of *name* and *cluster_header* may be specified. + * Only one of ``name`` and ``cluster_header`` may be specified. * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] * Name of the upstream cluster. The cluster must exist in the * :ref:`cluster manager configuration `. */ 'name'?: (string); /** - * Only one of *name* and *cluster_header* may be specified. + * Only one of ``name`` and ``cluster_header`` may be specified. * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1 }] * Envoy will determine the cluster to route to by reading the value of the * HTTP header named by cluster_header from the request headers. If the @@ -26,8 +26,8 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { * * .. attention:: * - * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 - * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 + * ``Host`` header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. * * .. note:: * @@ -35,10 +35,11 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { */ 'cluster_header'?: (string); /** - * An integer between 0 and :ref:`total_weight - * `. When a request matches the route, - * the choice of an upstream cluster is determined by its weight. The sum of weights across all - * entries in the clusters array must add up to the total_weight, which defaults to 100. + * The weight of the cluster. This value is relative to the other clusters' + * weights. When a request matches the route, the choice of an upstream cluster + * is determined by its weight. The sum of weights across all + * entries in the clusters array must be greater than 0, and must not exceed + * uint32_t maximal value (4294967295). */ 'weight'?: (_google_protobuf_UInt32Value | null); /** @@ -46,7 +47,7 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { * the upstream cluster with metadata matching what is set in this field will be considered for * load balancing. Note that this will be merged with what's provided in * :ref:`RouteAction.metadata_match `, with - * values here taking precedence. The filter name should be specified as *envoy.lb*. + * values here taking precedence. The filter name should be specified as ``envoy.lb``. */ 'metadata_match'?: (_envoy_config_core_v3_Metadata | null); /** @@ -80,11 +81,16 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { */ 'response_headers_to_remove'?: (string)[]; /** - * The per_filter_config field can be used to provide weighted cluster-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. + * The per_filter_config field can be used to provide weighted cluster-specific configurations + * for filters. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. * [#comment: An entry's value may be wrapped in a * :ref:`FilterConfig` * message to specify additional options.] @@ -103,14 +109,14 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight { */ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { /** - * Only one of *name* and *cluster_header* may be specified. + * Only one of ``name`` and ``cluster_header`` may be specified. * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1}] * Name of the upstream cluster. The cluster must exist in the * :ref:`cluster manager configuration `. */ 'name': (string); /** - * Only one of *name* and *cluster_header* may be specified. + * Only one of ``name`` and ``cluster_header`` may be specified. * [#next-major-version: Need to add back the validation rule: (validate.rules).string = {min_len: 1 }] * Envoy will determine the cluster to route to by reading the value of the * HTTP header named by cluster_header from the request headers. If the @@ -119,8 +125,8 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { * * .. attention:: * - * Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 - * *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. + * Internally, Envoy always uses the HTTP/2 ``:authority`` header to represent the HTTP/1 + * ``Host`` header. Thus, if attempting to match on ``Host``, match on ``:authority`` instead. * * .. note:: * @@ -128,10 +134,11 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { */ 'cluster_header': (string); /** - * An integer between 0 and :ref:`total_weight - * `. When a request matches the route, - * the choice of an upstream cluster is determined by its weight. The sum of weights across all - * entries in the clusters array must add up to the total_weight, which defaults to 100. + * The weight of the cluster. This value is relative to the other clusters' + * weights. When a request matches the route, the choice of an upstream cluster + * is determined by its weight. The sum of weights across all + * entries in the clusters array must be greater than 0, and must not exceed + * uint32_t maximal value (4294967295). */ 'weight': (_google_protobuf_UInt32Value__Output | null); /** @@ -139,7 +146,7 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { * the upstream cluster with metadata matching what is set in this field will be considered for * load balancing. Note that this will be merged with what's provided in * :ref:`RouteAction.metadata_match `, with - * values here taking precedence. The filter name should be specified as *envoy.lb*. + * values here taking precedence. The filter name should be specified as ``envoy.lb``. */ 'metadata_match': (_envoy_config_core_v3_Metadata__Output | null); /** @@ -173,11 +180,16 @@ export interface _envoy_config_route_v3_WeightedCluster_ClusterWeight__Output { */ 'response_headers_to_remove': (string)[]; /** - * The per_filter_config field can be used to provide weighted cluster-specific - * configurations for filters. The key should match the filter name, such as - * *envoy.filters.http.buffer* for the HTTP buffer filter. Use of this field is filter - * specific; see the :ref:`HTTP filter documentation ` - * for if and how it is utilized. + * The per_filter_config field can be used to provide weighted cluster-specific configurations + * for filters. + * The key should match the :ref:`filter config name + * `. + * The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also + * be used for the backwards compatibility. If there is no entry referred by the filter config name, the + * entry referred by the canonical filter name will be provided to the filters as fallback. + * + * Use of this field is filter specific; + * see the :ref:`HTTP filter documentation ` for if and how it is utilized. * [#comment: An entry's value may be wrapped in a * :ref:`FilterConfig` * message to specify additional options.] @@ -206,10 +218,10 @@ export interface WeightedCluster { 'clusters'?: (_envoy_config_route_v3_WeightedCluster_ClusterWeight)[]; /** * Specifies the runtime key prefix that should be used to construct the - * runtime keys associated with each cluster. When the *runtime_key_prefix* is + * runtime keys associated with each cluster. When the ``runtime_key_prefix`` is * specified, the router will look for weights associated with each upstream - * cluster under the key *runtime_key_prefix* + "." + *cluster[i].name* where - * *cluster[i]* denotes an entry in the clusters array field. If the runtime + * cluster under the key ``runtime_key_prefix`` + ``.`` + ``cluster[i].name`` where + * ``cluster[i]`` denotes an entry in the clusters array field. If the runtime * key for the cluster does not exist, the value specified in the * configuration file will be used as the default weight. See the :ref:`runtime documentation * ` for how key names map to the underlying implementation. @@ -217,9 +229,20 @@ export interface WeightedCluster { 'runtime_key_prefix'?: (string); /** * Specifies the total weight across all clusters. The sum of all cluster weights must equal this - * value, which must be greater than 0. Defaults to 100. + * value, if this is greater than 0. + * This field is now deprecated, and the client will use the sum of all + * cluster weights. It is up to the management server to supply the correct weights. */ 'total_weight'?: (_google_protobuf_UInt32Value | null); + /** + * Specifies the header name that is used to look up the random value passed in the request header. + * This is used to ensure consistent cluster picking across multiple proxy levels for weighted traffic. + * If header is not present or invalid, Envoy will fall back to use the internally generated random value. + * This header is expected to be single-valued header as we only want to have one selected value throughout + * the process for the consistency. And the value is a unsigned number between 0 and UINT64_MAX. + */ + 'header_name'?: (string); + 'random_value_specifier'?: "header_name"; } /** @@ -237,10 +260,10 @@ export interface WeightedCluster__Output { 'clusters': (_envoy_config_route_v3_WeightedCluster_ClusterWeight__Output)[]; /** * Specifies the runtime key prefix that should be used to construct the - * runtime keys associated with each cluster. When the *runtime_key_prefix* is + * runtime keys associated with each cluster. When the ``runtime_key_prefix`` is * specified, the router will look for weights associated with each upstream - * cluster under the key *runtime_key_prefix* + "." + *cluster[i].name* where - * *cluster[i]* denotes an entry in the clusters array field. If the runtime + * cluster under the key ``runtime_key_prefix`` + ``.`` + ``cluster[i].name`` where + * ``cluster[i]`` denotes an entry in the clusters array field. If the runtime * key for the cluster does not exist, the value specified in the * configuration file will be used as the default weight. See the :ref:`runtime documentation * ` for how key names map to the underlying implementation. @@ -248,7 +271,18 @@ export interface WeightedCluster__Output { 'runtime_key_prefix': (string); /** * Specifies the total weight across all clusters. The sum of all cluster weights must equal this - * value, which must be greater than 0. Defaults to 100. + * value, if this is greater than 0. + * This field is now deprecated, and the client will use the sum of all + * cluster weights. It is up to the management server to supply the correct weights. */ 'total_weight': (_google_protobuf_UInt32Value__Output | null); + /** + * Specifies the header name that is used to look up the random value passed in the request header. + * This is used to ensure consistent cluster picking across multiple proxy levels for weighted traffic. + * If header is not present or invalid, Envoy will fall back to use the internally generated random value. + * This header is expected to be single-valued header as we only want to have one selected value throughout + * the process for the consistency. And the value is a unsigned number between 0 and UINT64_MAX. + */ + 'header_name'?: (string); + 'random_value_specifier': "header_name"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts new file mode 100644 index 000000000..7679dfac1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts @@ -0,0 +1,421 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; +import type { TLSProperties as _envoy_data_accesslog_v3_TLSProperties, TLSProperties__Output as _envoy_data_accesslog_v3_TLSProperties__Output } from '../../../../envoy/data/accesslog/v3/TLSProperties'; +import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../../google/protobuf/Timestamp'; +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { ResponseFlags as _envoy_data_accesslog_v3_ResponseFlags, ResponseFlags__Output as _envoy_data_accesslog_v3_ResponseFlags__Output } from '../../../../envoy/data/accesslog/v3/ResponseFlags'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; +import type { AccessLogType as _envoy_data_accesslog_v3_AccessLogType } from '../../../../envoy/data/accesslog/v3/AccessLogType'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Defines fields that are shared by all Envoy access logs. + * [#next-free-field: 34] + */ +export interface AccessLogCommon { + /** + * [#not-implemented-hide:] + * This field indicates the rate at which this log entry was sampled. + * Valid range is (0.0, 1.0]. + */ + 'sample_rate'?: (number | string); + /** + * This field is the remote/origin address on which the request from the user was received. + * Note: This may not be the physical peer. E.g, if the remote address is inferred from for + * example the x-forwarder-for header, proxy protocol, etc. + */ + 'downstream_remote_address'?: (_envoy_config_core_v3_Address | null); + /** + * This field is the local/destination address on which the request from the user was received. + */ + 'downstream_local_address'?: (_envoy_config_core_v3_Address | null); + /** + * If the connection is secure,S this field will contain TLS properties. + */ + 'tls_properties'?: (_envoy_data_accesslog_v3_TLSProperties | null); + /** + * The time that Envoy started servicing this request. This is effectively the time that the first + * downstream byte is received. + */ + 'start_time'?: (_google_protobuf_Timestamp | null); + /** + * Interval between the first downstream byte received and the last + * downstream byte received (i.e. time it takes to receive a request). + */ + 'time_to_last_rx_byte'?: (_google_protobuf_Duration | null); + /** + * Interval between the first downstream byte received and the first upstream byte sent. There may + * by considerable delta between ``time_to_last_rx_byte`` and this value due to filters. + * Additionally, the same caveats apply as documented in ``time_to_last_downstream_tx_byte`` about + * not accounting for kernel socket buffer time, etc. + */ + 'time_to_first_upstream_tx_byte'?: (_google_protobuf_Duration | null); + /** + * Interval between the first downstream byte received and the last upstream byte sent. There may + * by considerable delta between ``time_to_last_rx_byte`` and this value due to filters. + * Additionally, the same caveats apply as documented in ``time_to_last_downstream_tx_byte`` about + * not accounting for kernel socket buffer time, etc. + */ + 'time_to_last_upstream_tx_byte'?: (_google_protobuf_Duration | null); + /** + * Interval between the first downstream byte received and the first upstream + * byte received (i.e. time it takes to start receiving a response). + */ + 'time_to_first_upstream_rx_byte'?: (_google_protobuf_Duration | null); + /** + * Interval between the first downstream byte received and the last upstream + * byte received (i.e. time it takes to receive a complete response). + */ + 'time_to_last_upstream_rx_byte'?: (_google_protobuf_Duration | null); + /** + * Interval between the first downstream byte received and the first downstream byte sent. + * There may be a considerable delta between the ``time_to_first_upstream_rx_byte`` and this field + * due to filters. Additionally, the same caveats apply as documented in + * ``time_to_last_downstream_tx_byte`` about not accounting for kernel socket buffer time, etc. + */ + 'time_to_first_downstream_tx_byte'?: (_google_protobuf_Duration | null); + /** + * Interval between the first downstream byte received and the last downstream byte sent. + * Depending on protocol, buffering, windowing, filters, etc. there may be a considerable delta + * between ``time_to_last_upstream_rx_byte`` and this field. Note also that this is an approximate + * time. In the current implementation it does not include kernel socket buffer time. In the + * current implementation it also does not include send window buffering inside the HTTP/2 codec. + * In the future it is likely that work will be done to make this duration more accurate. + */ + 'time_to_last_downstream_tx_byte'?: (_google_protobuf_Duration | null); + /** + * The upstream remote/destination address that handles this exchange. This does not include + * retries. + */ + 'upstream_remote_address'?: (_envoy_config_core_v3_Address | null); + /** + * The upstream local/origin address that handles this exchange. This does not include retries. + */ + 'upstream_local_address'?: (_envoy_config_core_v3_Address | null); + /** + * The upstream cluster that ``upstream_remote_address`` belongs to. + */ + 'upstream_cluster'?: (string); + /** + * Flags indicating occurrences during request/response processing. + */ + 'response_flags'?: (_envoy_data_accesslog_v3_ResponseFlags | null); + /** + * All metadata encountered during request processing, including endpoint + * selection. + * + * This can be used to associate IDs attached to the various configurations + * used to process this request with the access log entry. For example, a + * route created from a higher level forwarding rule with some ID can place + * that ID in this field and cross reference later. It can also be used to + * determine if a canary endpoint was used or not. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata | null); + /** + * If upstream connection failed due to transport socket (e.g. TLS handshake), provides the + * failure reason from the transport socket. The format of this field depends on the configured + * upstream transport socket. Common TLS failures are in + * :ref:`TLS trouble shooting `. + */ + 'upstream_transport_failure_reason'?: (string); + /** + * The name of the route + */ + 'route_name'?: (string); + /** + * This field is the downstream direct remote address on which the request from the user was + * received. Note: This is always the physical peer, even if the remote address is inferred from + * for example the x-forwarder-for header, proxy protocol, etc. + */ + 'downstream_direct_remote_address'?: (_envoy_config_core_v3_Address | null); + /** + * Map of filter state in stream info that have been configured to be logged. If the filter + * state serialized to any message other than ``google.protobuf.Any`` it will be packed into + * ``google.protobuf.Any``. + */ + 'filter_state_objects'?: ({[key: string]: _google_protobuf_Any}); + /** + * A list of custom tags, which annotate logs with additional information. + * To configure this value, users should configure + * :ref:`custom_tags `. + */ + 'custom_tags'?: ({[key: string]: string}); + /** + * For HTTP: Total duration in milliseconds of the request from the start time to the last byte out. + * For TCP: Total duration in milliseconds of the downstream connection. + * This is the total duration of the request (i.e., when the request's ActiveStream is destroyed) + * and may be longer than ``time_to_last_downstream_tx_byte``. + */ + 'duration'?: (_google_protobuf_Duration | null); + /** + * For HTTP: Number of times the request is attempted upstream. Note that the field is omitted when the request was never attempted upstream. + * For TCP: Number of times the connection request is attempted upstream. Note that the field is omitted when the connect request was never attempted upstream. + */ + 'upstream_request_attempt_count'?: (number); + /** + * Connection termination details may provide additional information about why the connection was terminated by Envoy for L4 reasons. + */ + 'connection_termination_details'?: (string); + /** + * Optional unique id of stream (TCP connection, long-live HTTP2 stream, HTTP request) for logging and tracing. + * This could be any format string that could be used to identify one stream. + */ + 'stream_id'?: (string); + /** + * If this log entry is final log entry that flushed after the stream completed or + * intermediate log entry that flushed periodically during the stream. + * There may be multiple intermediate log entries and only one final log entry for each + * long-live stream (TCP connection, long-live HTTP2 stream). + * And if it is necessary, unique ID or identifier can be added to the log entry + * :ref:`stream_id ` to + * correlate all these intermediate log entries and final log entry. + * + * .. attention:: + * + * This field is deprecated in favor of ``access_log_type`` for better indication of the + * type of the access log record. + */ + 'intermediate_log_entry'?: (boolean); + /** + * If downstream connection in listener failed due to transport socket (e.g. TLS handshake), provides the + * failure reason from the transport socket. The format of this field depends on the configured downstream + * transport socket. Common TLS failures are in :ref:`TLS trouble shooting `. + */ + 'downstream_transport_failure_reason'?: (string); + /** + * For HTTP: Total number of bytes sent to the downstream by the http stream. + * For TCP: Total number of bytes sent to the downstream by the tcp proxy. + */ + 'downstream_wire_bytes_sent'?: (number | string | Long); + /** + * For HTTP: Total number of bytes received from the downstream by the http stream. Envoy over counts sizes of received HTTP/1.1 pipelined requests by adding up bytes of requests in the pipeline to the one currently being processed. + * For TCP: Total number of bytes received from the downstream by the tcp proxy. + */ + 'downstream_wire_bytes_received'?: (number | string | Long); + /** + * For HTTP: Total number of bytes sent to the upstream by the http stream. This value accumulates during upstream retries. + * For TCP: Total number of bytes sent to the upstream by the tcp proxy. + */ + 'upstream_wire_bytes_sent'?: (number | string | Long); + /** + * For HTTP: Total number of bytes received from the upstream by the http stream. + * For TCP: Total number of bytes sent to the upstream by the tcp proxy. + */ + 'upstream_wire_bytes_received'?: (number | string | Long); + /** + * The type of the access log, which indicates when the log was recorded. + * See :ref:`ACCESS_LOG_TYPE ` for the available values. + * In case the access log was recorded by a flow which does not correspond to one of the supported + * values, then the default value will be ``NotSet``. + * For more information about how access log behaves and when it is being recorded, + * please refer to :ref:`access logging `. + */ + 'access_log_type'?: (_envoy_data_accesslog_v3_AccessLogType | keyof typeof _envoy_data_accesslog_v3_AccessLogType); +} + +/** + * Defines fields that are shared by all Envoy access logs. + * [#next-free-field: 34] + */ +export interface AccessLogCommon__Output { + /** + * [#not-implemented-hide:] + * This field indicates the rate at which this log entry was sampled. + * Valid range is (0.0, 1.0]. + */ + 'sample_rate': (number); + /** + * This field is the remote/origin address on which the request from the user was received. + * Note: This may not be the physical peer. E.g, if the remote address is inferred from for + * example the x-forwarder-for header, proxy protocol, etc. + */ + 'downstream_remote_address': (_envoy_config_core_v3_Address__Output | null); + /** + * This field is the local/destination address on which the request from the user was received. + */ + 'downstream_local_address': (_envoy_config_core_v3_Address__Output | null); + /** + * If the connection is secure,S this field will contain TLS properties. + */ + 'tls_properties': (_envoy_data_accesslog_v3_TLSProperties__Output | null); + /** + * The time that Envoy started servicing this request. This is effectively the time that the first + * downstream byte is received. + */ + 'start_time': (_google_protobuf_Timestamp__Output | null); + /** + * Interval between the first downstream byte received and the last + * downstream byte received (i.e. time it takes to receive a request). + */ + 'time_to_last_rx_byte': (_google_protobuf_Duration__Output | null); + /** + * Interval between the first downstream byte received and the first upstream byte sent. There may + * by considerable delta between ``time_to_last_rx_byte`` and this value due to filters. + * Additionally, the same caveats apply as documented in ``time_to_last_downstream_tx_byte`` about + * not accounting for kernel socket buffer time, etc. + */ + 'time_to_first_upstream_tx_byte': (_google_protobuf_Duration__Output | null); + /** + * Interval between the first downstream byte received and the last upstream byte sent. There may + * by considerable delta between ``time_to_last_rx_byte`` and this value due to filters. + * Additionally, the same caveats apply as documented in ``time_to_last_downstream_tx_byte`` about + * not accounting for kernel socket buffer time, etc. + */ + 'time_to_last_upstream_tx_byte': (_google_protobuf_Duration__Output | null); + /** + * Interval between the first downstream byte received and the first upstream + * byte received (i.e. time it takes to start receiving a response). + */ + 'time_to_first_upstream_rx_byte': (_google_protobuf_Duration__Output | null); + /** + * Interval between the first downstream byte received and the last upstream + * byte received (i.e. time it takes to receive a complete response). + */ + 'time_to_last_upstream_rx_byte': (_google_protobuf_Duration__Output | null); + /** + * Interval between the first downstream byte received and the first downstream byte sent. + * There may be a considerable delta between the ``time_to_first_upstream_rx_byte`` and this field + * due to filters. Additionally, the same caveats apply as documented in + * ``time_to_last_downstream_tx_byte`` about not accounting for kernel socket buffer time, etc. + */ + 'time_to_first_downstream_tx_byte': (_google_protobuf_Duration__Output | null); + /** + * Interval between the first downstream byte received and the last downstream byte sent. + * Depending on protocol, buffering, windowing, filters, etc. there may be a considerable delta + * between ``time_to_last_upstream_rx_byte`` and this field. Note also that this is an approximate + * time. In the current implementation it does not include kernel socket buffer time. In the + * current implementation it also does not include send window buffering inside the HTTP/2 codec. + * In the future it is likely that work will be done to make this duration more accurate. + */ + 'time_to_last_downstream_tx_byte': (_google_protobuf_Duration__Output | null); + /** + * The upstream remote/destination address that handles this exchange. This does not include + * retries. + */ + 'upstream_remote_address': (_envoy_config_core_v3_Address__Output | null); + /** + * The upstream local/origin address that handles this exchange. This does not include retries. + */ + 'upstream_local_address': (_envoy_config_core_v3_Address__Output | null); + /** + * The upstream cluster that ``upstream_remote_address`` belongs to. + */ + 'upstream_cluster': (string); + /** + * Flags indicating occurrences during request/response processing. + */ + 'response_flags': (_envoy_data_accesslog_v3_ResponseFlags__Output | null); + /** + * All metadata encountered during request processing, including endpoint + * selection. + * + * This can be used to associate IDs attached to the various configurations + * used to process this request with the access log entry. For example, a + * route created from a higher level forwarding rule with some ID can place + * that ID in this field and cross reference later. It can also be used to + * determine if a canary endpoint was used or not. + */ + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); + /** + * If upstream connection failed due to transport socket (e.g. TLS handshake), provides the + * failure reason from the transport socket. The format of this field depends on the configured + * upstream transport socket. Common TLS failures are in + * :ref:`TLS trouble shooting `. + */ + 'upstream_transport_failure_reason': (string); + /** + * The name of the route + */ + 'route_name': (string); + /** + * This field is the downstream direct remote address on which the request from the user was + * received. Note: This is always the physical peer, even if the remote address is inferred from + * for example the x-forwarder-for header, proxy protocol, etc. + */ + 'downstream_direct_remote_address': (_envoy_config_core_v3_Address__Output | null); + /** + * Map of filter state in stream info that have been configured to be logged. If the filter + * state serialized to any message other than ``google.protobuf.Any`` it will be packed into + * ``google.protobuf.Any``. + */ + 'filter_state_objects': ({[key: string]: _google_protobuf_Any__Output}); + /** + * A list of custom tags, which annotate logs with additional information. + * To configure this value, users should configure + * :ref:`custom_tags `. + */ + 'custom_tags': ({[key: string]: string}); + /** + * For HTTP: Total duration in milliseconds of the request from the start time to the last byte out. + * For TCP: Total duration in milliseconds of the downstream connection. + * This is the total duration of the request (i.e., when the request's ActiveStream is destroyed) + * and may be longer than ``time_to_last_downstream_tx_byte``. + */ + 'duration': (_google_protobuf_Duration__Output | null); + /** + * For HTTP: Number of times the request is attempted upstream. Note that the field is omitted when the request was never attempted upstream. + * For TCP: Number of times the connection request is attempted upstream. Note that the field is omitted when the connect request was never attempted upstream. + */ + 'upstream_request_attempt_count': (number); + /** + * Connection termination details may provide additional information about why the connection was terminated by Envoy for L4 reasons. + */ + 'connection_termination_details': (string); + /** + * Optional unique id of stream (TCP connection, long-live HTTP2 stream, HTTP request) for logging and tracing. + * This could be any format string that could be used to identify one stream. + */ + 'stream_id': (string); + /** + * If this log entry is final log entry that flushed after the stream completed or + * intermediate log entry that flushed periodically during the stream. + * There may be multiple intermediate log entries and only one final log entry for each + * long-live stream (TCP connection, long-live HTTP2 stream). + * And if it is necessary, unique ID or identifier can be added to the log entry + * :ref:`stream_id ` to + * correlate all these intermediate log entries and final log entry. + * + * .. attention:: + * + * This field is deprecated in favor of ``access_log_type`` for better indication of the + * type of the access log record. + */ + 'intermediate_log_entry': (boolean); + /** + * If downstream connection in listener failed due to transport socket (e.g. TLS handshake), provides the + * failure reason from the transport socket. The format of this field depends on the configured downstream + * transport socket. Common TLS failures are in :ref:`TLS trouble shooting `. + */ + 'downstream_transport_failure_reason': (string); + /** + * For HTTP: Total number of bytes sent to the downstream by the http stream. + * For TCP: Total number of bytes sent to the downstream by the tcp proxy. + */ + 'downstream_wire_bytes_sent': (string); + /** + * For HTTP: Total number of bytes received from the downstream by the http stream. Envoy over counts sizes of received HTTP/1.1 pipelined requests by adding up bytes of requests in the pipeline to the one currently being processed. + * For TCP: Total number of bytes received from the downstream by the tcp proxy. + */ + 'downstream_wire_bytes_received': (string); + /** + * For HTTP: Total number of bytes sent to the upstream by the http stream. This value accumulates during upstream retries. + * For TCP: Total number of bytes sent to the upstream by the tcp proxy. + */ + 'upstream_wire_bytes_sent': (string); + /** + * For HTTP: Total number of bytes received from the upstream by the http stream. + * For TCP: Total number of bytes sent to the upstream by the tcp proxy. + */ + 'upstream_wire_bytes_received': (string); + /** + * The type of the access log, which indicates when the log was recorded. + * See :ref:`ACCESS_LOG_TYPE ` for the available values. + * In case the access log was recorded by a flow which does not correspond to one of the supported + * values, then the default value will be ``NotSet``. + * For more information about how access log behaves and when it is being recorded, + * please refer to :ref:`access logging `. + */ + 'access_log_type': (keyof typeof _envoy_data_accesslog_v3_AccessLogType); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts new file mode 100644 index 000000000..a50bb42c1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts @@ -0,0 +1,15 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +export enum AccessLogType { + NotSet = 0, + TcpUpstreamConnected = 1, + TcpPeriodic = 2, + TcpConnectionEnd = 3, + DownstreamStart = 4, + DownstreamPeriodic = 5, + DownstreamEnd = 6, + UpstreamPoolReady = 7, + UpstreamPeriodic = 8, + UpstreamEnd = 9, + DownstreamTunnelSuccessfullyEstablished = 10, +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ConnectionProperties.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ConnectionProperties.ts new file mode 100644 index 000000000..a0cdfc75f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ConnectionProperties.ts @@ -0,0 +1,31 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +import type { Long } from '@grpc/proto-loader'; + +/** + * Defines fields for a connection + */ +export interface ConnectionProperties { + /** + * Number of bytes received from downstream. + */ + 'received_bytes'?: (number | string | Long); + /** + * Number of bytes sent to downstream. + */ + 'sent_bytes'?: (number | string | Long); +} + +/** + * Defines fields for a connection + */ +export interface ConnectionProperties__Output { + /** + * Number of bytes received from downstream. + */ + 'received_bytes': (string); + /** + * Number of bytes sent to downstream. + */ + 'sent_bytes': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts new file mode 100644 index 000000000..760954bb1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts @@ -0,0 +1,50 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +import type { AccessLogCommon as _envoy_data_accesslog_v3_AccessLogCommon, AccessLogCommon__Output as _envoy_data_accesslog_v3_AccessLogCommon__Output } from '../../../../envoy/data/accesslog/v3/AccessLogCommon'; +import type { HTTPRequestProperties as _envoy_data_accesslog_v3_HTTPRequestProperties, HTTPRequestProperties__Output as _envoy_data_accesslog_v3_HTTPRequestProperties__Output } from '../../../../envoy/data/accesslog/v3/HTTPRequestProperties'; +import type { HTTPResponseProperties as _envoy_data_accesslog_v3_HTTPResponseProperties, HTTPResponseProperties__Output as _envoy_data_accesslog_v3_HTTPResponseProperties__Output } from '../../../../envoy/data/accesslog/v3/HTTPResponseProperties'; + +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +/** + * HTTP version + */ +export enum _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion { + PROTOCOL_UNSPECIFIED = 0, + HTTP10 = 1, + HTTP11 = 2, + HTTP2 = 3, + HTTP3 = 4, +} + +export interface HTTPAccessLogEntry { + /** + * Common properties shared by all Envoy access logs. + */ + 'common_properties'?: (_envoy_data_accesslog_v3_AccessLogCommon | null); + 'protocol_version'?: (_envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion | keyof typeof _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion); + /** + * Description of the incoming HTTP request. + */ + 'request'?: (_envoy_data_accesslog_v3_HTTPRequestProperties | null); + /** + * Description of the outgoing HTTP response. + */ + 'response'?: (_envoy_data_accesslog_v3_HTTPResponseProperties | null); +} + +export interface HTTPAccessLogEntry__Output { + /** + * Common properties shared by all Envoy access logs. + */ + 'common_properties': (_envoy_data_accesslog_v3_AccessLogCommon__Output | null); + 'protocol_version': (keyof typeof _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion); + /** + * Description of the incoming HTTP request. + */ + 'request': (_envoy_data_accesslog_v3_HTTPRequestProperties__Output | null); + /** + * Description of the outgoing HTTP response. + */ + 'response': (_envoy_data_accesslog_v3_HTTPResponseProperties__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts new file mode 100644 index 000000000..9e145503c --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts @@ -0,0 +1,163 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +import type { RequestMethod as _envoy_config_core_v3_RequestMethod } from '../../../../envoy/config/core/v3/RequestMethod'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Long } from '@grpc/proto-loader'; + +/** + * [#next-free-field: 16] + */ +export interface HTTPRequestProperties { + /** + * The request method (RFC 7231/2616). + */ + 'request_method'?: (_envoy_config_core_v3_RequestMethod | keyof typeof _envoy_config_core_v3_RequestMethod); + /** + * The scheme portion of the incoming request URI. + */ + 'scheme'?: (string); + /** + * HTTP/2 ``:authority`` or HTTP/1.1 ``Host`` header value. + */ + 'authority'?: (string); + /** + * The port of the incoming request URI + * (unused currently, as port is composed onto authority). + */ + 'port'?: (_google_protobuf_UInt32Value | null); + /** + * The path portion from the incoming request URI. + */ + 'path'?: (string); + /** + * Value of the ``User-Agent`` request header. + */ + 'user_agent'?: (string); + /** + * Value of the ``Referer`` request header. + */ + 'referer'?: (string); + /** + * Value of the ``X-Forwarded-For`` request header. + */ + 'forwarded_for'?: (string); + /** + * Value of the ``X-Request-Id`` request header + * + * This header is used by Envoy to uniquely identify a request. + * It will be generated for all external requests and internal requests that + * do not already have a request ID. + */ + 'request_id'?: (string); + /** + * Value of the ``X-Envoy-Original-Path`` request header. + */ + 'original_path'?: (string); + /** + * Size of the HTTP request headers in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include overhead from framing or encoding at other networking layers. + */ + 'request_headers_bytes'?: (number | string | Long); + /** + * Size of the HTTP request body in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include overhead from framing or encoding at other networking layers. + */ + 'request_body_bytes'?: (number | string | Long); + /** + * Map of additional headers that have been configured to be logged. + */ + 'request_headers'?: ({[key: string]: string}); + /** + * Number of header bytes sent to the upstream by the http stream, including protocol overhead. + * + * This value accumulates during upstream retries. + */ + 'upstream_header_bytes_sent'?: (number | string | Long); + /** + * Number of header bytes received from the downstream by the http stream, including protocol overhead. + */ + 'downstream_header_bytes_received'?: (number | string | Long); +} + +/** + * [#next-free-field: 16] + */ +export interface HTTPRequestProperties__Output { + /** + * The request method (RFC 7231/2616). + */ + 'request_method': (keyof typeof _envoy_config_core_v3_RequestMethod); + /** + * The scheme portion of the incoming request URI. + */ + 'scheme': (string); + /** + * HTTP/2 ``:authority`` or HTTP/1.1 ``Host`` header value. + */ + 'authority': (string); + /** + * The port of the incoming request URI + * (unused currently, as port is composed onto authority). + */ + 'port': (_google_protobuf_UInt32Value__Output | null); + /** + * The path portion from the incoming request URI. + */ + 'path': (string); + /** + * Value of the ``User-Agent`` request header. + */ + 'user_agent': (string); + /** + * Value of the ``Referer`` request header. + */ + 'referer': (string); + /** + * Value of the ``X-Forwarded-For`` request header. + */ + 'forwarded_for': (string); + /** + * Value of the ``X-Request-Id`` request header + * + * This header is used by Envoy to uniquely identify a request. + * It will be generated for all external requests and internal requests that + * do not already have a request ID. + */ + 'request_id': (string); + /** + * Value of the ``X-Envoy-Original-Path`` request header. + */ + 'original_path': (string); + /** + * Size of the HTTP request headers in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include overhead from framing or encoding at other networking layers. + */ + 'request_headers_bytes': (string); + /** + * Size of the HTTP request body in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include overhead from framing or encoding at other networking layers. + */ + 'request_body_bytes': (string); + /** + * Map of additional headers that have been configured to be logged. + */ + 'request_headers': ({[key: string]: string}); + /** + * Number of header bytes sent to the upstream by the http stream, including protocol overhead. + * + * This value accumulates during upstream retries. + */ + 'upstream_header_bytes_sent': (string); + /** + * Number of header bytes received from the downstream by the http stream, including protocol overhead. + */ + 'downstream_header_bytes_received': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPResponseProperties.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPResponseProperties.ts new file mode 100644 index 000000000..1368fc8cd --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPResponseProperties.ts @@ -0,0 +1,92 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; +import type { Long } from '@grpc/proto-loader'; + +/** + * [#next-free-field: 9] + */ +export interface HTTPResponseProperties { + /** + * The HTTP response code returned by Envoy. + */ + 'response_code'?: (_google_protobuf_UInt32Value | null); + /** + * Size of the HTTP response headers in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include protocol overhead or overhead from framing or encoding at other networking layers. + */ + 'response_headers_bytes'?: (number | string | Long); + /** + * Size of the HTTP response body in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include overhead from framing or encoding at other networking layers. + */ + 'response_body_bytes'?: (number | string | Long); + /** + * Map of additional headers configured to be logged. + */ + 'response_headers'?: ({[key: string]: string}); + /** + * Map of trailers configured to be logged. + */ + 'response_trailers'?: ({[key: string]: string}); + /** + * The HTTP response code details. + */ + 'response_code_details'?: (string); + /** + * Number of header bytes received from the upstream by the http stream, including protocol overhead. + */ + 'upstream_header_bytes_received'?: (number | string | Long); + /** + * Number of header bytes sent to the downstream by the http stream, including protocol overhead. + */ + 'downstream_header_bytes_sent'?: (number | string | Long); +} + +/** + * [#next-free-field: 9] + */ +export interface HTTPResponseProperties__Output { + /** + * The HTTP response code returned by Envoy. + */ + 'response_code': (_google_protobuf_UInt32Value__Output | null); + /** + * Size of the HTTP response headers in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include protocol overhead or overhead from framing or encoding at other networking layers. + */ + 'response_headers_bytes': (string); + /** + * Size of the HTTP response body in bytes. + * + * This value is captured from the OSI layer 7 perspective, i.e. it does not + * include overhead from framing or encoding at other networking layers. + */ + 'response_body_bytes': (string); + /** + * Map of additional headers configured to be logged. + */ + 'response_headers': ({[key: string]: string}); + /** + * Map of trailers configured to be logged. + */ + 'response_trailers': ({[key: string]: string}); + /** + * The HTTP response code details. + */ + 'response_code_details': (string); + /** + * Number of header bytes received from the upstream by the http stream, including protocol overhead. + */ + 'upstream_header_bytes_received': (string); + /** + * Number of header bytes sent to the downstream by the http stream, including protocol overhead. + */ + 'downstream_header_bytes_sent': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts new file mode 100644 index 000000000..ec45824b3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts @@ -0,0 +1,255 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + + +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +/** + * Reasons why the request was unauthorized + */ +export enum _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason { + REASON_UNSPECIFIED = 0, + /** + * The request was denied by the external authorization service. + */ + EXTERNAL_SERVICE = 1, +} + +export interface _envoy_data_accesslog_v3_ResponseFlags_Unauthorized { + 'reason'?: (_envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason | keyof typeof _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason); +} + +export interface _envoy_data_accesslog_v3_ResponseFlags_Unauthorized__Output { + 'reason': (keyof typeof _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason); +} + +/** + * Flags indicating occurrences during request/response processing. + * [#next-free-field: 28] + */ +export interface ResponseFlags { + /** + * Indicates local server healthcheck failed. + */ + 'failed_local_healthcheck'?: (boolean); + /** + * Indicates there was no healthy upstream. + */ + 'no_healthy_upstream'?: (boolean); + /** + * Indicates an there was an upstream request timeout. + */ + 'upstream_request_timeout'?: (boolean); + /** + * Indicates local codec level reset was sent on the stream. + */ + 'local_reset'?: (boolean); + /** + * Indicates remote codec level reset was received on the stream. + */ + 'upstream_remote_reset'?: (boolean); + /** + * Indicates there was a local reset by a connection pool due to an initial connection failure. + */ + 'upstream_connection_failure'?: (boolean); + /** + * Indicates the stream was reset due to an upstream connection termination. + */ + 'upstream_connection_termination'?: (boolean); + /** + * Indicates the stream was reset because of a resource overflow. + */ + 'upstream_overflow'?: (boolean); + /** + * Indicates no route was found for the request. + */ + 'no_route_found'?: (boolean); + /** + * Indicates that the request was delayed before proxying. + */ + 'delay_injected'?: (boolean); + /** + * Indicates that the request was aborted with an injected error code. + */ + 'fault_injected'?: (boolean); + /** + * Indicates that the request was rate-limited locally. + */ + 'rate_limited'?: (boolean); + /** + * Indicates if the request was deemed unauthorized and the reason for it. + */ + 'unauthorized_details'?: (_envoy_data_accesslog_v3_ResponseFlags_Unauthorized | null); + /** + * Indicates that the request was rejected because there was an error in rate limit service. + */ + 'rate_limit_service_error'?: (boolean); + /** + * Indicates the stream was reset due to a downstream connection termination. + */ + 'downstream_connection_termination'?: (boolean); + /** + * Indicates that the upstream retry limit was exceeded, resulting in a downstream error. + */ + 'upstream_retry_limit_exceeded'?: (boolean); + /** + * Indicates that the stream idle timeout was hit, resulting in a downstream 408. + */ + 'stream_idle_timeout'?: (boolean); + /** + * Indicates that the request was rejected because an envoy request header failed strict + * validation. + */ + 'invalid_envoy_request_headers'?: (boolean); + /** + * Indicates there was an HTTP protocol error on the downstream request. + */ + 'downstream_protocol_error'?: (boolean); + /** + * Indicates there was a max stream duration reached on the upstream request. + */ + 'upstream_max_stream_duration_reached'?: (boolean); + /** + * Indicates the response was served from a cache filter. + */ + 'response_from_cache_filter'?: (boolean); + /** + * Indicates that a filter configuration is not available. + */ + 'no_filter_config_found'?: (boolean); + /** + * Indicates that request or connection exceeded the downstream connection duration. + */ + 'duration_timeout'?: (boolean); + /** + * Indicates there was an HTTP protocol error in the upstream response. + */ + 'upstream_protocol_error'?: (boolean); + /** + * Indicates no cluster was found for the request. + */ + 'no_cluster_found'?: (boolean); + /** + * Indicates overload manager terminated the request. + */ + 'overload_manager'?: (boolean); + /** + * Indicates a DNS resolution failed. + */ + 'dns_resolution_failure'?: (boolean); +} + +/** + * Flags indicating occurrences during request/response processing. + * [#next-free-field: 28] + */ +export interface ResponseFlags__Output { + /** + * Indicates local server healthcheck failed. + */ + 'failed_local_healthcheck': (boolean); + /** + * Indicates there was no healthy upstream. + */ + 'no_healthy_upstream': (boolean); + /** + * Indicates an there was an upstream request timeout. + */ + 'upstream_request_timeout': (boolean); + /** + * Indicates local codec level reset was sent on the stream. + */ + 'local_reset': (boolean); + /** + * Indicates remote codec level reset was received on the stream. + */ + 'upstream_remote_reset': (boolean); + /** + * Indicates there was a local reset by a connection pool due to an initial connection failure. + */ + 'upstream_connection_failure': (boolean); + /** + * Indicates the stream was reset due to an upstream connection termination. + */ + 'upstream_connection_termination': (boolean); + /** + * Indicates the stream was reset because of a resource overflow. + */ + 'upstream_overflow': (boolean); + /** + * Indicates no route was found for the request. + */ + 'no_route_found': (boolean); + /** + * Indicates that the request was delayed before proxying. + */ + 'delay_injected': (boolean); + /** + * Indicates that the request was aborted with an injected error code. + */ + 'fault_injected': (boolean); + /** + * Indicates that the request was rate-limited locally. + */ + 'rate_limited': (boolean); + /** + * Indicates if the request was deemed unauthorized and the reason for it. + */ + 'unauthorized_details': (_envoy_data_accesslog_v3_ResponseFlags_Unauthorized__Output | null); + /** + * Indicates that the request was rejected because there was an error in rate limit service. + */ + 'rate_limit_service_error': (boolean); + /** + * Indicates the stream was reset due to a downstream connection termination. + */ + 'downstream_connection_termination': (boolean); + /** + * Indicates that the upstream retry limit was exceeded, resulting in a downstream error. + */ + 'upstream_retry_limit_exceeded': (boolean); + /** + * Indicates that the stream idle timeout was hit, resulting in a downstream 408. + */ + 'stream_idle_timeout': (boolean); + /** + * Indicates that the request was rejected because an envoy request header failed strict + * validation. + */ + 'invalid_envoy_request_headers': (boolean); + /** + * Indicates there was an HTTP protocol error on the downstream request. + */ + 'downstream_protocol_error': (boolean); + /** + * Indicates there was a max stream duration reached on the upstream request. + */ + 'upstream_max_stream_duration_reached': (boolean); + /** + * Indicates the response was served from a cache filter. + */ + 'response_from_cache_filter': (boolean); + /** + * Indicates that a filter configuration is not available. + */ + 'no_filter_config_found': (boolean); + /** + * Indicates that request or connection exceeded the downstream connection duration. + */ + 'duration_timeout': (boolean); + /** + * Indicates there was an HTTP protocol error in the upstream response. + */ + 'upstream_protocol_error': (boolean); + /** + * Indicates no cluster was found for the request. + */ + 'no_cluster_found': (boolean); + /** + * Indicates overload manager terminated the request. + */ + 'overload_manager': (boolean); + /** + * Indicates a DNS resolution failed. + */ + 'dns_resolution_failure': (boolean); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TCPAccessLogEntry.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TCPAccessLogEntry.ts new file mode 100644 index 000000000..55e9cace0 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TCPAccessLogEntry.ts @@ -0,0 +1,26 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +import type { AccessLogCommon as _envoy_data_accesslog_v3_AccessLogCommon, AccessLogCommon__Output as _envoy_data_accesslog_v3_AccessLogCommon__Output } from '../../../../envoy/data/accesslog/v3/AccessLogCommon'; +import type { ConnectionProperties as _envoy_data_accesslog_v3_ConnectionProperties, ConnectionProperties__Output as _envoy_data_accesslog_v3_ConnectionProperties__Output } from '../../../../envoy/data/accesslog/v3/ConnectionProperties'; + +export interface TCPAccessLogEntry { + /** + * Common properties shared by all Envoy access logs. + */ + 'common_properties'?: (_envoy_data_accesslog_v3_AccessLogCommon | null); + /** + * Properties of the TCP connection. + */ + 'connection_properties'?: (_envoy_data_accesslog_v3_ConnectionProperties | null); +} + +export interface TCPAccessLogEntry__Output { + /** + * Common properties shared by all Envoy access logs. + */ + 'common_properties': (_envoy_data_accesslog_v3_AccessLogCommon__Output | null); + /** + * Properties of the TCP connection. + */ + 'connection_properties': (_envoy_data_accesslog_v3_ConnectionProperties__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts new file mode 100644 index 000000000..106d24d09 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts @@ -0,0 +1,131 @@ +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; + +export interface _envoy_data_accesslog_v3_TLSProperties_CertificateProperties { + /** + * SANs present in the certificate. + */ + 'subject_alt_name'?: (_envoy_data_accesslog_v3_TLSProperties_CertificateProperties_SubjectAltName)[]; + /** + * The subject field of the certificate. + */ + 'subject'?: (string); +} + +export interface _envoy_data_accesslog_v3_TLSProperties_CertificateProperties__Output { + /** + * SANs present in the certificate. + */ + 'subject_alt_name': (_envoy_data_accesslog_v3_TLSProperties_CertificateProperties_SubjectAltName__Output)[]; + /** + * The subject field of the certificate. + */ + 'subject': (string); +} + +export interface _envoy_data_accesslog_v3_TLSProperties_CertificateProperties_SubjectAltName { + 'uri'?: (string); + /** + * [#not-implemented-hide:] + */ + 'dns'?: (string); + 'san'?: "uri"|"dns"; +} + +export interface _envoy_data_accesslog_v3_TLSProperties_CertificateProperties_SubjectAltName__Output { + 'uri'?: (string); + /** + * [#not-implemented-hide:] + */ + 'dns'?: (string); + 'san': "uri"|"dns"; +} + +// Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto + +export enum _envoy_data_accesslog_v3_TLSProperties_TLSVersion { + VERSION_UNSPECIFIED = 0, + TLSv1 = 1, + TLSv1_1 = 2, + TLSv1_2 = 3, + TLSv1_3 = 4, +} + +/** + * Properties of a negotiated TLS connection. + * [#next-free-field: 8] + */ +export interface TLSProperties { + /** + * Version of TLS that was negotiated. + */ + 'tls_version'?: (_envoy_data_accesslog_v3_TLSProperties_TLSVersion | keyof typeof _envoy_data_accesslog_v3_TLSProperties_TLSVersion); + /** + * TLS cipher suite negotiated during handshake. The value is a + * four-digit hex code defined by the IANA TLS Cipher Suite Registry + * (e.g. ``009C`` for ``TLS_RSA_WITH_AES_128_GCM_SHA256``). + * + * Here it is expressed as an integer. + */ + 'tls_cipher_suite'?: (_google_protobuf_UInt32Value | null); + /** + * SNI hostname from handshake. + */ + 'tls_sni_hostname'?: (string); + /** + * Properties of the local certificate used to negotiate TLS. + */ + 'local_certificate_properties'?: (_envoy_data_accesslog_v3_TLSProperties_CertificateProperties | null); + /** + * Properties of the peer certificate used to negotiate TLS. + */ + 'peer_certificate_properties'?: (_envoy_data_accesslog_v3_TLSProperties_CertificateProperties | null); + /** + * The TLS session ID. + */ + 'tls_session_id'?: (string); + /** + * The ``JA3`` fingerprint when ``JA3`` fingerprinting is enabled. + */ + 'ja3_fingerprint'?: (string); +} + +/** + * Properties of a negotiated TLS connection. + * [#next-free-field: 8] + */ +export interface TLSProperties__Output { + /** + * Version of TLS that was negotiated. + */ + 'tls_version': (keyof typeof _envoy_data_accesslog_v3_TLSProperties_TLSVersion); + /** + * TLS cipher suite negotiated during handshake. The value is a + * four-digit hex code defined by the IANA TLS Cipher Suite Registry + * (e.g. ``009C`` for ``TLS_RSA_WITH_AES_128_GCM_SHA256``). + * + * Here it is expressed as an integer. + */ + 'tls_cipher_suite': (_google_protobuf_UInt32Value__Output | null); + /** + * SNI hostname from handshake. + */ + 'tls_sni_hostname': (string); + /** + * Properties of the local certificate used to negotiate TLS. + */ + 'local_certificate_properties': (_envoy_data_accesslog_v3_TLSProperties_CertificateProperties__Output | null); + /** + * Properties of the peer certificate used to negotiate TLS. + */ + 'peer_certificate_properties': (_envoy_data_accesslog_v3_TLSProperties_CertificateProperties__Output | null); + /** + * The TLS session ID. + */ + 'tls_session_id': (string); + /** + * The ``JA3`` fingerprint when ``JA3`` fingerprinting is enabled. + */ + 'ja3_fingerprint': (string); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts index d78bd9e4a..dd3a3b50c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/fault/v3/HTTPFault.ts @@ -5,9 +5,10 @@ import type { FaultAbort as _envoy_extensions_filters_http_fault_v3_FaultAbort, import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../../../envoy/config/route/v3/HeaderMatcher'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../../google/protobuf/UInt32Value'; import type { FaultRateLimit as _envoy_extensions_filters_common_fault_v3_FaultRateLimit, FaultRateLimit__Output as _envoy_extensions_filters_common_fault_v3_FaultRateLimit__Output } from '../../../../../../envoy/extensions/filters/common/fault/v3/FaultRateLimit'; +import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../../../google/protobuf/Struct'; /** - * [#next-free-field: 16] + * [#next-free-field: 17] */ export interface HTTPFault { /** @@ -17,7 +18,7 @@ export interface HTTPFault { 'delay'?: (_envoy_extensions_filters_common_fault_v3_FaultDelay | null); /** * If specified, the filter will abort requests based on the values in - * the object. At least *abort* or *delay* must be specified. + * the object. At least ``abort`` or ``delay`` must be specified. */ 'abort'?: (_envoy_extensions_filters_http_fault_v3_FaultAbort | null); /** @@ -35,7 +36,7 @@ export interface HTTPFault { * The filter will check the request's headers against all the specified * headers in the filter config. A match will happen if all the headers in the * config are present in the request with the same values (or based on - * presence if the *value* field is not in the config). + * presence if the ``value`` field is not in the config). */ 'headers'?: (_envoy_config_route_v3_HeaderMatcher)[]; /** @@ -52,9 +53,9 @@ export interface HTTPFault { * filter. Note that because this setting can be overridden at the route level, it's possible * for the number of active faults to be greater than this value (if injected via a different * route). If not specified, defaults to unlimited. This setting can be overridden via - * `runtime ` and any faults that are not injected - * due to overflow will be indicated via the `faults_overflow - * ` stat. + * ``runtime `` and any faults that are not injected + * due to overflow will be indicated via the ``faults_overflow + * `` stat. * * .. attention:: * Like other :ref:`circuit breakers ` in Envoy, this is a fuzzy @@ -114,10 +115,18 @@ export interface HTTPFault { * Default value is false. */ 'disable_downstream_cluster_stats'?: (boolean); + /** + * When an abort or delay fault is executed, the metadata struct provided here will be added to the + * request's dynamic metadata under the namespace corresponding to the name of the fault filter. + * This data can be logged as part of Access Logs using the :ref:`command operator + * ` %DYNAMIC_METADATA(NAMESPACE)%, where NAMESPACE is the name of + * the fault filter. + */ + 'filter_metadata'?: (_google_protobuf_Struct | null); } /** - * [#next-free-field: 16] + * [#next-free-field: 17] */ export interface HTTPFault__Output { /** @@ -127,7 +136,7 @@ export interface HTTPFault__Output { 'delay': (_envoy_extensions_filters_common_fault_v3_FaultDelay__Output | null); /** * If specified, the filter will abort requests based on the values in - * the object. At least *abort* or *delay* must be specified. + * the object. At least ``abort`` or ``delay`` must be specified. */ 'abort': (_envoy_extensions_filters_http_fault_v3_FaultAbort__Output | null); /** @@ -145,7 +154,7 @@ export interface HTTPFault__Output { * The filter will check the request's headers against all the specified * headers in the filter config. A match will happen if all the headers in the * config are present in the request with the same values (or based on - * presence if the *value* field is not in the config). + * presence if the ``value`` field is not in the config). */ 'headers': (_envoy_config_route_v3_HeaderMatcher__Output)[]; /** @@ -162,9 +171,9 @@ export interface HTTPFault__Output { * filter. Note that because this setting can be overridden at the route level, it's possible * for the number of active faults to be greater than this value (if injected via a different * route). If not specified, defaults to unlimited. This setting can be overridden via - * `runtime ` and any faults that are not injected - * due to overflow will be indicated via the `faults_overflow - * ` stat. + * ``runtime `` and any faults that are not injected + * due to overflow will be indicated via the ``faults_overflow + * `` stat. * * .. attention:: * Like other :ref:`circuit breakers ` in Envoy, this is a fuzzy @@ -224,4 +233,12 @@ export interface HTTPFault__Output { * Default value is false. */ 'disable_downstream_cluster_stats': (boolean); + /** + * When an abort or delay fault is executed, the metadata struct provided here will be added to the + * request's dynamic metadata under the namespace corresponding to the name of the fault filter. + * This data can be logged as part of Access Logs using the :ref:`command operator + * ` %DYNAMIC_METADATA(NAMESPACE)%, where NAMESPACE is the name of + * the fault filter. + */ + 'filter_metadata': (_google_protobuf_Struct__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts index fdf7084fe..1b4f36fd8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts @@ -19,6 +19,7 @@ import type { SchemeHeaderTransformation as _envoy_config_core_v3_SchemeHeaderTr import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../../../envoy/type/v3/Percent'; import type { CustomTag as _envoy_type_tracing_v3_CustomTag, CustomTag__Output as _envoy_type_tracing_v3_CustomTag__Output } from '../../../../../../envoy/type/tracing/v3/CustomTag'; import type { _envoy_config_trace_v3_Tracing_Http, _envoy_config_trace_v3_Tracing_Http__Output } from '../../../../../../envoy/config/trace/v3/Tracing'; +import type { CidrRange as _envoy_config_core_v3_CidrRange, CidrRange__Output as _envoy_config_core_v3_CidrRange__Output } from '../../../../../../envoy/config/core/v3/CidrRange'; import type { PathTransformation as _envoy_type_http_v3_PathTransformation, PathTransformation__Output as _envoy_type_http_v3_PathTransformation__Output } from '../../../../../../envoy/type/http/v3/PathTransformation'; // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -83,11 +84,68 @@ export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpCon ALWAYS_FORWARD_ONLY = 4, } +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_HcmAccessLogOptions { + /** + * The interval to flush the above access logs. By default, the HCM will flush exactly one access log + * on stream close, when the HTTP request is complete. If this field is set, the HCM will flush access + * logs periodically at the specified interval. This is especially useful in the case of long-lived + * requests, such as CONNECT and Websockets. Final access logs can be detected via the + * `requestComplete()` method of `StreamInfo` in access log filters, or thru the `%DURATION%` substitution + * string. + * The interval must be at least 1 millisecond. + */ + 'access_log_flush_interval'?: (_google_protobuf_Duration | null); + /** + * If set to true, HCM will flush an access log when a new HTTP request is received, after request + * headers have been evaluated, before iterating through the HTTP filter chain. + * This log record, if enabled, does not depend on periodic log records or request completion log. + * Details related to upstream cluster, such as upstream host, will not be available for this log. + */ + 'flush_access_log_on_new_request'?: (boolean); + /** + * If true, the HCM will flush an access log when a tunnel is successfully established. For example, + * this could be when an upstream has successfully returned 101 Switching Protocols, or when the proxy + * has returned 200 to a CONNECT request. + */ + 'flush_log_on_tunnel_successfully_established'?: (boolean); +} + +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_HcmAccessLogOptions__Output { + /** + * The interval to flush the above access logs. By default, the HCM will flush exactly one access log + * on stream close, when the HTTP request is complete. If this field is set, the HCM will flush access + * logs periodically at the specified interval. This is especially useful in the case of long-lived + * requests, such as CONNECT and Websockets. Final access logs can be detected via the + * `requestComplete()` method of `StreamInfo` in access log filters, or thru the `%DURATION%` substitution + * string. + * The interval must be at least 1 millisecond. + */ + 'access_log_flush_interval': (_google_protobuf_Duration__Output | null); + /** + * If set to true, HCM will flush an access log when a new HTTP request is received, after request + * headers have been evaluated, before iterating through the HTTP filter chain. + * This log record, if enabled, does not depend on periodic log records or request completion log. + * Details related to upstream cluster, such as upstream host, will not be available for this log. + */ + 'flush_access_log_on_new_request': (boolean); + /** + * If true, the HCM will flush an access log when a tunnel is successfully established. For example, + * this could be when an upstream has successfully returned 101 Switching Protocols, or when the proxy + * has returned 200 to a CONNECT request. + */ + 'flush_log_on_tunnel_successfully_established': (boolean); +} + export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig { /** * Whether unix socket addresses should be considered internal. */ 'unix_sockets'?: (boolean); + /** + * List of CIDR ranges that are treated as internal. If unset, then RFC1918 / RFC4193 + * IP addresses will be considered internal. + */ + 'cidr_ranges'?: (_envoy_config_core_v3_CidrRange)[]; } export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_InternalAddressConfig__Output { @@ -95,6 +153,11 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * Whether unix socket addresses should be considered internal. */ 'unix_sockets': (boolean); + /** + * List of CIDR ranges that are treated as internal. If unset, then RFC1918 / RFC4193 + * IP addresses will be considered internal. + */ + 'cidr_ranges': (_envoy_config_core_v3_CidrRange__Output)[]; } // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -116,15 +179,15 @@ export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpCon * path will be visible internally if a transformation is enabled. Any path rewrites that the * router performs (e.g. :ref:`regex_rewrite * ` or :ref:`prefix_rewrite - * `) will apply to the *:path* header + * `) will apply to the ``:path`` header * destined for the upstream. * - * Note: access logging and tracing will show the original *:path* header. + * Note: access logging and tracing will show the original ``:path`` header. */ export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions { /** * [#not-implemented-hide:] Normalization applies internally before any processing of requests by - * HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults + * HTTP filters, routing, and matching *and* will affect the forwarded ``:path`` header. Defaults * to :ref:`NormalizePathRFC3986 * `. When not * specified, this value may be overridden by the runtime variable @@ -136,7 +199,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht /** * [#not-implemented-hide:] Normalization only applies internally before any processing of * requests by HTTP filters, routing, and matching. These will be applied after full - * transformation is applied. The *:path* header before this transformation will be restored in + * transformation is applied. The ``:path`` header before this transformation will be restored in * the router filter and sent upstream unless it was mutated by a filter. Defaults to no * transformations. * Multiple actions can be applied in the same Transformation, forming a sequential @@ -153,15 +216,15 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * path will be visible internally if a transformation is enabled. Any path rewrites that the * router performs (e.g. :ref:`regex_rewrite * ` or :ref:`prefix_rewrite - * `) will apply to the *:path* header + * `) will apply to the ``:path`` header * destined for the upstream. * - * Note: access logging and tracing will show the original *:path* header. + * Note: access logging and tracing will show the original ``:path`` header. */ export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathNormalizationOptions__Output { /** * [#not-implemented-hide:] Normalization applies internally before any processing of requests by - * HTTP filters, routing, and matching *and* will affect the forwarded *:path* header. Defaults + * HTTP filters, routing, and matching *and* will affect the forwarded ``:path`` header. Defaults * to :ref:`NormalizePathRFC3986 * `. When not * specified, this value may be overridden by the runtime variable @@ -173,7 +236,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht /** * [#not-implemented-hide:] Normalization only applies internally before any processing of * requests by HTTP filters, routing, and matching. These will be applied after full - * transformation is applied. The *:path* header before this transformation will be restored in + * transformation is applied. The ``:path`` header before this transformation will be restored in * the router filter and sent upstream unless it was mutated by a filter. Defaults to no * transformations. * Multiple actions can be applied in the same Transformation, forming a sequential @@ -224,6 +287,122 @@ export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpCon UNESCAPE_AND_FORWARD = 4, } +/** + * Configures the manner in which the Proxy-Status HTTP response header is + * populated. + * + * See the [Proxy-Status + * RFC](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-proxy-status-08). + * [#comment:TODO: Update this with the non-draft URL when finalized.] + * + * The Proxy-Status header is a string of the form: + * + * "; error=; details=
" + * [#next-free-field: 7] + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ProxyStatusConfig { + /** + * If true, the details field of the Proxy-Status header is not populated with stream_info.response_code_details. + * This value defaults to ``false``, i.e. the ``details`` field is populated by default. + */ + 'remove_details'?: (boolean); + /** + * If true, the details field of the Proxy-Status header will not contain + * connection termination details. This value defaults to ``false``, i.e. the + * ``details`` field will contain connection termination details by default. + */ + 'remove_connection_termination_details'?: (boolean); + /** + * If true, the details field of the Proxy-Status header will not contain an + * enumeration of the Envoy ResponseFlags. This value defaults to ``false``, + * i.e. the ``details`` field will contain a list of ResponseFlags by default. + */ + 'remove_response_flags'?: (boolean); + /** + * If true, overwrites the existing Status header with the response code + * recommended by the Proxy-Status spec. + * This value defaults to ``false``, i.e. the HTTP response code is not + * overwritten. + */ + 'set_recommended_response_code'?: (boolean); + /** + * If ``use_node_id`` is set, Proxy-Status headers will use the Envoy's node + * ID as the name of the proxy. + */ + 'use_node_id'?: (boolean); + /** + * If ``literal_proxy_name`` is set, Proxy-Status headers will use this + * value as the name of the proxy. + */ + 'literal_proxy_name'?: (string); + /** + * The name of the proxy as it appears at the start of the Proxy-Status + * header. + * + * If neither of these values are set, this value defaults to ``server_name``, + * which itself defaults to "envoy". + */ + 'proxy_name'?: "use_node_id"|"literal_proxy_name"; +} + +/** + * Configures the manner in which the Proxy-Status HTTP response header is + * populated. + * + * See the [Proxy-Status + * RFC](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-proxy-status-08). + * [#comment:TODO: Update this with the non-draft URL when finalized.] + * + * The Proxy-Status header is a string of the form: + * + * "; error=; details=
" + * [#next-free-field: 7] + */ +export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ProxyStatusConfig__Output { + /** + * If true, the details field of the Proxy-Status header is not populated with stream_info.response_code_details. + * This value defaults to ``false``, i.e. the ``details`` field is populated by default. + */ + 'remove_details': (boolean); + /** + * If true, the details field of the Proxy-Status header will not contain + * connection termination details. This value defaults to ``false``, i.e. the + * ``details`` field will contain connection termination details by default. + */ + 'remove_connection_termination_details': (boolean); + /** + * If true, the details field of the Proxy-Status header will not contain an + * enumeration of the Envoy ResponseFlags. This value defaults to ``false``, + * i.e. the ``details`` field will contain a list of ResponseFlags by default. + */ + 'remove_response_flags': (boolean); + /** + * If true, overwrites the existing Status header with the response code + * recommended by the Proxy-Status spec. + * This value defaults to ``false``, i.e. the HTTP response code is not + * overwritten. + */ + 'set_recommended_response_code': (boolean); + /** + * If ``use_node_id`` is set, Proxy-Status headers will use the Envoy's node + * ID as the name of the proxy. + */ + 'use_node_id'?: (boolean); + /** + * If ``literal_proxy_name`` is set, Proxy-Status headers will use this + * value as the name of the proxy. + */ + 'literal_proxy_name'?: (string); + /** + * The name of the proxy as it appears at the start of the Proxy-Status + * header. + * + * If neither of these values are set, this value defaults to ``server_name``, + * which itself defaults to "envoy". + */ + 'proxy_name': "use_node_id"|"literal_proxy_name"; +} + // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation { @@ -317,7 +496,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * Target percentage of requests managed by this HTTP connection manager that will be force * traced if the :ref:`x-client-trace-id ` * header is set. This field is a direct analog for the runtime variable - * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * 'tracing.client_enabled' in the :ref:`HTTP Connection Manager * `. * Default: 100% */ @@ -361,7 +540,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * If not specified, no tracing will be performed. * * .. attention:: - * Please be aware that *envoy.tracers.opencensus* provider can only be configured once + * Please be aware that ``envoy.tracers.opencensus`` provider can only be configured once * in Envoy lifetime. * Any attempts to reconfigure it or to use different configurations for different HCM filters * will be rejected. @@ -379,7 +558,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * Target percentage of requests managed by this HTTP connection manager that will be force * traced if the :ref:`x-client-trace-id ` * header is set. This field is a direct analog for the runtime variable - * 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + * 'tracing.client_enabled' in the :ref:`HTTP Connection Manager * `. * Default: 100% */ @@ -423,7 +602,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * If not specified, no tracing will be performed. * * .. attention:: - * Please be aware that *envoy.tracers.opencensus* provider can only be configured once + * Please be aware that ``envoy.tracers.opencensus`` provider can only be configured once * in Envoy lifetime. * Any attempts to reconfigure it or to use different configurations for different HCM filters * will be rejected. @@ -508,7 +687,7 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht } /** - * [#next-free-field: 49] + * [#next-free-field: 57] */ export interface HttpConnectionManager { /** @@ -549,6 +728,10 @@ export interface HttpConnectionManager { 'tracing'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing | null); /** * Additional HTTP/1 settings that are passed to the HTTP/1 codec. + * [#comment:TODO: The following fields are ignored when the + * :ref:`header validation configuration ` + * is present: + * 1. :ref:`allow_chunked_length `] */ 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions | null); /** @@ -557,7 +740,7 @@ export interface HttpConnectionManager { 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); /** * An optional override that the connection manager will write to the server - * header in responses. If not set, the default is *envoy*. + * header in responses. If not set, the default is ``envoy``. */ 'server_name'?: (string); /** @@ -604,8 +787,8 @@ export interface HttpConnectionManager { * ` * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in * the client certificate to be forwarded. Note that in the - * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and - * *By* is always set when the client certificate presents the URI type Subject Alternative Name + * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, ``Hash`` is always set, and + * ``By`` is always set when the client certificate presents the URI type Subject Alternative Name * value. */ 'set_current_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails | null); @@ -629,7 +812,7 @@ export interface HttpConnectionManager { * :ref:`use_remote_address * ` * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is - * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. + * an IPv4 address, the address will be mapped to IPv6 before it is appended to ``x-forwarded-for``. * This is useful for testing compatibility of upstream services that parse the header value. For * example, 50.0.0.1 is represented as ::FFFF:50.0.0.1. See `IPv4-Mapped IPv6 Addresses * `_ for details. This will also affect the @@ -647,7 +830,7 @@ export interface HttpConnectionManager { * has mutated the request headers. While :ref:`use_remote_address * ` * will also suppress XFF addition, it has consequences for logging and other - * Envoy uses of the remote address, so *skip_xff_append* should be used + * Envoy uses of the remote address, so ``skip_xff_append`` should be used * when only an elision of XFF addition is intended. */ 'skip_xff_append'?: (boolean); @@ -754,7 +937,7 @@ export interface HttpConnectionManager { 'max_request_headers_kb'?: (_google_protobuf_UInt32Value | null); /** * Should paths be normalized according to RFC 3986 before any processing of - * requests by HTTP filters or routing? This affects the upstream *:path* header + * requests by HTTP filters or routing? This affects the upstream ``:path`` header * as well. For paths that fail this check, Envoy will respond with 400 to * paths that are malformed. This defaults to false currently but will default * true in the future. When not specified, this value may be overridden by the @@ -764,6 +947,9 @@ export interface HttpConnectionManager { * for details of normalization. * Note that Envoy does not perform * `case normalization `_ + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'normalize_path'?: (_google_protobuf_BoolValue | null); /** @@ -781,10 +967,13 @@ export interface HttpConnectionManager { 'preserve_external_request_id'?: (boolean); /** * Determines if adjacent slashes in the path are merged into one before any processing of - * requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without - * setting this option, incoming requests with path `//dir///file` will not match against route - * with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of + * requests by HTTP filters or routing. This affects the upstream ``:path`` header as well. Without + * setting this option, incoming requests with path ``//dir///file`` will not match against route + * with ``prefix`` match set to ``/dir``. Defaults to ``false``. Note that slash merging is not part of * `HTTP spec `_ and is provided for convenience. + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'merge_slashes'?: (boolean); /** @@ -835,10 +1024,10 @@ export interface HttpConnectionManager { * local port. This affects the upstream host header unless the method is * CONNECT in which case if no filter adds a port the original port will be restored before headers are * sent upstream. - * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * Without setting this option, incoming requests with host ``example:443`` will not match against + * route with :ref:`domains` match set to ``example``. Defaults to ``false``. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. - * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + * Only one of ``strip_matching_host_port`` or ``strip_any_host_port`` can be set. */ 'strip_matching_host_port'?: (boolean); /** @@ -856,7 +1045,7 @@ export interface HttpConnectionManager { * ` or the new HTTP/2 option * :ref:`override_stream_error_on_invalid_http_message * ` - * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging + * ``not`` the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging * ` */ 'stream_error_on_invalid_http_message'?: (_google_protobuf_BoolValue | null); @@ -871,17 +1060,17 @@ export interface HttpConnectionManager { * of request by HTTP filters or routing. * This affects the upstream host header unless the method is CONNECT in * which case if no filter adds a port the original port will be restored before headers are sent upstream. - * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * Without setting this option, incoming requests with host ``example:443`` will not match against + * route with :ref:`domains` match set to ``example``. Defaults to ``false``. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. - * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + * Only one of ``strip_matching_host_port`` or ``strip_any_host_port`` can be set. */ 'strip_any_host_port'?: (boolean); /** * [#not-implemented-hide:] Path normalization configuration. This includes * configurations for transformations (e.g. RFC 3986 normalization or merge * adjacent slashes) and the policy to apply them. The policy determines - * whether transformations affect the forwarded *:path* header. RFC 3986 path + * whether transformations affect the forwarded ``:path`` header. RFC 3986 path * normalization is enabled by default and the default policy is that the * normalized header will be forwarded. See :ref:`PathNormalizationOptions * ` @@ -899,6 +1088,9 @@ export interface HttpConnectionManager { * runtime variable. * The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime * variable can be used to apply the action to a portion of all requests. + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'path_with_escaped_slashes_action'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); /** @@ -925,11 +1117,11 @@ export interface HttpConnectionManager { * Determines if trailing dot of the host should be removed from host/authority header before any * processing of request by HTTP filters or routing. * This affects the upstream host header. - * Without setting this option, incoming requests with host `example.com.` will not match against - * route with :ref:`domains` match set to `example.com`. Defaults to `false`. + * Without setting this option, incoming requests with host ``example.com.`` will not match against + * route with :ref:`domains` match set to ``example.com``. Defaults to ``false``. * When the incoming request contains a host/authority header that includes a port number, * setting this option will strip a trailing dot, if present, from the host section, - * leaving the port as is (e.g. host value `example.com.:443` will be updated to `example.com:443`). + * leaving the port as is (e.g. host value ``example.com.:443`` will be updated to ``example.com:443``). */ 'strip_trailing_host_dot'?: (boolean); /** @@ -938,12 +1130,88 @@ export interface HttpConnectionManager { * handling applies. */ 'scheme_header_transformation'?: (_envoy_config_core_v3_SchemeHeaderTransformation | null); + /** + * Proxy-Status HTTP response header configuration. + * If this config is set, the Proxy-Status HTTP response header field is + * populated. By default, it is not. + */ + 'proxy_status_config'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ProxyStatusConfig | null); + /** + * Configuration options for Header Validation (UHV). + * UHV is an extensible mechanism for checking validity of HTTP requests as well as providing + * normalization for request attributes, such as URI path. + * If the typed_header_validation_config is present it overrides the following options: + * ``normalize_path``, ``merge_slashes``, ``path_with_escaped_slashes_action`` + * ``http_protocol_options.allow_chunked_length``, ``common_http_protocol_options.headers_with_underscores_action``. + * + * The default UHV checks the following: + * + * #. HTTP/1 header map validity according to `RFC 7230 section 3.2`_ + * #. Syntax of HTTP/1 request target URI and response status + * #. HTTP/2 header map validity according to `RFC 7540 section 8.1.2`_ + * #. Syntax of HTTP/3 pseudo headers + * #. Syntax of ``Content-Length`` and ``Transfer-Encoding`` + * #. Validation of HTTP/1 requests with both ``Content-Length`` and ``Transfer-Encoding`` headers + * #. Normalization of the URI path according to `Normalization and Comparison `_ + * without `case normalization `_ + * + * [#not-implemented-hide:] + * [#extension-category: envoy.http.header_validators] + */ + 'typed_header_validation_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); + /** + * Append the `x-forwarded-port` header with the port value client used to connect to Envoy. It + * will be ignored if the `x-forwarded-port` header has been set by any trusted proxy in front of Envoy. + */ + 'append_x_forwarded_port'?: (boolean); + /** + * The configuration for the early header mutation extensions. + * + * When configured the extensions will be called before any routing, tracing, or any filter processing. + * Each extension will be applied in the order they are configured. + * If the same header is mutated by multiple extensions, then the last extension will win. + * + * [#extension-category: envoy.http.early_header_mutation] + */ + 'early_header_mutation_extensions'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; + /** + * Whether the HCM will add ProxyProtocolFilterState to the Connection lifetime filter state. Defaults to `true`. + * This should be set to `false` in cases where Envoy's view of the downstream address may not correspond to the + * actual client address, for example, if there's another proxy in front of the Envoy. + */ + 'add_proxy_protocol_connection_state'?: (_google_protobuf_BoolValue | null); + /** + * .. attention:: + * This field is deprecated in favor of + * :ref:`access_log_flush_interval + * `. + * Note that if both this field and :ref:`access_log_flush_interval + * ` + * are specified, the former (deprecated field) is ignored. + */ + 'access_log_flush_interval'?: (_google_protobuf_Duration | null); + /** + * .. attention:: + * This field is deprecated in favor of + * :ref:`flush_access_log_on_new_request + * `. + * Note that if both this field and :ref:`flush_access_log_on_new_request + * ` + * are specified, the former (deprecated field) is ignored. + */ + 'flush_access_log_on_new_request'?: (boolean); + /** + * Additional access log options for HTTP connection manager. + */ + 'access_log_options'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_HcmAccessLogOptions | null); 'route_specifier'?: "rds"|"route_config"|"scoped_routes"; 'strip_port_mode'?: "strip_any_host_port"; } /** - * [#next-free-field: 49] + * [#next-free-field: 57] */ export interface HttpConnectionManager__Output { /** @@ -984,6 +1252,10 @@ export interface HttpConnectionManager__Output { 'tracing': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing__Output | null); /** * Additional HTTP/1 settings that are passed to the HTTP/1 codec. + * [#comment:TODO: The following fields are ignored when the + * :ref:`header validation configuration ` + * is present: + * 1. :ref:`allow_chunked_length `] */ 'http_protocol_options': (_envoy_config_core_v3_Http1ProtocolOptions__Output | null); /** @@ -992,7 +1264,7 @@ export interface HttpConnectionManager__Output { 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); /** * An optional override that the connection manager will write to the server - * header in responses. If not set, the default is *envoy*. + * header in responses. If not set, the default is ``envoy``. */ 'server_name': (string); /** @@ -1039,8 +1311,8 @@ export interface HttpConnectionManager__Output { * ` * is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in * the client certificate to be forwarded. Note that in the - * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and - * *By* is always set when the client certificate presents the URI type Subject Alternative Name + * :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, ``Hash`` is always set, and + * ``By`` is always set when the client certificate presents the URI type Subject Alternative Name * value. */ 'set_current_client_cert_details': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_SetCurrentClientCertDetails__Output | null); @@ -1064,7 +1336,7 @@ export interface HttpConnectionManager__Output { * :ref:`use_remote_address * ` * is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is - * an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. + * an IPv4 address, the address will be mapped to IPv6 before it is appended to ``x-forwarded-for``. * This is useful for testing compatibility of upstream services that parse the header value. For * example, 50.0.0.1 is represented as ::FFFF:50.0.0.1. See `IPv4-Mapped IPv6 Addresses * `_ for details. This will also affect the @@ -1082,7 +1354,7 @@ export interface HttpConnectionManager__Output { * has mutated the request headers. While :ref:`use_remote_address * ` * will also suppress XFF addition, it has consequences for logging and other - * Envoy uses of the remote address, so *skip_xff_append* should be used + * Envoy uses of the remote address, so ``skip_xff_append`` should be used * when only an elision of XFF addition is intended. */ 'skip_xff_append': (boolean); @@ -1189,7 +1461,7 @@ export interface HttpConnectionManager__Output { 'max_request_headers_kb': (_google_protobuf_UInt32Value__Output | null); /** * Should paths be normalized according to RFC 3986 before any processing of - * requests by HTTP filters or routing? This affects the upstream *:path* header + * requests by HTTP filters or routing? This affects the upstream ``:path`` header * as well. For paths that fail this check, Envoy will respond with 400 to * paths that are malformed. This defaults to false currently but will default * true in the future. When not specified, this value may be overridden by the @@ -1199,6 +1471,9 @@ export interface HttpConnectionManager__Output { * for details of normalization. * Note that Envoy does not perform * `case normalization `_ + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'normalize_path': (_google_protobuf_BoolValue__Output | null); /** @@ -1216,10 +1491,13 @@ export interface HttpConnectionManager__Output { 'preserve_external_request_id': (boolean); /** * Determines if adjacent slashes in the path are merged into one before any processing of - * requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without - * setting this option, incoming requests with path `//dir///file` will not match against route - * with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of + * requests by HTTP filters or routing. This affects the upstream ``:path`` header as well. Without + * setting this option, incoming requests with path ``//dir///file`` will not match against route + * with ``prefix`` match set to ``/dir``. Defaults to ``false``. Note that slash merging is not part of * `HTTP spec `_ and is provided for convenience. + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'merge_slashes': (boolean); /** @@ -1270,10 +1548,10 @@ export interface HttpConnectionManager__Output { * local port. This affects the upstream host header unless the method is * CONNECT in which case if no filter adds a port the original port will be restored before headers are * sent upstream. - * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * Without setting this option, incoming requests with host ``example:443`` will not match against + * route with :ref:`domains` match set to ``example``. Defaults to ``false``. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. - * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + * Only one of ``strip_matching_host_port`` or ``strip_any_host_port`` can be set. */ 'strip_matching_host_port': (boolean); /** @@ -1291,7 +1569,7 @@ export interface HttpConnectionManager__Output { * ` or the new HTTP/2 option * :ref:`override_stream_error_on_invalid_http_message * ` - * *not* the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging + * ``not`` the deprecated but similarly named :ref:`stream_error_on_invalid_http_messaging * ` */ 'stream_error_on_invalid_http_message': (_google_protobuf_BoolValue__Output | null); @@ -1306,17 +1584,17 @@ export interface HttpConnectionManager__Output { * of request by HTTP filters or routing. * This affects the upstream host header unless the method is CONNECT in * which case if no filter adds a port the original port will be restored before headers are sent upstream. - * Without setting this option, incoming requests with host `example:443` will not match against - * route with :ref:`domains` match set to `example`. Defaults to `false`. Note that port removal is not part + * Without setting this option, incoming requests with host ``example:443`` will not match against + * route with :ref:`domains` match set to ``example``. Defaults to ``false``. Note that port removal is not part * of `HTTP spec `_ and is provided for convenience. - * Only one of `strip_matching_host_port` or `strip_any_host_port` can be set. + * Only one of ``strip_matching_host_port`` or ``strip_any_host_port`` can be set. */ 'strip_any_host_port'?: (boolean); /** * [#not-implemented-hide:] Path normalization configuration. This includes * configurations for transformations (e.g. RFC 3986 normalization or merge * adjacent slashes) and the policy to apply them. The policy determines - * whether transformations affect the forwarded *:path* header. RFC 3986 path + * whether transformations affect the forwarded ``:path`` header. RFC 3986 path * normalization is enabled by default and the default policy is that the * normalized header will be forwarded. See :ref:`PathNormalizationOptions * ` @@ -1334,6 +1612,9 @@ export interface HttpConnectionManager__Output { * runtime variable. * The :ref:`http_connection_manager.path_with_escaped_slashes_action_sampling` runtime * variable can be used to apply the action to a portion of all requests. + * [#comment:TODO: This field is ignored when the + * :ref:`header validation configuration ` + * is present.] */ 'path_with_escaped_slashes_action': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); /** @@ -1360,11 +1641,11 @@ export interface HttpConnectionManager__Output { * Determines if trailing dot of the host should be removed from host/authority header before any * processing of request by HTTP filters or routing. * This affects the upstream host header. - * Without setting this option, incoming requests with host `example.com.` will not match against - * route with :ref:`domains` match set to `example.com`. Defaults to `false`. + * Without setting this option, incoming requests with host ``example.com.`` will not match against + * route with :ref:`domains` match set to ``example.com``. Defaults to ``false``. * When the incoming request contains a host/authority header that includes a port number, * setting this option will strip a trailing dot, if present, from the host section, - * leaving the port as is (e.g. host value `example.com.:443` will be updated to `example.com:443`). + * leaving the port as is (e.g. host value ``example.com.:443`` will be updated to ``example.com:443``). */ 'strip_trailing_host_dot': (boolean); /** @@ -1373,6 +1654,82 @@ export interface HttpConnectionManager__Output { * handling applies. */ 'scheme_header_transformation': (_envoy_config_core_v3_SchemeHeaderTransformation__Output | null); + /** + * Proxy-Status HTTP response header configuration. + * If this config is set, the Proxy-Status HTTP response header field is + * populated. By default, it is not. + */ + 'proxy_status_config': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ProxyStatusConfig__Output | null); + /** + * Configuration options for Header Validation (UHV). + * UHV is an extensible mechanism for checking validity of HTTP requests as well as providing + * normalization for request attributes, such as URI path. + * If the typed_header_validation_config is present it overrides the following options: + * ``normalize_path``, ``merge_slashes``, ``path_with_escaped_slashes_action`` + * ``http_protocol_options.allow_chunked_length``, ``common_http_protocol_options.headers_with_underscores_action``. + * + * The default UHV checks the following: + * + * #. HTTP/1 header map validity according to `RFC 7230 section 3.2`_ + * #. Syntax of HTTP/1 request target URI and response status + * #. HTTP/2 header map validity according to `RFC 7540 section 8.1.2`_ + * #. Syntax of HTTP/3 pseudo headers + * #. Syntax of ``Content-Length`` and ``Transfer-Encoding`` + * #. Validation of HTTP/1 requests with both ``Content-Length`` and ``Transfer-Encoding`` headers + * #. Normalization of the URI path according to `Normalization and Comparison `_ + * without `case normalization `_ + * + * [#not-implemented-hide:] + * [#extension-category: envoy.http.header_validators] + */ + 'typed_header_validation_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); + /** + * Append the `x-forwarded-port` header with the port value client used to connect to Envoy. It + * will be ignored if the `x-forwarded-port` header has been set by any trusted proxy in front of Envoy. + */ + 'append_x_forwarded_port': (boolean); + /** + * The configuration for the early header mutation extensions. + * + * When configured the extensions will be called before any routing, tracing, or any filter processing. + * Each extension will be applied in the order they are configured. + * If the same header is mutated by multiple extensions, then the last extension will win. + * + * [#extension-category: envoy.http.early_header_mutation] + */ + 'early_header_mutation_extensions': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; + /** + * Whether the HCM will add ProxyProtocolFilterState to the Connection lifetime filter state. Defaults to `true`. + * This should be set to `false` in cases where Envoy's view of the downstream address may not correspond to the + * actual client address, for example, if there's another proxy in front of the Envoy. + */ + 'add_proxy_protocol_connection_state': (_google_protobuf_BoolValue__Output | null); + /** + * .. attention:: + * This field is deprecated in favor of + * :ref:`access_log_flush_interval + * `. + * Note that if both this field and :ref:`access_log_flush_interval + * ` + * are specified, the former (deprecated field) is ignored. + */ + 'access_log_flush_interval': (_google_protobuf_Duration__Output | null); + /** + * .. attention:: + * This field is deprecated in favor of + * :ref:`flush_access_log_on_new_request + * `. + * Note that if both this field and :ref:`flush_access_log_on_new_request + * ` + * are specified, the former (deprecated field) is ignored. + */ + 'flush_access_log_on_new_request': (boolean); + /** + * Additional access log options for HTTP connection manager. + */ + 'access_log_options': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_HcmAccessLogOptions__Output | null); 'route_specifier': "rds"|"route_config"|"scoped_routes"; 'strip_port_mode': "strip_any_host_port"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts index d1c1cbc06..1550ca237 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter.ts @@ -8,9 +8,7 @@ import type { ExtensionConfigSource as _envoy_config_core_v3_ExtensionConfigSour */ export interface HttpFilter { /** - * The name of the filter configuration. The name is used as a fallback to - * select an extension if the type of the configuration proto is not - * sufficient. It also serves as a resource name in ExtensionConfigDS. + * The name of the filter configuration. It also serves as a resource name in ExtensionConfigDS. */ 'name'?: (string); /** @@ -38,7 +36,6 @@ export interface HttpFilter { * If true, clients that do not support this filter may ignore the * filter but otherwise accept the config. * Otherwise, clients that do not support this filter must reject the config. - * This is also same with typed per filter config. */ 'is_optional'?: (boolean); 'config_type'?: "typed_config"|"config_discovery"; @@ -49,9 +46,7 @@ export interface HttpFilter { */ export interface HttpFilter__Output { /** - * The name of the filter configuration. The name is used as a fallback to - * select an extension if the type of the configuration proto is not - * sufficient. It also serves as a resource name in ExtensionConfigDS. + * The name of the filter configuration. It also serves as a resource name in ExtensionConfigDS. */ 'name': (string); /** @@ -79,7 +74,6 @@ export interface HttpFilter__Output { * If true, clients that do not support this filter may ignore the * filter but otherwise accept the config. * Otherwise, clients that do not support this filter must reject the config. - * This is also same with typed per filter config. */ 'is_optional': (boolean); 'config_type': "typed_config"|"config_discovery"; diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts index 26d14a07d..66533b411 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/ResponseMapper.ts @@ -20,12 +20,12 @@ export interface ResponseMapper { */ 'status_code'?: (_google_protobuf_UInt32Value | null); /** - * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` - * command operator in the `body_format`. + * The new local reply body text if specified. It will be used in the ``%LOCAL_REPLY_BODY%`` + * command operator in the ``body_format``. */ 'body'?: (_envoy_config_core_v3_DataSource | null); /** - * A per mapper `body_format` to override the :ref:`body_format `. + * A per mapper ``body_format`` to override the :ref:`body_format `. * It will be used when this mapper is matched. */ 'body_format_override'?: (_envoy_config_core_v3_SubstitutionFormatString | null); @@ -50,12 +50,12 @@ export interface ResponseMapper__Output { */ 'status_code': (_google_protobuf_UInt32Value__Output | null); /** - * The new local reply body text if specified. It will be used in the `%LOCAL_REPLY_BODY%` - * command operator in the `body_format`. + * The new local reply body text if specified. It will be used in the ``%LOCAL_REPLY_BODY%`` + * command operator in the ``body_format``. */ 'body': (_envoy_config_core_v3_DataSource__Output | null); /** - * A per mapper `body_format` to override the :ref:`body_format `. + * A per mapper ``body_format`` to override the :ref:`body_format `. * It will be used when this mapper is matched. */ 'body_format_override': (_envoy_config_core_v3_SubstitutionFormatString__Output | null); diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality.ts new file mode 100644 index 000000000..d35fb06e5 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality.ts @@ -0,0 +1,25 @@ +// Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto + +import type { LoadBalancingPolicy as _envoy_config_cluster_v3_LoadBalancingPolicy, LoadBalancingPolicy__Output as _envoy_config_cluster_v3_LoadBalancingPolicy__Output } from '../../../../../envoy/config/cluster/v3/LoadBalancingPolicy'; + +/** + * Configuration for the wrr_locality LB policy. See the :ref:`load balancing architecture overview + * ` for more information. + */ +export interface WrrLocality { + /** + * The child LB policy to create for endpoint-picking within the chosen locality. + */ + 'endpoint_picking_policy'?: (_envoy_config_cluster_v3_LoadBalancingPolicy | null); +} + +/** + * Configuration for the wrr_locality LB policy. See the :ref:`load balancing architecture overview + * ` for more information. + */ +export interface WrrLocality__Output { + /** + * The child LB policy to create for endpoint-picking within the chosen locality. + */ + 'endpoint_picking_policy': (_envoy_config_cluster_v3_LoadBalancingPolicy__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts index e6498177b..e8d7df1f2 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/AggregatedDiscoveryService.ts @@ -8,7 +8,7 @@ import type { DiscoveryRequest as _envoy_service_discovery_v3_DiscoveryRequest, import type { DiscoveryResponse as _envoy_service_discovery_v3_DiscoveryResponse, DiscoveryResponse__Output as _envoy_service_discovery_v3_DiscoveryResponse__Output } from '../../../../envoy/service/discovery/v3/DiscoveryResponse'; /** - * See https://github.com/lyft/envoy-api#apis for a description of the role of + * See https://github.com/envoyproxy/envoy-api#apis for a description of the role of * ADS and how it is intended to be used by a management server. ADS requests * have the same structure as their singleton xDS counterparts, but can * multiplex many resource types on a single stream. The type_url in the @@ -35,7 +35,7 @@ export interface AggregatedDiscoveryServiceClient extends grpc.Client { } /** - * See https://github.com/lyft/envoy-api#apis for a description of the role of + * See https://github.com/envoyproxy/envoy-api#apis for a description of the role of * ADS and how it is intended to be used by a management server. ADS requests * have the same structure as their singleton xDS counterparts, but can * multiplex many resource types on a single stream. The type_url in the diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts index 6e900970a..6c1a3cd95 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryRequest.ts @@ -2,6 +2,7 @@ import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../../google/rpc/Status'; +import type { ResourceLocator as _envoy_service_discovery_v3_ResourceLocator, ResourceLocator__Output as _envoy_service_discovery_v3_ResourceLocator__Output } from '../../../../envoy/service/discovery/v3/ResourceLocator'; /** * DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC @@ -36,7 +37,7 @@ import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status * In particular, initial_resource_versions being sent at the "start" of every * gRPC stream actually entails a message for each type_url, each with its own * initial_resource_versions. - * [#next-free-field: 8] + * [#next-free-field: 10] */ export interface DeltaDiscoveryRequest { /** @@ -45,9 +46,9 @@ export interface DeltaDiscoveryRequest { 'node'?: (_envoy_config_core_v3_Node | null); /** * Type of the resource that is being requested, e.g. - * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if - * resources are only referenced via *xds_resource_subscribe* and - * *xds_resources_unsubscribe*. + * ``type.googleapis.com/envoy.api.v2.ClusterLoadAssignment``. This does not need to be set if + * resources are only referenced via ``xds_resource_subscribe`` and + * ``xds_resources_unsubscribe``. */ 'type_url'?: (string); /** @@ -98,10 +99,26 @@ export interface DeltaDiscoveryRequest { 'response_nonce'?: (string); /** * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* + * failed to update configuration. The ``message`` field in ``error_details`` * provides the Envoy internal exception related to the failure. */ 'error_detail'?: (_google_rpc_Status | null); + /** + * [#not-implemented-hide:] + * Alternative to ``resource_names_subscribe`` field that allows specifying dynamic parameters + * along with each resource name. + * Note that it is legal for a request to have some resources listed + * in ``resource_names_subscribe`` and others in ``resource_locators_subscribe``. + */ + 'resource_locators_subscribe'?: (_envoy_service_discovery_v3_ResourceLocator)[]; + /** + * [#not-implemented-hide:] + * Alternative to ``resource_names_unsubscribe`` field that allows specifying dynamic parameters + * along with each resource name. + * Note that it is legal for a request to have some resources listed + * in ``resource_names_unsubscribe`` and others in ``resource_locators_unsubscribe``. + */ + 'resource_locators_unsubscribe'?: (_envoy_service_discovery_v3_ResourceLocator)[]; } /** @@ -137,7 +154,7 @@ export interface DeltaDiscoveryRequest { * In particular, initial_resource_versions being sent at the "start" of every * gRPC stream actually entails a message for each type_url, each with its own * initial_resource_versions. - * [#next-free-field: 8] + * [#next-free-field: 10] */ export interface DeltaDiscoveryRequest__Output { /** @@ -146,9 +163,9 @@ export interface DeltaDiscoveryRequest__Output { 'node': (_envoy_config_core_v3_Node__Output | null); /** * Type of the resource that is being requested, e.g. - * "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment". This does not need to be set if - * resources are only referenced via *xds_resource_subscribe* and - * *xds_resources_unsubscribe*. + * ``type.googleapis.com/envoy.api.v2.ClusterLoadAssignment``. This does not need to be set if + * resources are only referenced via ``xds_resource_subscribe`` and + * ``xds_resources_unsubscribe``. */ 'type_url': (string); /** @@ -199,8 +216,24 @@ export interface DeltaDiscoveryRequest__Output { 'response_nonce': (string); /** * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* + * failed to update configuration. The ``message`` field in ``error_details`` * provides the Envoy internal exception related to the failure. */ 'error_detail': (_google_rpc_Status__Output | null); + /** + * [#not-implemented-hide:] + * Alternative to ``resource_names_subscribe`` field that allows specifying dynamic parameters + * along with each resource name. + * Note that it is legal for a request to have some resources listed + * in ``resource_names_subscribe`` and others in ``resource_locators_subscribe``. + */ + 'resource_locators_subscribe': (_envoy_service_discovery_v3_ResourceLocator__Output)[]; + /** + * [#not-implemented-hide:] + * Alternative to ``resource_names_unsubscribe`` field that allows specifying dynamic parameters + * along with each resource name. + * Note that it is legal for a request to have some resources listed + * in ``resource_names_unsubscribe`` and others in ``resource_locators_unsubscribe``. + */ + 'resource_locators_unsubscribe': (_envoy_service_discovery_v3_ResourceLocator__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts index efbbed85c..0728140ee 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DeltaDiscoveryResponse.ts @@ -2,9 +2,10 @@ import type { Resource as _envoy_service_discovery_v3_Resource, Resource__Output as _envoy_service_discovery_v3_Resource__Output } from '../../../../envoy/service/discovery/v3/Resource'; import type { ControlPlane as _envoy_config_core_v3_ControlPlane, ControlPlane__Output as _envoy_config_core_v3_ControlPlane__Output } from '../../../../envoy/config/core/v3/ControlPlane'; +import type { ResourceName as _envoy_service_discovery_v3_ResourceName, ResourceName__Output as _envoy_service_discovery_v3_ResourceName__Output } from '../../../../envoy/service/discovery/v3/ResourceName'; /** - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface DeltaDiscoveryResponse { /** @@ -36,10 +37,16 @@ export interface DeltaDiscoveryResponse { * The control plane instance that sent the response. */ 'control_plane'?: (_envoy_config_core_v3_ControlPlane | null); + /** + * Alternative to removed_resources that allows specifying which variant of + * a resource is being removed. This variant must be used for any resource + * for which dynamic parameter constraints were sent to the client. + */ + 'removed_resource_names'?: (_envoy_service_discovery_v3_ResourceName)[]; } /** - * [#next-free-field: 8] + * [#next-free-field: 9] */ export interface DeltaDiscoveryResponse__Output { /** @@ -71,4 +78,10 @@ export interface DeltaDiscoveryResponse__Output { * The control plane instance that sent the response. */ 'control_plane': (_envoy_config_core_v3_ControlPlane__Output | null); + /** + * Alternative to removed_resources that allows specifying which variant of + * a resource is being removed. This variant must be used for any resource + * for which dynamic parameter constraints were sent to the client. + */ + 'removed_resource_names': (_envoy_service_discovery_v3_ResourceName__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts index f392ab8ae..95f1299cb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DiscoveryRequest.ts @@ -2,11 +2,12 @@ import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; import type { Status as _google_rpc_Status, Status__Output as _google_rpc_Status__Output } from '../../../../google/rpc/Status'; +import type { ResourceLocator as _envoy_service_discovery_v3_ResourceLocator, ResourceLocator__Output as _envoy_service_discovery_v3_ResourceLocator__Output } from '../../../../envoy/service/discovery/v3/ResourceLocator'; /** * A DiscoveryRequest requests a set of versioned resources of the same type for * a given Envoy node on some API. - * [#next-free-field: 7] + * [#next-free-field: 8] */ export interface DiscoveryRequest { /** @@ -49,17 +50,27 @@ export interface DiscoveryRequest { 'response_nonce'?: (string); /** * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* provides the Envoy + * failed to update configuration. The ``message`` field in ``error_details`` provides the Envoy * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. */ 'error_detail'?: (_google_rpc_Status | null); + /** + * [#not-implemented-hide:] + * Alternative to ``resource_names`` field that allows specifying dynamic + * parameters along with each resource name. Clients that populate this + * field must be able to handle responses from the server where resources + * are wrapped in a Resource message. + * Note that it is legal for a request to have some resources listed + * in ``resource_names`` and others in ``resource_locators``. + */ + 'resource_locators'?: (_envoy_service_discovery_v3_ResourceLocator)[]; } /** * A DiscoveryRequest requests a set of versioned resources of the same type for * a given Envoy node on some API. - * [#next-free-field: 7] + * [#next-free-field: 8] */ export interface DiscoveryRequest__Output { /** @@ -102,9 +113,19 @@ export interface DiscoveryRequest__Output { 'response_nonce': (string); /** * This is populated when the previous :ref:`DiscoveryResponse ` - * failed to update configuration. The *message* field in *error_details* provides the Envoy + * failed to update configuration. The ``message`` field in ``error_details`` provides the Envoy * internal exception related to the failure. It is only intended for consumption during manual * debugging, the string provided is not guaranteed to be stable across Envoy versions. */ 'error_detail': (_google_rpc_Status__Output | null); + /** + * [#not-implemented-hide:] + * Alternative to ``resource_names`` field that allows specifying dynamic + * parameters along with each resource name. Clients that populate this + * field must be able to handle responses from the server where resources + * are wrapped in a Resource message. + * Note that it is legal for a request to have some resources listed + * in ``resource_names`` and others in ``resource_locators``. + */ + 'resource_locators': (_envoy_service_discovery_v3_ResourceLocator__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DynamicParameterConstraints.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DynamicParameterConstraints.ts new file mode 100644 index 000000000..5bba10719 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/DynamicParameterConstraints.ts @@ -0,0 +1,119 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { DynamicParameterConstraints as _envoy_service_discovery_v3_DynamicParameterConstraints, DynamicParameterConstraints__Output as _envoy_service_discovery_v3_DynamicParameterConstraints__Output } from '../../../../envoy/service/discovery/v3/DynamicParameterConstraints'; + +export interface _envoy_service_discovery_v3_DynamicParameterConstraints_ConstraintList { + 'constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints)[]; +} + +export interface _envoy_service_discovery_v3_DynamicParameterConstraints_ConstraintList__Output { + 'constraints': (_envoy_service_discovery_v3_DynamicParameterConstraints__Output)[]; +} + +export interface _envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint_Exists { +} + +export interface _envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint_Exists__Output { +} + +/** + * A single constraint for a given key. + */ +export interface _envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint { + /** + * The key to match against. + */ + 'key'?: (string); + /** + * Matches this exact value. + */ + 'value'?: (string); + /** + * Key is present (matches any value except for the key being absent). + * This allows setting a default constraint for clients that do + * not send a key at all, while there may be other clients that need + * special configuration based on that key. + */ + 'exists'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint_Exists | null); + 'constraint_type'?: "value"|"exists"; +} + +/** + * A single constraint for a given key. + */ +export interface _envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint__Output { + /** + * The key to match against. + */ + 'key': (string); + /** + * Matches this exact value. + */ + 'value'?: (string); + /** + * Key is present (matches any value except for the key being absent). + * This allows setting a default constraint for clients that do + * not send a key at all, while there may be other clients that need + * special configuration based on that key. + */ + 'exists'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint_Exists__Output | null); + 'constraint_type': "value"|"exists"; +} + +/** + * A set of dynamic parameter constraints associated with a variant of an individual xDS resource. + * These constraints determine whether the resource matches a subscription based on the set of + * dynamic parameters in the subscription, as specified in the + * :ref:`ResourceLocator.dynamic_parameters` + * field. This allows xDS implementations (clients, servers, and caching proxies) to determine + * which variant of a resource is appropriate for a given client. + */ +export interface DynamicParameterConstraints { + /** + * A single constraint to evaluate. + */ + 'constraint'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint | null); + /** + * A list of constraints that match if any one constraint in the list + * matches. + */ + 'or_constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_ConstraintList | null); + /** + * A list of constraints that must all match. + */ + 'and_constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_ConstraintList | null); + /** + * The inverse (NOT) of a set of constraints. + */ + 'not_constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints | null); + 'type'?: "constraint"|"or_constraints"|"and_constraints"|"not_constraints"; +} + +/** + * A set of dynamic parameter constraints associated with a variant of an individual xDS resource. + * These constraints determine whether the resource matches a subscription based on the set of + * dynamic parameters in the subscription, as specified in the + * :ref:`ResourceLocator.dynamic_parameters` + * field. This allows xDS implementations (clients, servers, and caching proxies) to determine + * which variant of a resource is appropriate for a given client. + */ +export interface DynamicParameterConstraints__Output { + /** + * A single constraint to evaluate. + */ + 'constraint'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_SingleConstraint__Output | null); + /** + * A list of constraints that match if any one constraint in the list + * matches. + */ + 'or_constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_ConstraintList__Output | null); + /** + * A list of constraints that must all match. + */ + 'and_constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints_ConstraintList__Output | null); + /** + * The inverse (NOT) of a set of constraints. + */ + 'not_constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints__Output | null); + 'type': "constraint"|"or_constraints"|"and_constraints"|"not_constraints"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts index 0e5897ab4..6bef71ff8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/Resource.ts @@ -2,6 +2,8 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; +import type { ResourceName as _envoy_service_discovery_v3_ResourceName, ResourceName__Output as _envoy_service_discovery_v3_ResourceName__Output } from '../../../../envoy/service/discovery/v3/ResourceName'; +import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; /** * Cache control properties for the resource. @@ -30,7 +32,7 @@ export interface _envoy_service_discovery_v3_Resource_CacheControl__Output { } /** - * [#next-free-field: 8] + * [#next-free-field: 10] */ export interface Resource { /** @@ -44,6 +46,7 @@ export interface Resource { 'resource'?: (_google_protobuf_Any | null); /** * The resource's name, to distinguish it from others of the same type of resource. + * Only one of ``name`` or ``resource_name`` may be set. */ 'name'?: (string); /** @@ -71,10 +74,22 @@ export interface Resource { * [#not-implemented-hide:] */ 'cache_control'?: (_envoy_service_discovery_v3_Resource_CacheControl | null); + /** + * Alternative to the ``name`` field, to be used when the server supports + * multiple variants of the named resource that are differentiated by + * dynamic parameter constraints. + * Only one of ``name`` or ``resource_name`` may be set. + */ + 'resource_name'?: (_envoy_service_discovery_v3_ResourceName | null); + /** + * The Metadata field can be used to provide additional information for the resource. + * E.g. the trace data for debugging. + */ + 'metadata'?: (_envoy_config_core_v3_Metadata | null); } /** - * [#next-free-field: 8] + * [#next-free-field: 10] */ export interface Resource__Output { /** @@ -88,6 +103,7 @@ export interface Resource__Output { 'resource': (_google_protobuf_Any__Output | null); /** * The resource's name, to distinguish it from others of the same type of resource. + * Only one of ``name`` or ``resource_name`` may be set. */ 'name': (string); /** @@ -115,4 +131,16 @@ export interface Resource__Output { * [#not-implemented-hide:] */ 'cache_control': (_envoy_service_discovery_v3_Resource_CacheControl__Output | null); + /** + * Alternative to the ``name`` field, to be used when the server supports + * multiple variants of the named resource that are differentiated by + * dynamic parameter constraints. + * Only one of ``name`` or ``resource_name`` may be set. + */ + 'resource_name': (_envoy_service_discovery_v3_ResourceName__Output | null); + /** + * The Metadata field can be used to provide additional information for the resource. + * E.g. the trace data for debugging. + */ + 'metadata': (_envoy_config_core_v3_Metadata__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceLocator.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceLocator.ts new file mode 100644 index 000000000..c37dc68d3 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceLocator.ts @@ -0,0 +1,34 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + + +/** + * Specifies a resource to be subscribed to. + */ +export interface ResourceLocator { + /** + * The resource name to subscribe to. + */ + 'name'?: (string); + /** + * A set of dynamic parameters used to match against the dynamic parameter + * constraints on the resource. This allows clients to select between + * multiple variants of the same resource. + */ + 'dynamic_parameters'?: ({[key: string]: string}); +} + +/** + * Specifies a resource to be subscribed to. + */ +export interface ResourceLocator__Output { + /** + * The resource name to subscribe to. + */ + 'name': (string); + /** + * A set of dynamic parameters used to match against the dynamic parameter + * constraints on the resource. This allows clients to select between + * multiple variants of the same resource. + */ + 'dynamic_parameters': ({[key: string]: string}); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceName.ts b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceName.ts new file mode 100644 index 000000000..da7f4fd8a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/service/discovery/v3/ResourceName.ts @@ -0,0 +1,33 @@ +// Original file: deps/envoy-api/envoy/service/discovery/v3/discovery.proto + +import type { DynamicParameterConstraints as _envoy_service_discovery_v3_DynamicParameterConstraints, DynamicParameterConstraints__Output as _envoy_service_discovery_v3_DynamicParameterConstraints__Output } from '../../../../envoy/service/discovery/v3/DynamicParameterConstraints'; + +/** + * Specifies a concrete resource name. + */ +export interface ResourceName { + /** + * The name of the resource. + */ + 'name'?: (string); + /** + * Dynamic parameter constraints associated with this resource. To be used by client-side caches + * (including xDS proxies) when matching subscribed resource locators. + */ + 'dynamic_parameter_constraints'?: (_envoy_service_discovery_v3_DynamicParameterConstraints | null); +} + +/** + * Specifies a concrete resource name. + */ +export interface ResourceName__Output { + /** + * The name of the resource. + */ + 'name': (string); + /** + * Dynamic parameter constraints associated with this resource. To be used by client-side caches + * (including xDS proxies) when matching subscribed resource locators. + */ + 'dynamic_parameter_constraints': (_envoy_service_discovery_v3_DynamicParameterConstraints__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts index 40f561870..6429d18c8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/load_stats/v3/LoadStatsResponse.ts @@ -9,22 +9,22 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google export interface LoadStatsResponse { /** * Clusters to report stats for. - * Not populated if *send_all_clusters* is true. + * Not populated if ``send_all_clusters`` is true. */ 'clusters'?: (string)[]; /** * The minimum interval of time to collect stats over. This is only a minimum for two reasons: * * 1. There may be some delay from when the timer fires until stats sampling occurs. - * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic - * that is observed in between the corresponding previous *LoadStatsRequest* and this - * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + * 2. For clusters that were already feature in the previous ``LoadStatsResponse``, any traffic + * that is observed in between the corresponding previous ``LoadStatsRequest`` and this + * ``LoadStatsResponse`` will also be accumulated and billed to the cluster. This avoids a period * of inobservability that might otherwise exists between the messages. New clusters are not * subject to this consideration. */ 'load_reporting_interval'?: (_google_protobuf_Duration | null); /** - * Set to *true* if the management server supports endpoint granularity + * Set to ``true`` if the management server supports endpoint granularity * report. */ 'report_endpoint_granularity'?: (boolean); @@ -43,22 +43,22 @@ export interface LoadStatsResponse { export interface LoadStatsResponse__Output { /** * Clusters to report stats for. - * Not populated if *send_all_clusters* is true. + * Not populated if ``send_all_clusters`` is true. */ 'clusters': (string)[]; /** * The minimum interval of time to collect stats over. This is only a minimum for two reasons: * * 1. There may be some delay from when the timer fires until stats sampling occurs. - * 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic - * that is observed in between the corresponding previous *LoadStatsRequest* and this - * *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + * 2. For clusters that were already feature in the previous ``LoadStatsResponse``, any traffic + * that is observed in between the corresponding previous ``LoadStatsRequest`` and this + * ``LoadStatsResponse`` will also be accumulated and billed to the cluster. This avoids a period * of inobservability that might otherwise exists between the messages. New clusters are not * subject to this consideration. */ 'load_reporting_interval': (_google_protobuf_Duration__Output | null); /** - * Set to *true* if the management server supports endpoint granularity + * Set to ``true`` if the management server supports endpoint granularity * report. */ 'report_endpoint_granularity': (boolean); diff --git a/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts b/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts index 4dc10efcb..c8aca7e3f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/http/v3/PathTransformation.ts @@ -3,10 +3,10 @@ /** * Determines if adjacent slashes are merged into one. A common use case is for a request path - * header. Using this option in `:ref: PathNormalizationOptions - * ` - * will allow incoming requests with path `//dir///file` to match against route with `prefix` - * match set to `/dir`. When using for header transformations, note that slash merging is not + * header. Using this option in ``:ref: PathNormalizationOptions + * `` + * will allow incoming requests with path ``//dir///file`` to match against route with ``prefix`` + * match set to ``/dir``. When using for header transformations, note that slash merging is not * part of `HTTP spec `_ and is provided for convenience. */ export interface _envoy_type_http_v3_PathTransformation_Operation_MergeSlashes { @@ -14,10 +14,10 @@ export interface _envoy_type_http_v3_PathTransformation_Operation_MergeSlashes { /** * Determines if adjacent slashes are merged into one. A common use case is for a request path - * header. Using this option in `:ref: PathNormalizationOptions - * ` - * will allow incoming requests with path `//dir///file` to match against route with `prefix` - * match set to `/dir`. When using for header transformations, note that slash merging is not + * header. Using this option in ``:ref: PathNormalizationOptions + * `` + * will allow incoming requests with path ``//dir///file`` to match against route with ``prefix`` + * match set to ``/dir``. When using for header transformations, note that slash merging is not * part of `HTTP spec `_ and is provided for convenience. */ export interface _envoy_type_http_v3_PathTransformation_Operation_MergeSlashes__Output { diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts index 1addb1730..c83f8b473 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts @@ -7,14 +7,14 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a * the documented `syntax `_. The engine is designed * to complete execution in linear time as well as limit the amount of memory used. * - * Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` - * and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + * Envoy supports program size checking via runtime. The runtime keys ``re2.max_program_size.error_level`` + * and ``re2.max_program_size.warn_level`` can be set to integers as the maximum program size or * complexity that a compiled regex can have before an exception is thrown or a warning is - * logged, respectively. `re2.max_program_size.error_level` defaults to 100, and - * `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + * logged, respectively. ``re2.max_program_size.error_level`` defaults to 100, and + * ``re2.max_program_size.warn_level`` has no default if unset (will not check/log a warning). * - * Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, - * which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + * Envoy emits two stats for tracking the program size of regexes: the histogram ``re2.program_size``, + * which records the program size, and the counter ``re2.exceeded_warn_level``, which is incremented * each time the program size exceeds the warn level threshold. */ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2 { @@ -26,6 +26,11 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2 { * * This field is deprecated; regexp validation should be performed on the management server * instead of being done by each individual client. + * + * .. note:: + * + * Although this field is deprecated, the program size will still be checked against the + * global ``re2.max_program_size.error_level`` runtime value. */ 'max_program_size'?: (_google_protobuf_UInt32Value | null); } @@ -35,14 +40,14 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2 { * the documented `syntax `_. The engine is designed * to complete execution in linear time as well as limit the amount of memory used. * - * Envoy supports program size checking via runtime. The runtime keys `re2.max_program_size.error_level` - * and `re2.max_program_size.warn_level` can be set to integers as the maximum program size or + * Envoy supports program size checking via runtime. The runtime keys ``re2.max_program_size.error_level`` + * and ``re2.max_program_size.warn_level`` can be set to integers as the maximum program size or * complexity that a compiled regex can have before an exception is thrown or a warning is - * logged, respectively. `re2.max_program_size.error_level` defaults to 100, and - * `re2.max_program_size.warn_level` has no default if unset (will not check/log a warning). + * logged, respectively. ``re2.max_program_size.error_level`` defaults to 100, and + * ``re2.max_program_size.warn_level`` has no default if unset (will not check/log a warning). * - * Envoy emits two stats for tracking the program size of regexes: the histogram `re2.program_size`, - * which records the program size, and the counter `re2.exceeded_warn_level`, which is incremented + * Envoy emits two stats for tracking the program size of regexes: the histogram ``re2.program_size``, + * which records the program size, and the counter ``re2.exceeded_warn_level``, which is incremented * each time the program size exceeds the warn level threshold. */ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output { @@ -54,6 +59,11 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output { * * This field is deprecated; regexp validation should be performed on the management server * instead of being done by each individual client. + * + * .. note:: + * + * Although this field is deprecated, the program size will still be checked against the + * global ``re2.max_program_size.error_level`` runtime value. */ 'max_program_size': (_google_protobuf_UInt32Value__Output | null); } @@ -67,7 +77,8 @@ export interface RegexMatcher { */ 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2 | null); /** - * The regex match string. The string must be supported by the configured engine. + * The regex match string. The string must be supported by the configured engine. The regex is matched + * against the full string, not as a partial match. */ 'regex'?: (string); 'engine_type'?: "google_re2"; @@ -82,7 +93,8 @@ export interface RegexMatcher__Output { */ 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output | null); /** - * The regex match string. The string must be supported by the configured engine. + * The regex match string. The string must be supported by the configured engine. The regex is matched + * against the full string, not as a partial match. */ 'regex': (string); 'engine_type': "google_re2"; diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts index 7440746f1..181d59d54 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StringMatcher.ts @@ -12,7 +12,7 @@ export interface StringMatcher { * * Examples: * - * * *abc* only matches the value *abc*. + * * ``abc`` only matches the value ``abc``. */ 'exact'?: (string); /** @@ -21,7 +21,7 @@ export interface StringMatcher { * * Examples: * - * * *abc* matches the value *abc.xyz* + * * ``abc`` matches the value ``abc.xyz`` */ 'prefix'?: (string); /** @@ -30,7 +30,7 @@ export interface StringMatcher { * * Examples: * - * * *abc* matches the value *xyz.abc* + * * ``abc`` matches the value ``xyz.abc`` */ 'suffix'?: (string); /** @@ -40,7 +40,7 @@ export interface StringMatcher { /** * If true, indicates the exact/prefix/suffix/contains matching should be case insensitive. This * has no effect for the safe_regex match. - * For example, the matcher *data* will match both input string *Data* and *data* if set to true. + * For example, the matcher ``data`` will match both input string ``Data`` and ``data`` if set to true. */ 'ignore_case'?: (boolean); /** @@ -49,7 +49,7 @@ export interface StringMatcher { * * Examples: * - * * *abc* matches the value *xyz.abc.def* + * * ``abc`` matches the value ``xyz.abc.def`` */ 'contains'?: (string); 'match_pattern'?: "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; @@ -65,7 +65,7 @@ export interface StringMatcher__Output { * * Examples: * - * * *abc* only matches the value *abc*. + * * ``abc`` only matches the value ``abc``. */ 'exact'?: (string); /** @@ -74,7 +74,7 @@ export interface StringMatcher__Output { * * Examples: * - * * *abc* matches the value *abc.xyz* + * * ``abc`` matches the value ``abc.xyz`` */ 'prefix'?: (string); /** @@ -83,7 +83,7 @@ export interface StringMatcher__Output { * * Examples: * - * * *abc* matches the value *xyz.abc* + * * ``abc`` matches the value ``xyz.abc`` */ 'suffix'?: (string); /** @@ -93,7 +93,7 @@ export interface StringMatcher__Output { /** * If true, indicates the exact/prefix/suffix/contains matching should be case insensitive. This * has no effect for the safe_regex match. - * For example, the matcher *data* will match both input string *Data* and *data* if set to true. + * For example, the matcher ``data`` will match both input string ``Data`` and ``data`` if set to true. */ 'ignore_case': (boolean); /** @@ -102,7 +102,7 @@ export interface StringMatcher__Output { * * Examples: * - * * *abc* matches the value *xyz.abc.def* + * * ``abc`` matches the value ``xyz.abc.def`` */ 'contains'?: (string); 'match_pattern': "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts index 141806489..22afe24a9 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/StructMatcher.ts @@ -26,7 +26,7 @@ export interface _envoy_type_matcher_v3_StructMatcher_PathSegment__Output { /** * StructMatcher provides a general interface to check if a given value is matched in - * google.protobuf.Struct. It uses `path` to retrieve the value + * google.protobuf.Struct. It uses ``path`` to retrieve the value * from the struct and then check if it's matched to the specified value. * * For example, for the following Struct: @@ -90,7 +90,7 @@ export interface StructMatcher { /** * StructMatcher provides a general interface to check if a given value is matched in - * google.protobuf.Struct. It uses `path` to retrieve the value + * google.protobuf.Struct. It uses ``path`` to retrieve the value * from the struct and then check if it's matched to the specified value. * * For example, for the following Struct: diff --git a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts index 50b6690d3..bc81233fc 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/metadata/v3/MetadataKey.ts @@ -26,7 +26,7 @@ export interface _envoy_type_metadata_v3_MetadataKey_PathSegment__Output { } /** - * MetadataKey provides a general interface using `key` and `path` to retrieve value from + * MetadataKey provides a general interface using ``key`` and ``path`` to retrieve value from * :ref:`Metadata `. * * For example, for the following Metadata: @@ -67,7 +67,7 @@ export interface MetadataKey { } /** - * MetadataKey provides a general interface using `key` and `path` to retrieve value from + * MetadataKey provides a general interface using ``key`` and ``path`` to retrieve value from * :ref:`Metadata `. * * For example, for the following Metadata: diff --git a/packages/grpc-js-xds/src/generated/fault.ts b/packages/grpc-js-xds/src/generated/fault.ts index 896382e50..4ec7ed078 100644 --- a/packages/grpc-js-xds/src/generated/fault.ts +++ b/packages/grpc-js-xds/src/generated/fault.ts @@ -14,21 +14,16 @@ export interface ProtoGrpcType { core: { v3: { Address: MessageTypeDefinition - AggregatedConfigSource: MessageTypeDefinition - ApiConfigSource: MessageTypeDefinition - ApiVersion: EnumTypeDefinition AsyncDataSource: MessageTypeDefinition BackoffStrategy: MessageTypeDefinition BindConfig: MessageTypeDefinition BuildVersion: MessageTypeDefinition CidrRange: MessageTypeDefinition - ConfigSource: MessageTypeDefinition ControlPlane: MessageTypeDefinition DataSource: MessageTypeDefinition EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition - ExtensionConfigSource: MessageTypeDefinition - GrpcService: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition HeaderValueOption: MessageTypeDefinition @@ -38,8 +33,8 @@ export interface ProtoGrpcType { Node: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + ProxyProtocolPassThroughTLVs: MessageTypeDefinition QueryParameter: MessageTypeDefinition - RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition RequestMethod: EnumTypeDefinition RetryPolicy: MessageTypeDefinition @@ -49,9 +44,9 @@ export interface ProtoGrpcType { RuntimeFractionalPercent: MessageTypeDefinition RuntimePercent: MessageTypeDefinition RuntimeUInt32: MessageTypeDefinition - SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition @@ -61,6 +56,7 @@ export interface ProtoGrpcType { } route: { v3: { + ClusterSpecifierPlugin: MessageTypeDefinition CorsPolicy: MessageTypeDefinition Decorator: MessageTypeDefinition DirectResponseAction: MessageTypeDefinition @@ -76,6 +72,7 @@ export interface ProtoGrpcType { RetryPolicy: MessageTypeDefinition Route: MessageTypeDefinition RouteAction: MessageTypeDefinition + RouteList: MessageTypeDefinition RouteMatch: MessageTypeDefinition Tracing: MessageTypeDefinition VirtualCluster: MessageTypeDefinition @@ -146,7 +143,6 @@ export interface ProtoGrpcType { DescriptorProto: MessageTypeDefinition DoubleValue: MessageTypeDefinition Duration: MessageTypeDefinition - Empty: MessageTypeDefinition EnumDescriptorProto: MessageTypeDefinition EnumOptions: MessageTypeDefinition EnumValueDescriptorProto: MessageTypeDefinition @@ -227,8 +223,18 @@ export interface ProtoGrpcType { } core: { v3: { - Authority: MessageTypeDefinition ContextParams: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + Matcher: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } } } } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts b/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts index 5f81f0dd9..e47fd756c 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/MethodOptions.ts @@ -1,16 +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 { 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 | null); } export interface MethodOptions__Output { 'deprecated': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; - '.google.api.http': (_google_api_HttpRule__Output | null); } diff --git a/packages/grpc-js-xds/src/generated/http_connection_manager.ts b/packages/grpc-js-xds/src/generated/http_connection_manager.ts index 137dcd45a..e0e06f904 100644 --- a/packages/grpc-js-xds/src/generated/http_connection_manager.ts +++ b/packages/grpc-js-xds/src/generated/http_connection_manager.ts @@ -21,6 +21,7 @@ export interface ProtoGrpcType { ExtensionFilter: MessageTypeDefinition GrpcStatusFilter: MessageTypeDefinition HeaderFilter: MessageTypeDefinition + LogTypeFilter: MessageTypeDefinition MetadataFilter: MessageTypeDefinition NotHealthCheckFilter: MessageTypeDefinition OrFilter: MessageTypeDefinition @@ -48,6 +49,7 @@ export interface ProtoGrpcType { EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition ExtensionConfigSource: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition GrpcProtocolOptions: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition @@ -62,9 +64,12 @@ export interface ProtoGrpcType { Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition + PathConfigSource: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + ProxyProtocolPassThroughTLVs: MessageTypeDefinition QueryParameter: MessageTypeDefinition + QuicKeepAliveSettings: MessageTypeDefinition QuicProtocolOptions: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition @@ -80,6 +85,7 @@ export interface ProtoGrpcType { SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition SubstitutionFormatString: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TcpProtocolOptions: MessageTypeDefinition @@ -109,6 +115,7 @@ export interface ProtoGrpcType { Route: MessageTypeDefinition RouteAction: MessageTypeDefinition RouteConfiguration: MessageTypeDefinition + RouteList: MessageTypeDefinition RouteMatch: MessageTypeDefinition ScopedRouteConfiguration: MessageTypeDefinition Tracing: MessageTypeDefinition @@ -124,6 +131,21 @@ export interface ProtoGrpcType { } } } + data: { + accesslog: { + v3: { + AccessLogCommon: MessageTypeDefinition + AccessLogType: EnumTypeDefinition + ConnectionProperties: MessageTypeDefinition + HTTPAccessLogEntry: MessageTypeDefinition + HTTPRequestProperties: MessageTypeDefinition + HTTPResponseProperties: MessageTypeDefinition + ResponseFlags: MessageTypeDefinition + TCPAccessLogEntry: MessageTypeDefinition + TLSProperties: MessageTypeDefinition + } + } + } extensions: { filters: { network: { @@ -275,6 +297,17 @@ export interface ProtoGrpcType { v3: { Authority: MessageTypeDefinition ContextParams: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + Matcher: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } } } } diff --git a/packages/grpc-js-xds/src/generated/listener.ts b/packages/grpc-js-xds/src/generated/listener.ts index b92353ab1..4ffc4712d 100644 --- a/packages/grpc-js-xds/src/generated/listener.ts +++ b/packages/grpc-js-xds/src/generated/listener.ts @@ -21,6 +21,7 @@ export interface ProtoGrpcType { ExtensionFilter: MessageTypeDefinition GrpcStatusFilter: MessageTypeDefinition HeaderFilter: MessageTypeDefinition + LogTypeFilter: MessageTypeDefinition MetadataFilter: MessageTypeDefinition NotHealthCheckFilter: MessageTypeDefinition OrFilter: MessageTypeDefinition @@ -48,6 +49,7 @@ export interface ProtoGrpcType { EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition ExtensionConfigSource: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition GrpcProtocolOptions: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition @@ -62,9 +64,12 @@ export interface ProtoGrpcType { Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition + PathConfigSource: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + ProxyProtocolPassThroughTLVs: MessageTypeDefinition QueryParameter: MessageTypeDefinition + QuicKeepAliveSettings: MessageTypeDefinition QuicProtocolOptions: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition @@ -80,6 +85,7 @@ export interface ProtoGrpcType { SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TcpProtocolOptions: MessageTypeDefinition TrafficDirection: EnumTypeDefinition @@ -93,7 +99,9 @@ export interface ProtoGrpcType { listener: { v3: { ActiveRawUdpListenerConfig: MessageTypeDefinition + AdditionalAddress: MessageTypeDefinition ApiListener: MessageTypeDefinition + ApiListenerManager: MessageTypeDefinition Filter: MessageTypeDefinition FilterChain: MessageTypeDefinition FilterChainMatch: MessageTypeDefinition @@ -101,12 +109,15 @@ export interface ProtoGrpcType { ListenerCollection: MessageTypeDefinition ListenerFilter: MessageTypeDefinition ListenerFilterChainMatchPredicate: MessageTypeDefinition + ListenerManager: MessageTypeDefinition QuicProtocolOptions: MessageTypeDefinition UdpListenerConfig: MessageTypeDefinition + ValidationListenerManager: MessageTypeDefinition } } route: { v3: { + ClusterSpecifierPlugin: MessageTypeDefinition CorsPolicy: MessageTypeDefinition Decorator: MessageTypeDefinition DirectResponseAction: MessageTypeDefinition @@ -122,6 +133,7 @@ export interface ProtoGrpcType { RetryPolicy: MessageTypeDefinition Route: MessageTypeDefinition RouteAction: MessageTypeDefinition + RouteList: MessageTypeDefinition RouteMatch: MessageTypeDefinition Tracing: MessageTypeDefinition VirtualCluster: MessageTypeDefinition @@ -130,6 +142,21 @@ export interface ProtoGrpcType { } } } + data: { + accesslog: { + v3: { + AccessLogCommon: MessageTypeDefinition + AccessLogType: EnumTypeDefinition + ConnectionProperties: MessageTypeDefinition + HTTPAccessLogEntry: MessageTypeDefinition + HTTPRequestProperties: MessageTypeDefinition + HTTPResponseProperties: MessageTypeDefinition + ResponseFlags: MessageTypeDefinition + TCPAccessLogEntry: MessageTypeDefinition + TLSProperties: MessageTypeDefinition + } + } + } type: { matcher: { v3: { @@ -258,6 +285,17 @@ export interface ProtoGrpcType { CollectionEntry: MessageTypeDefinition ContextParams: MessageTypeDefinition ResourceLocator: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + Matcher: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } } } } diff --git a/packages/grpc-js-xds/src/generated/lrs.ts b/packages/grpc-js-xds/src/generated/lrs.ts index e57d6c249..d49f1123c 100644 --- a/packages/grpc-js-xds/src/generated/lrs.ts +++ b/packages/grpc-js-xds/src/generated/lrs.ts @@ -24,6 +24,7 @@ export interface ProtoGrpcType { DataSource: MessageTypeDefinition EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition HeaderValueOption: MessageTypeDefinition @@ -44,6 +45,7 @@ export interface ProtoGrpcType { RuntimeUInt32: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition diff --git a/packages/grpc-js-xds/src/generated/route.ts b/packages/grpc-js-xds/src/generated/route.ts index d6485bcd7..25552e612 100644 --- a/packages/grpc-js-xds/src/generated/route.ts +++ b/packages/grpc-js-xds/src/generated/route.ts @@ -28,6 +28,7 @@ export interface ProtoGrpcType { EnvoyInternalAddress: MessageTypeDefinition Extension: MessageTypeDefinition ExtensionConfigSource: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition GrpcService: MessageTypeDefinition HeaderMap: MessageTypeDefinition HeaderValue: MessageTypeDefinition @@ -36,8 +37,10 @@ export interface ProtoGrpcType { Locality: MessageTypeDefinition Metadata: MessageTypeDefinition Node: MessageTypeDefinition + PathConfigSource: MessageTypeDefinition Pipe: MessageTypeDefinition ProxyProtocolConfig: MessageTypeDefinition + ProxyProtocolPassThroughTLVs: MessageTypeDefinition QueryParameter: MessageTypeDefinition RateLimitSettings: MessageTypeDefinition RemoteDataSource: MessageTypeDefinition @@ -52,6 +55,7 @@ export interface ProtoGrpcType { SelfConfigSource: MessageTypeDefinition SocketAddress: MessageTypeDefinition SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition TcpKeepalive: MessageTypeDefinition TrafficDirection: EnumTypeDefinition TransportSocket: MessageTypeDefinition @@ -78,6 +82,7 @@ export interface ProtoGrpcType { Route: MessageTypeDefinition RouteAction: MessageTypeDefinition RouteConfiguration: MessageTypeDefinition + RouteList: MessageTypeDefinition RouteMatch: MessageTypeDefinition Tracing: MessageTypeDefinition Vhds: MessageTypeDefinition @@ -212,6 +217,17 @@ export interface ProtoGrpcType { v3: { Authority: MessageTypeDefinition ContextParams: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + Matcher: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } } } } diff --git a/packages/grpc-js-xds/src/generated/wrr_locality.ts b/packages/grpc-js-xds/src/generated/wrr_locality.ts new file mode 100644 index 000000000..e0275ef9a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/wrr_locality.ts @@ -0,0 +1,231 @@ +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: { + annotations: { + } + config: { + cluster: { + v3: { + CircuitBreakers: MessageTypeDefinition + Cluster: MessageTypeDefinition + ClusterCollection: MessageTypeDefinition + Filter: MessageTypeDefinition + LoadBalancingPolicy: MessageTypeDefinition + OutlierDetection: MessageTypeDefinition + TrackClusterStats: MessageTypeDefinition + UpstreamConnectionOptions: MessageTypeDefinition + } + } + core: { + v3: { + Address: MessageTypeDefinition + AggregatedConfigSource: MessageTypeDefinition + AlternateProtocolsCacheOptions: MessageTypeDefinition + ApiConfigSource: MessageTypeDefinition + ApiVersion: EnumTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ConfigSource: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + DnsResolutionConfig: MessageTypeDefinition + DnsResolverOptions: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + EventServiceConfig: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtensionConfigSource: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition + GrpcProtocolOptions: MessageTypeDefinition + GrpcService: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HealthCheck: MessageTypeDefinition + HealthStatus: EnumTypeDefinition + HealthStatusSet: MessageTypeDefinition + Http1ProtocolOptions: MessageTypeDefinition + Http2ProtocolOptions: MessageTypeDefinition + Http3ProtocolOptions: MessageTypeDefinition + HttpProtocolOptions: MessageTypeDefinition + HttpUri: MessageTypeDefinition + KeepaliveSettings: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + PathConfigSource: MessageTypeDefinition + Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + QuicKeepAliveSettings: MessageTypeDefinition + QuicProtocolOptions: MessageTypeDefinition + RateLimitSettings: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SchemeHeaderTransformation: MessageTypeDefinition + SelfConfigSource: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TcpProtocolOptions: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + TypedExtensionConfig: MessageTypeDefinition + UpstreamHttpProtocolOptions: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + endpoint: { + v3: { + ClusterLoadAssignment: MessageTypeDefinition + Endpoint: MessageTypeDefinition + LbEndpoint: MessageTypeDefinition + LedsClusterLocalityConfig: MessageTypeDefinition + LocalityLbEndpoints: MessageTypeDefinition + } + } + } + extensions: { + load_balancing_policies: { + wrr_locality: { + v3: { + WrrLocality: MessageTypeDefinition + } + } + } + } + type: { + matcher: { + v3: { + ListStringMatcher: MessageTypeDefinition + RegexMatchAndSubstitute: MessageTypeDefinition + RegexMatcher: MessageTypeDefinition + StringMatcher: MessageTypeDefinition + } + } + v3: { + CodecClientType: EnumTypeDefinition + DoubleRange: MessageTypeDefinition + FractionalPercent: MessageTypeDefinition + Int32Range: MessageTypeDefinition + Int64Range: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: 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 + FieldSecurityAnnotation: MessageTypeDefinition + FileMigrateAnnotation: MessageTypeDefinition + MigrateAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + VersioningAnnotation: 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 + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + Authority: MessageTypeDefinition + CollectionEntry: MessageTypeDefinition + ContextParams: MessageTypeDefinition + ResourceLocator: MessageTypeDefinition + } + } + } +} + diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/TypedExtensionConfig.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/TypedExtensionConfig.ts new file mode 100644 index 000000000..b6ce18c0a --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/TypedExtensionConfig.ts @@ -0,0 +1,43 @@ +// Original file: deps/xds/xds/core/v3/extension.proto + +import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; + +/** + * Message type for extension configuration. + */ +export interface TypedExtensionConfig { + /** + * The name of an extension. This is not used to select the extension, instead + * it serves the role of an opaque identifier. + */ + 'name'?: (string); + /** + * The typed config for the extension. The type URL will be used to identify + * the extension. In the case that the type URL is *xds.type.v3.TypedStruct* + * (or, for historical reasons, *udpa.type.v1.TypedStruct*), the inner type + * URL of *TypedStruct* will be utilized. See the + * :ref:`extension configuration overview + * ` for further details. + */ + 'typed_config'?: (_google_protobuf_Any | null); +} + +/** + * Message type for extension configuration. + */ +export interface TypedExtensionConfig__Output { + /** + * The name of an extension. This is not used to select the extension, instead + * it serves the role of an opaque identifier. + */ + 'name': (string); + /** + * The typed config for the extension. The type URL will be used to identify + * the extension. In the case that the type URL is *xds.type.v3.TypedStruct* + * (or, for historical reasons, *udpa.type.v1.TypedStruct*), the inner type + * URL of *TypedStruct* will be utilized. See the + * :ref:`extension configuration overview + * ` for further details. + */ + 'typed_config': (_google_protobuf_Any__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/ListStringMatcher.ts b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/ListStringMatcher.ts new file mode 100644 index 000000000..e839f292b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/ListStringMatcher.ts @@ -0,0 +1,17 @@ +// Original file: deps/xds/xds/type/matcher/v3/string.proto + +import type { StringMatcher as _xds_type_matcher_v3_StringMatcher, StringMatcher__Output as _xds_type_matcher_v3_StringMatcher__Output } from '../../../../xds/type/matcher/v3/StringMatcher'; + +/** + * Specifies a list of ways to match a string. + */ +export interface ListStringMatcher { + 'patterns'?: (_xds_type_matcher_v3_StringMatcher)[]; +} + +/** + * Specifies a list of ways to match a string. + */ +export interface ListStringMatcher__Output { + 'patterns': (_xds_type_matcher_v3_StringMatcher__Output)[]; +} diff --git a/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/Matcher.ts b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/Matcher.ts new file mode 100644 index 000000000..be93c0f16 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/Matcher.ts @@ -0,0 +1,307 @@ +// Original file: deps/xds/xds/type/matcher/v3/matcher.proto + +import type { Matcher as _xds_type_matcher_v3_Matcher, Matcher__Output as _xds_type_matcher_v3_Matcher__Output } from '../../../../xds/type/matcher/v3/Matcher'; +import type { TypedExtensionConfig as _xds_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _xds_core_v3_TypedExtensionConfig__Output } from '../../../../xds/core/v3/TypedExtensionConfig'; +import type { StringMatcher as _xds_type_matcher_v3_StringMatcher, StringMatcher__Output as _xds_type_matcher_v3_StringMatcher__Output } from '../../../../xds/type/matcher/v3/StringMatcher'; + +/** + * An individual matcher. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_FieldMatcher { + /** + * Determines if the match succeeds. + */ + 'predicate'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate | null); + /** + * What to do if the match succeeds. + */ + 'on_match'?: (_xds_type_matcher_v3_Matcher_OnMatch | null); +} + +/** + * An individual matcher. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_FieldMatcher__Output { + /** + * Determines if the match succeeds. + */ + 'predicate': (_xds_type_matcher_v3_Matcher_MatcherList_Predicate__Output | null); + /** + * What to do if the match succeeds. + */ + 'on_match': (_xds_type_matcher_v3_Matcher_OnMatch__Output | null); +} + +/** + * A map of configured matchers. Used to allow using a map within a oneof. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherTree_MatchMap { + 'map'?: ({[key: string]: _xds_type_matcher_v3_Matcher_OnMatch}); +} + +/** + * A map of configured matchers. Used to allow using a map within a oneof. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherTree_MatchMap__Output { + 'map': ({[key: string]: _xds_type_matcher_v3_Matcher_OnMatch__Output}); +} + +/** + * A linear list of field matchers. + * The field matchers are evaluated in order, and the first match + * wins. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList { + /** + * A list of matchers. First match wins. + */ + 'matchers'?: (_xds_type_matcher_v3_Matcher_MatcherList_FieldMatcher)[]; +} + +/** + * A linear list of field matchers. + * The field matchers are evaluated in order, and the first match + * wins. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList__Output { + /** + * A list of matchers. First match wins. + */ + 'matchers': (_xds_type_matcher_v3_Matcher_MatcherList_FieldMatcher__Output)[]; +} + +export interface _xds_type_matcher_v3_Matcher_MatcherTree { + /** + * Protocol-specific specification of input field to match on. + */ + 'input'?: (_xds_core_v3_TypedExtensionConfig | null); + 'exact_match_map'?: (_xds_type_matcher_v3_Matcher_MatcherTree_MatchMap | null); + /** + * Longest matching prefix wins. + */ + 'prefix_match_map'?: (_xds_type_matcher_v3_Matcher_MatcherTree_MatchMap | null); + /** + * Extension for custom matching logic. + */ + 'custom_match'?: (_xds_core_v3_TypedExtensionConfig | null); + /** + * Exact or prefix match maps in which to look up the input value. + * If the lookup succeeds, the match is considered successful, and + * the corresponding OnMatch is used. + */ + 'tree_type'?: "exact_match_map"|"prefix_match_map"|"custom_match"; +} + +export interface _xds_type_matcher_v3_Matcher_MatcherTree__Output { + /** + * Protocol-specific specification of input field to match on. + */ + 'input': (_xds_core_v3_TypedExtensionConfig__Output | null); + 'exact_match_map'?: (_xds_type_matcher_v3_Matcher_MatcherTree_MatchMap__Output | null); + /** + * Longest matching prefix wins. + */ + 'prefix_match_map'?: (_xds_type_matcher_v3_Matcher_MatcherTree_MatchMap__Output | null); + /** + * Extension for custom matching logic. + */ + 'custom_match'?: (_xds_core_v3_TypedExtensionConfig__Output | null); + /** + * Exact or prefix match maps in which to look up the input value. + * If the lookup succeeds, the match is considered successful, and + * the corresponding OnMatch is used. + */ + 'tree_type': "exact_match_map"|"prefix_match_map"|"custom_match"; +} + +/** + * What to do if a match is successful. + */ +export interface _xds_type_matcher_v3_Matcher_OnMatch { + /** + * Nested matcher to evaluate. + * If the nested matcher does not match and does not specify + * on_no_match, then this matcher is considered not to have + * matched, even if a predicate at this level or above returned + * true. + */ + 'matcher'?: (_xds_type_matcher_v3_Matcher | null); + /** + * Protocol-specific action to take. + */ + 'action'?: (_xds_core_v3_TypedExtensionConfig | null); + 'on_match'?: "matcher"|"action"; +} + +/** + * What to do if a match is successful. + */ +export interface _xds_type_matcher_v3_Matcher_OnMatch__Output { + /** + * Nested matcher to evaluate. + * If the nested matcher does not match and does not specify + * on_no_match, then this matcher is considered not to have + * matched, even if a predicate at this level or above returned + * true. + */ + 'matcher'?: (_xds_type_matcher_v3_Matcher__Output | null); + /** + * Protocol-specific action to take. + */ + 'action'?: (_xds_core_v3_TypedExtensionConfig__Output | null); + 'on_match': "matcher"|"action"; +} + +/** + * Predicate to determine if a match is successful. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_Predicate { + /** + * A single predicate to evaluate. + */ + 'single_predicate'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate_SinglePredicate | null); + /** + * A list of predicates to be OR-ed together. + */ + 'or_matcher'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate_PredicateList | null); + /** + * A list of predicates to be AND-ed together. + */ + 'and_matcher'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate_PredicateList | null); + /** + * The invert of a predicate + */ + 'not_matcher'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate | null); + 'match_type'?: "single_predicate"|"or_matcher"|"and_matcher"|"not_matcher"; +} + +/** + * Predicate to determine if a match is successful. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_Predicate__Output { + /** + * A single predicate to evaluate. + */ + 'single_predicate'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate_SinglePredicate__Output | null); + /** + * A list of predicates to be OR-ed together. + */ + 'or_matcher'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate_PredicateList__Output | null); + /** + * A list of predicates to be AND-ed together. + */ + 'and_matcher'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate_PredicateList__Output | null); + /** + * The invert of a predicate + */ + 'not_matcher'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate__Output | null); + 'match_type': "single_predicate"|"or_matcher"|"and_matcher"|"not_matcher"; +} + +/** + * A list of two or more matchers. Used to allow using a list within a oneof. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_Predicate_PredicateList { + 'predicate'?: (_xds_type_matcher_v3_Matcher_MatcherList_Predicate)[]; +} + +/** + * A list of two or more matchers. Used to allow using a list within a oneof. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_Predicate_PredicateList__Output { + 'predicate': (_xds_type_matcher_v3_Matcher_MatcherList_Predicate__Output)[]; +} + +/** + * Predicate for a single input field. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_Predicate_SinglePredicate { + /** + * Protocol-specific specification of input field to match on. + * [#extension-category: envoy.matching.common_inputs] + */ + 'input'?: (_xds_core_v3_TypedExtensionConfig | null); + /** + * Built-in string matcher. + */ + 'value_match'?: (_xds_type_matcher_v3_StringMatcher | null); + /** + * Extension for custom matching logic. + * [#extension-category: envoy.matching.input_matchers] + */ + 'custom_match'?: (_xds_core_v3_TypedExtensionConfig | null); + 'matcher'?: "value_match"|"custom_match"; +} + +/** + * Predicate for a single input field. + */ +export interface _xds_type_matcher_v3_Matcher_MatcherList_Predicate_SinglePredicate__Output { + /** + * Protocol-specific specification of input field to match on. + * [#extension-category: envoy.matching.common_inputs] + */ + 'input': (_xds_core_v3_TypedExtensionConfig__Output | null); + /** + * Built-in string matcher. + */ + 'value_match'?: (_xds_type_matcher_v3_StringMatcher__Output | null); + /** + * Extension for custom matching logic. + * [#extension-category: envoy.matching.input_matchers] + */ + 'custom_match'?: (_xds_core_v3_TypedExtensionConfig__Output | null); + 'matcher': "value_match"|"custom_match"; +} + +/** + * A matcher, which may traverse a matching tree in order to result in a match action. + * During matching, the tree will be traversed until a match is found, or if no match + * is found the action specified by the most specific on_no_match will be evaluated. + * As an on_no_match might result in another matching tree being evaluated, this process + * might repeat several times until the final OnMatch (or no match) is decided. + */ +export interface Matcher { + /** + * A linear list of matchers to evaluate. + */ + 'matcher_list'?: (_xds_type_matcher_v3_Matcher_MatcherList | null); + /** + * A match tree to evaluate. + */ + 'matcher_tree'?: (_xds_type_matcher_v3_Matcher_MatcherTree | null); + /** + * Optional OnMatch to use if the matcher failed. + * If specified, the OnMatch is used, and the matcher is considered + * to have matched. + * If not specified, the matcher is considered not to have matched. + */ + 'on_no_match'?: (_xds_type_matcher_v3_Matcher_OnMatch | null); + 'matcher_type'?: "matcher_list"|"matcher_tree"; +} + +/** + * A matcher, which may traverse a matching tree in order to result in a match action. + * During matching, the tree will be traversed until a match is found, or if no match + * is found the action specified by the most specific on_no_match will be evaluated. + * As an on_no_match might result in another matching tree being evaluated, this process + * might repeat several times until the final OnMatch (or no match) is decided. + */ +export interface Matcher__Output { + /** + * A linear list of matchers to evaluate. + */ + 'matcher_list'?: (_xds_type_matcher_v3_Matcher_MatcherList__Output | null); + /** + * A match tree to evaluate. + */ + 'matcher_tree'?: (_xds_type_matcher_v3_Matcher_MatcherTree__Output | null); + /** + * Optional OnMatch to use if the matcher failed. + * If specified, the OnMatch is used, and the matcher is considered + * to have matched. + * If not specified, the matcher is considered not to have matched. + */ + 'on_no_match': (_xds_type_matcher_v3_Matcher_OnMatch__Output | null); + 'matcher_type': "matcher_list"|"matcher_tree"; +} diff --git a/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/RegexMatcher.ts b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/RegexMatcher.ts new file mode 100644 index 000000000..575051041 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/RegexMatcher.ts @@ -0,0 +1,80 @@ +// Original file: deps/xds/xds/type/matcher/v3/regex.proto + + +/** + * Google's `RE2 `_ regex engine. The regex + * string must adhere to the documented `syntax + * `_. The engine is designed to + * complete execution in linear time as well as limit the amount of memory + * used. + * + * Envoy supports program size checking via runtime. The runtime keys + * `re2.max_program_size.error_level` and `re2.max_program_size.warn_level` + * can be set to integers as the maximum program size or complexity that a + * compiled regex can have before an exception is thrown or a warning is + * logged, respectively. `re2.max_program_size.error_level` defaults to 100, + * and `re2.max_program_size.warn_level` has no default if unset (will not + * check/log a warning). + * + * Envoy emits two stats for tracking the program size of regexes: the + * histogram `re2.program_size`, which records the program size, and the + * counter `re2.exceeded_warn_level`, which is incremented each time the + * program size exceeds the warn level threshold. + */ +export interface _xds_type_matcher_v3_RegexMatcher_GoogleRE2 { +} + +/** + * Google's `RE2 `_ regex engine. The regex + * string must adhere to the documented `syntax + * `_. The engine is designed to + * complete execution in linear time as well as limit the amount of memory + * used. + * + * Envoy supports program size checking via runtime. The runtime keys + * `re2.max_program_size.error_level` and `re2.max_program_size.warn_level` + * can be set to integers as the maximum program size or complexity that a + * compiled regex can have before an exception is thrown or a warning is + * logged, respectively. `re2.max_program_size.error_level` defaults to 100, + * and `re2.max_program_size.warn_level` has no default if unset (will not + * check/log a warning). + * + * Envoy emits two stats for tracking the program size of regexes: the + * histogram `re2.program_size`, which records the program size, and the + * counter `re2.exceeded_warn_level`, which is incremented each time the + * program size exceeds the warn level threshold. + */ +export interface _xds_type_matcher_v3_RegexMatcher_GoogleRE2__Output { +} + +/** + * A regex matcher designed for safety when used with untrusted input. + */ +export interface RegexMatcher { + /** + * Google's RE2 regex engine. + */ + 'google_re2'?: (_xds_type_matcher_v3_RegexMatcher_GoogleRE2 | null); + /** + * The regex match string. The string must be supported by the configured + * engine. + */ + 'regex'?: (string); + 'engine_type'?: "google_re2"; +} + +/** + * A regex matcher designed for safety when used with untrusted input. + */ +export interface RegexMatcher__Output { + /** + * Google's RE2 regex engine. + */ + 'google_re2'?: (_xds_type_matcher_v3_RegexMatcher_GoogleRE2__Output | null); + /** + * The regex match string. The string must be supported by the configured + * engine. + */ + 'regex': (string); + 'engine_type': "google_re2"; +} diff --git a/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/StringMatcher.ts b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/StringMatcher.ts new file mode 100644 index 000000000..af2f2f56f --- /dev/null +++ b/packages/grpc-js-xds/src/generated/xds/type/matcher/v3/StringMatcher.ts @@ -0,0 +1,109 @@ +// Original file: deps/xds/xds/type/matcher/v3/string.proto + +import type { RegexMatcher as _xds_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _xds_type_matcher_v3_RegexMatcher__Output } from '../../../../xds/type/matcher/v3/RegexMatcher'; + +/** + * Specifies the way to match a string. + * [#next-free-field: 8] + */ +export interface StringMatcher { + /** + * The input string must match exactly the string specified here. + * + * Examples: + * + * * *abc* only matches the value *abc*. + */ + 'exact'?: (string); + /** + * The input string must have the prefix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *abc.xyz* + */ + 'prefix'?: (string); + /** + * The input string must have the suffix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc* + */ + 'suffix'?: (string); + /** + * The input string must match the regular expression specified here. + */ + 'safe_regex'?: (_xds_type_matcher_v3_RegexMatcher | null); + /** + * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no + * effect for the safe_regex match. + * For example, the matcher *data* will match both input string *Data* and *data* if set to true. + */ + 'ignore_case'?: (boolean); + /** + * The input string must have the substring specified here. + * Note: empty contains match is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc.def* + */ + 'contains'?: (string); + 'match_pattern'?: "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; +} + +/** + * Specifies the way to match a string. + * [#next-free-field: 8] + */ +export interface StringMatcher__Output { + /** + * The input string must match exactly the string specified here. + * + * Examples: + * + * * *abc* only matches the value *abc*. + */ + 'exact'?: (string); + /** + * The input string must have the prefix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *abc.xyz* + */ + 'prefix'?: (string); + /** + * The input string must have the suffix specified here. + * Note: empty prefix is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc* + */ + 'suffix'?: (string); + /** + * The input string must match the regular expression specified here. + */ + 'safe_regex'?: (_xds_type_matcher_v3_RegexMatcher__Output | null); + /** + * If true, indicates the exact/prefix/suffix matching should be case insensitive. This has no + * effect for the safe_regex match. + * For example, the matcher *data* will match both input string *Data* and *data* if set to true. + */ + 'ignore_case': (boolean); + /** + * The input string must have the substring specified here. + * Note: empty contains match is not allowed, please use regex instead. + * + * Examples: + * + * * *abc* matches the value *xyz.abc.def* + */ + 'contains'?: (string); + 'match_pattern': "exact"|"prefix"|"suffix"|"safe_regex"|"contains"; +} diff --git a/packages/grpc-js-xds/src/load-balancer-lrs.ts b/packages/grpc-js-xds/src/load-balancer-lrs.ts deleted file mode 100644 index 40c653a03..000000000 --- a/packages/grpc-js-xds/src/load-balancer-lrs.ts +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2020 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 { connectivityState as ConnectivityState, StatusObject, status as Status, experimental } from '@grpc/grpc-js'; -import { Locality__Output } from './generated/envoy/config/core/v3/Locality'; -import { validateXdsServerConfig, XdsServerConfig } from './xds-bootstrap'; -import { XdsClusterLocalityStats, XdsClient, getSingletonXdsClient } from './xds-client'; -import LoadBalancer = experimental.LoadBalancer; -import ChannelControlHelper = experimental.ChannelControlHelper; -import registerLoadBalancerType = experimental.registerLoadBalancerType; -import SubchannelAddress = experimental.SubchannelAddress; -import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; -import Picker = experimental.Picker; -import PickArgs = experimental.PickArgs; -import PickResultType = experimental.PickResultType; -import PickResult = experimental.PickResult; -import Filter = experimental.Filter; -import BaseFilter = experimental.BaseFilter; -import FilterFactory = experimental.FilterFactory; -import Call = experimental.CallStream; -import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; -import selectLbConfigFromList = experimental.selectLbConfigFromList; - -const TYPE_NAME = 'lrs'; - -class LrsLoadBalancingConfig implements TypedLoadBalancingConfig { - getLoadBalancerName(): string { - return TYPE_NAME; - } - toJsonObject(): object { - return { - [TYPE_NAME]: { - cluster_name: this.clusterName, - eds_service_name: this.edsServiceName, - lrs_load_reporting_server: this.lrsLoadReportingServer, - locality: this.locality, - child_policy: [this.childPolicy.toJsonObject()] - } - } - } - - constructor(private clusterName: string, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, private locality: Locality__Output, private childPolicy: TypedLoadBalancingConfig) {} - - getClusterName() { - return this.clusterName; - } - - getEdsServiceName() { - return this.edsServiceName; - } - - getLrsLoadReportingServer() { - return this.lrsLoadReportingServer; - } - - getLocality() { - return this.locality; - } - - getChildPolicy() { - return this.childPolicy; - } - - static createFromJson(obj: any): LrsLoadBalancingConfig { - if (!('cluster_name' in obj && typeof obj.cluster_name === 'string')) { - throw new Error('lrs config must have a string field cluster_name'); - } - if (!('eds_service_name' in obj && typeof obj.eds_service_name === 'string')) { - throw new Error('lrs config must have a string field eds_service_name'); - } - if (!('locality' in obj && obj.locality !== null && typeof obj.locality === 'object')) { - throw new Error('lrs config must have an object field locality'); - } - if ('region' in obj.locality && typeof obj.locality.region !== 'string') { - throw new Error('lrs config locality.region field must be a string if provided'); - } - if ('zone' in obj.locality && typeof obj.locality.zone !== 'string') { - throw new Error('lrs config locality.zone field must be a string if provided'); - } - if ('sub_zone' in obj.locality && typeof obj.locality.sub_zone !== 'string') { - throw new Error('lrs config locality.sub_zone field must be a string if provided'); - } - if (!('child_policy' in obj && Array.isArray(obj.child_policy))) { - throw new Error('lrs config must have a child_policy array'); - } - if (!('lrs_load_reporting_server' in obj && obj.lrs_load_reporting_server !== null && typeof obj.lrs_load_reporting_server === 'object')) { - throw new Error('lrs config must have an object field lrs_load_reporting_server'); - } - const childConfig = selectLbConfigFromList(obj.child_policy); - if (!childConfig) { - throw new Error('lrs config child_policy parsing failed'); - } - return new LrsLoadBalancingConfig(obj.cluster_name, obj.eds_service_name, validateXdsServerConfig(obj.lrs_load_reporting_server), { - region: obj.locality.region ?? '', - zone: obj.locality.zone ?? '', - sub_zone: obj.locality.sub_zone ?? '' - }, childConfig); - } -} - -/** - * Picker that delegates picking to another picker, and reports when calls - * created using those picks start and end. - */ -class LoadReportingPicker implements Picker { - constructor( - private wrappedPicker: Picker, - private localityStatsReporter: XdsClusterLocalityStats - ) {} - - pick(pickArgs: PickArgs): PickResult { - const wrappedPick = this.wrappedPicker.pick(pickArgs); - if (wrappedPick.pickResultType === PickResultType.COMPLETE) { - return { - pickResultType: PickResultType.COMPLETE, - subchannel: wrappedPick.subchannel, - status: null, - onCallStarted: () => { - wrappedPick.onCallStarted?.(); - this.localityStatsReporter.addCallStarted(); - }, - onCallEnded: status => { - wrappedPick.onCallEnded?.(status); - this.localityStatsReporter.addCallFinished(status !== Status.OK); - } - }; - } else { - return wrappedPick; - } - } -} - -/** - * "Load balancer" that delegates the actual load balancing logic to another - * LoadBalancer class and adds hooks to track when calls started using that - * LoadBalancer start and end, and uses the XdsClient to report that - * information back to the xDS server. - */ -export class LrsLoadBalancer implements LoadBalancer { - private childBalancer: ChildLoadBalancerHandler; - private localityStatsReporter: XdsClusterLocalityStats | null = null; - - constructor(private channelControlHelper: ChannelControlHelper) { - this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(channelControlHelper, { - updateState: (connectivityState: ConnectivityState, picker: Picker) => { - if (this.localityStatsReporter !== null) { - picker = new LoadReportingPicker(picker, this.localityStatsReporter); - } - channelControlHelper.updateState(connectivityState, picker); - }, - })); - } - - updateAddressList( - addressList: SubchannelAddress[], - lbConfig: TypedLoadBalancingConfig, - attributes: { [key: string]: unknown } - ): void { - if (!(lbConfig instanceof LrsLoadBalancingConfig)) { - return; - } - this.localityStatsReporter = (attributes.xdsClient as XdsClient).addClusterLocalityStats( - lbConfig.getLrsLoadReportingServer(), - lbConfig.getClusterName(), - lbConfig.getEdsServiceName(), - lbConfig.getLocality() - ); - this.childBalancer.updateAddressList(addressList, lbConfig.getChildPolicy(), attributes); - } - exitIdle(): void { - this.childBalancer.exitIdle(); - } - resetBackoff(): void { - this.childBalancer.resetBackoff(); - } - destroy(): void { - this.childBalancer.destroy(); - } - getTypeName(): string { - return TYPE_NAME; - } -} - -export function setup() { - registerLoadBalancerType(TYPE_NAME, LrsLoadBalancer, LrsLoadBalancingConfig); -} From 69257a78937d60bfe27574c3e10cdd24df0ecc48 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 21 Aug 2023 11:14:17 -0700 Subject: [PATCH 565/694] grpc-js: Fix method config name handling in service configs --- packages/grpc-js/package.json | 2 +- .../grpc-js/src/resolving-load-balancer.ts | 92 ++++++++++++++++--- packages/grpc-js/src/service-config.ts | 40 +++++--- 3 files changed, 107 insertions(+), 27 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 97ff965a2..800dde9ac 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.0", + "version": "1.9.1", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index d49609ff2..9b5d4c2dc 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -21,7 +21,11 @@ import { LoadBalancingConfig, getFirstUsableConfig, } from './load-balancer'; -import { ServiceConfig, validateServiceConfig } from './service-config'; +import { + MethodConfig, + ServiceConfig, + validateServiceConfig, +} from './service-config'; import { ConnectivityState } from './connectivity-state'; import { ConfigSelector, createResolver, Resolver } from './resolver'; import { ServiceError } from './call'; @@ -43,6 +47,59 @@ function trace(text: string): void { logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); } +type NameMatchLevel = 'EMPTY' | 'SERVICE' | 'SERVICE_AND_METHOD'; + +/** + * Name match levels in order from most to least specific. This is the order in + * which searches will be performed. + */ +const NAME_MATCH_LEVEL_ORDER: NameMatchLevel[] = [ + 'SERVICE_AND_METHOD', + 'SERVICE', + 'EMPTY', +]; + +function hasMatchingName( + service: string, + method: string, + methodConfig: MethodConfig, + matchLevel: NameMatchLevel +): boolean { + for (const name of methodConfig.name) { + switch (matchLevel) { + case 'EMPTY': + if (!name.service && !name.method) { + return true; + } + break; + case 'SERVICE': + if (name.service === service && !name.method) { + return true; + } + break; + case 'SERVICE_AND_METHOD': + if (name.service === service && name.method === method) { + return true; + } + } + } + return false; +} + +function findMatchingConfig( + service: string, + method: string, + methodConfigs: MethodConfig[], + matchLevel: NameMatchLevel +): MethodConfig | null { + for (const config of methodConfigs) { + if (hasMatchingName(service, method, config, matchLevel)) { + return config; + } + } + return null; +} + function getDefaultConfigSelector( serviceConfig: ServiceConfig | null ): ConfigSelector { @@ -54,19 +111,26 @@ function getDefaultConfigSelector( const service = splitName[0] ?? ''; const method = splitName[1] ?? ''; if (serviceConfig && serviceConfig.methodConfig) { - for (const methodConfig of serviceConfig.methodConfig) { - for (const name of methodConfig.name) { - if ( - name.service === service && - (name.method === undefined || name.method === method) - ) { - return { - methodConfig: methodConfig, - pickInformation: {}, - status: Status.OK, - dynamicFilterFactories: [], - }; - } + /* Check for the following in order, and return the first method + * config that matches: + * 1. A name that exactly matches the service and method + * 2. A name with no method set that matches the service + * 3. An empty name + */ + for (const matchLevel of NAME_MATCH_LEVEL_ORDER) { + const matchingConfig = findMatchingConfig( + service, + method, + serviceConfig.methodConfig, + matchLevel + ); + if (matchingConfig) { + return { + methodConfig: matchingConfig, + pickInformation: {}, + status: Status.OK, + dynamicFilterFactories: [], + }; } } } diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index 91bee52c2..aece7cb77 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -35,7 +35,7 @@ import { } from './load-balancer'; export interface MethodConfigName { - service: string; + service?: string; method?: string; } @@ -95,20 +95,36 @@ const DURATION_REGEX = /^\d+(\.\d{1,9})?s$/; const CLIENT_LANGUAGE_STRING = 'node'; function validateName(obj: any): MethodConfigName { - if (!('service' in obj) || typeof obj.service !== 'string') { - throw new Error('Invalid method config name: invalid service'); - } - const result: MethodConfigName = { - service: obj.service, - }; - if ('method' in obj) { - if (typeof obj.method === 'string') { - result.method = obj.method; + // In this context, and unset field and '' are considered the same + if ('service' in obj && obj.service !== '') { + if (typeof obj.service !== 'string') { + throw new Error( + `Invalid method config name: invalid service: expected type string, got ${typeof obj.service}` + ); + } + if ('method' in obj && obj.method !== '') { + if (typeof obj.method !== 'string') { + throw new Error( + `Invalid method config name: invalid method: expected type string, got ${typeof obj.service}` + ); + } + return { + service: obj.service, + method: obj.method, + }; } else { - throw new Error('Invalid method config name: invalid method'); + return { + service: obj.service, + }; } + } else { + if ('method' in obj && obj.method !== undefined) { + throw new Error( + `Invalid method config name: method set with empty or unset service` + ); + } + return {}; } - return result; } function validateRetryPolicy(obj: any): RetryPolicy { From f9af919393a6753ad7cb144aa4695d63809fc6e1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 21 Aug 2023 13:17:11 -0700 Subject: [PATCH 566/694] grpc-js: Update dependency on @grpc/proto-loader --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 97ff965a2..70fd573ca 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -65,7 +65,7 @@ "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto" }, "dependencies": { - "@grpc/proto-loader": "^0.7.0", + "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" }, "files": [ From 8896bfe4c969b756b3e18d2e83ec4480aeac3a9e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 21 Aug 2023 13:30:33 -0700 Subject: [PATCH 567/694] grpc-js: Defer actions in http2 stream write callback --- packages/grpc-js/src/subchannel-call.ts | 26 +++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index e06ece388..3b9b6152f 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -501,16 +501,22 @@ export class Http2SubchannelCall implements SubchannelCall { sendMessageWithContext(context: MessageContext, message: Buffer) { this.trace('write() called with message of length ' + message.length); const cb: WriteCallback = (error?: Error | null) => { - let code: Status = Status.UNAVAILABLE; - if ( - (error as NodeJS.ErrnoException)?.code === 'ERR_STREAM_WRITE_AFTER_END' - ) { - code = Status.INTERNAL; - } - if (error) { - this.cancelWithStatus(code, `Write error: ${error.message}`); - } - context.callback?.(); + /* nextTick here ensures that no stream action can be taken in the call + * stack of the write callback, in order to hopefully work around + * https://github.com/nodejs/node/issues/49147 */ + process.nextTick(() => { + let code: Status = Status.UNAVAILABLE; + if ( + (error as NodeJS.ErrnoException)?.code === + 'ERR_STREAM_WRITE_AFTER_END' + ) { + code = Status.INTERNAL; + } + if (error) { + this.cancelWithStatus(code, `Write error: ${error.message}`); + } + context.callback?.(); + }); }; this.trace('sending data chunk of length ' + message.length); this.callEventTracker.addMessageSent(); From c6797262460207c82cd74496a06b11642879c06e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 22 Aug 2023 09:53:19 -0700 Subject: [PATCH 568/694] Add custom LB interop test support --- .../grpc-js-xds/interop/xds-interop-client.ts | 100 +++++++++++++++++- .../src/load-balancer-xds-wrr-locality.ts | 54 ++++++++++ packages/grpc-js/src/load-balancing-call.ts | 4 +- 3 files changed, 151 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 3055b5b5d..c28e9cc28 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -30,8 +30,98 @@ import { XdsUpdateClientConfigureServiceHandlers } from './generated/grpc/testin import { Empty__Output } from './generated/grpc/testing/Empty'; import { LoadBalancerAccumulatedStatsResponse } from './generated/grpc/testing/LoadBalancerAccumulatedStatsResponse'; +import TypedLoadBalancingConfig = grpc.experimental.TypedLoadBalancingConfig; +import LoadBalancer = grpc.experimental.LoadBalancer; +import ChannelControlHelper = grpc.experimental.ChannelControlHelper; +import ChildLoadBalancerHandler = grpc.experimental.ChildLoadBalancerHandler; +import SubchannelAddress = grpc.experimental.SubchannelAddress; +import Picker = grpc.experimental.Picker; +import PickArgs = grpc.experimental.PickArgs; +import PickResult = grpc.experimental.PickResult; +import PickResultType = grpc.experimental.PickResultType; +import createChildChannelControlHelper = grpc.experimental.createChildChannelControlHelper; +import parseLoadBalancingConfig = grpc.experimental.parseLoadBalancingConfig; + grpc_xds.register(); +const LB_POLICY_NAME = 'RpcBehaviorLoadBalancer'; + +class RpcBehaviorLoadBalancingConfig implements TypedLoadBalancingConfig { + constructor(private rpcBehavior: string) {} + getLoadBalancerName(): string { + return LB_POLICY_NAME; + } + toJsonObject(): object { + return { + [LB_POLICY_NAME]: { + 'rpcBehavior': this.rpcBehavior + } + }; + } + getRpcBehavior() { + return this.rpcBehavior; + } + static createFromJson(obj: any): RpcBehaviorLoadBalancingConfig { + if (!('rpcBehavior' in obj && typeof obj.rpcBehavior === 'string')) { + throw new Error(`${LB_POLICY_NAME} parsing error: expected string field rpcBehavior`); + } + return new RpcBehaviorLoadBalancingConfig(obj.rpcBehavior); + } +} + +class RpcBehaviorPicker implements Picker { + constructor(private wrappedPicker: Picker, private rpcBehavior: string) {} + pick(pickArgs: PickArgs): PickResult { + const wrappedPick = this.wrappedPicker.pick(pickArgs); + if (wrappedPick.pickResultType === PickResultType.COMPLETE) { + pickArgs.metadata.add('rpc-behavior', this.rpcBehavior); + } + return wrappedPick; + } +} + +const RPC_BEHAVIOR_CHILD_CONFIG = parseLoadBalancingConfig({round_robin: {}}); + +/** + * Load balancer implementation for Custom LB policy test + */ +class RpcBehaviorLoadBalancer implements LoadBalancer { + private child: ChildLoadBalancerHandler; + private latestConfig: RpcBehaviorLoadBalancingConfig | null = null; + constructor(channelControlHelper: ChannelControlHelper) { + const childChannelControlHelper = createChildChannelControlHelper(channelControlHelper, { + updateState: (connectivityState, picker) => { + if (connectivityState === grpc.connectivityState.READY && this.latestConfig) { + picker = new RpcBehaviorPicker(picker, this.latestConfig.getLoadBalancerName()); + } + channelControlHelper.updateState(connectivityState, picker); + } + }); + this.child = new ChildLoadBalancerHandler(childChannelControlHelper); + } + updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) { + return; + } + this.latestConfig = lbConfig; + this.child.updateAddressList(addressList, RPC_BEHAVIOR_CHILD_CONFIG, attributes); + } + exitIdle(): void { + this.child.exitIdle(); + } + resetBackoff(): void { + this.child.resetBackoff(); + } + destroy(): void { + this.child.destroy(); + } + getTypeName(): string { + return LB_POLICY_NAME; + } +} + +grpc.experimental.registerLoadBalancerType(LB_POLICY_NAME, RpcBehaviorLoadBalancer, RpcBehaviorLoadBalancingConfig); + const packageDefinition = protoLoader.loadSync('grpc/testing/test.proto', { keepCase: true, defaults: true, @@ -91,7 +181,7 @@ class CallSubscriber { } if (peerName in this.callsSucceededByPeer) { this.callsSucceededByPeer[peerName] += 1; - } else { + } else { this.callsSucceededByPeer[peerName] = 1; } this.callsSucceeded += 1; @@ -426,9 +516,9 @@ function main() { * channels do not share any subchannels. It does not have any * inherent function. */ console.log(`Interop client channel ${i} starting sending ${argv.qps} QPS to ${argv.server}`); - sendConstantQps(new loadedProto.grpc.testing.TestService(argv.server, grpc.credentials.createInsecure(), {'unique': i}), - argv.qps, - argv.fail_on_failed_rpcs === 'true', + sendConstantQps(new loadedProto.grpc.testing.TestService(argv.server, grpc.credentials.createInsecure(), {'unique': i}), + argv.qps, + argv.fail_on_failed_rpcs === 'true', callStatsTracker); } @@ -486,4 +576,4 @@ function main() { if (require.main === module) { main(); -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts index 757f9b93b..2a7d677b1 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts @@ -16,6 +16,7 @@ */ import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; +import { loadProtosWithOptionsSync } from "@grpc/proto-loader/build/src/util"; import { WeightedTargetRaw } from "./load-balancer-weighted-target"; import { isLocalitySubchannelAddress } from "./load-balancer-priority"; import { localityToName } from "./load-balancer-xds-cluster-resolver"; @@ -26,6 +27,11 @@ import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import SubchannelAddress = experimental.SubchannelAddress; import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; import registerLoadBalancerType = experimental.registerLoadBalancerType; +import { Any__Output } from "./generated/google/protobuf/Any"; +import { WrrLocality__Output } from "./generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality"; +import { TypedExtensionConfig__Output } from "./generated/envoy/config/core/v3/TypedExtensionConfig"; +import { LoadBalancingPolicy__Output } from "./generated/envoy/config/cluster/v3/LoadBalancingPolicy"; +import { registerLbPolicy } from "./lb-policy-registry"; const TRACER_NAME = 'xds_wrr_locality'; @@ -107,6 +113,54 @@ class XdsWrrLocalityLoadBalancer implements LoadBalancer { } } +const WRR_LOCALITY_TYPE_URL = 'envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality'; + +const resourceRoot = loadProtosWithOptionsSync([ + 'xds/type/v3/typed_struct.proto', + 'udpa/type/v1/typed_struct.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/protoc-gen-validate' + ], + } +); + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +function decodeWrrLocality(message: Any__Output): WrrLocality__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 WrrLocality__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 { + if (protoPolicy.typed_config?.type_url !== WRR_LOCALITY_TYPE_URL) { + throw new Error(`WRR Locality LB policy parsing error: unexpected type URL ${protoPolicy.typed_config?.type_url}`); + } + const wrrLocalityMessage = decodeWrrLocality(protoPolicy.typed_config); + if (!wrrLocalityMessage.endpoint_picking_policy) { + throw new Error('WRR Locality LB parsing error: no endpoint_picking_policy specified'); + } + return { + [TYPE_NAME]: { + child_policy: selectChildPolicy(wrrLocalityMessage.endpoint_picking_policy) + } + }; +} + export function setup() { registerLoadBalancerType(TYPE_NAME, XdsWrrLocalityLoadBalancer, XdsWrrLocalityLoadBalancingConfig); + registerLbPolicy(WRR_LOCALITY_TYPE_URL, convertToLoadBalancingPolicy); } diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 6e9718a7a..69dc518f8 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -114,8 +114,9 @@ export class LoadBalancingCall implements Call { throw new Error('doPick called before start'); } this.trace('Pick called'); + const finalMetadata = this.metadata.clone(); const pickResult = this.channel.doPick( - this.metadata, + finalMetadata, this.callConfig.pickInformation ); const subchannelString = pickResult.subchannel @@ -140,7 +141,6 @@ export class LoadBalancingCall implements Call { .generateMetadata({ service_url: this.serviceUrl }) .then( credsMetadata => { - const finalMetadata = this.metadata!.clone(); finalMetadata.merge(credsMetadata); if (finalMetadata.get('authorization').length > 1) { this.outputStatus( From a0e028f788c0845aa62dc1f657d80ba06541c333 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 22 Aug 2023 11:19:23 -0700 Subject: [PATCH 569/694] grpc-js-xds: Fix backoff timer reference when handling LRS stream messages --- packages/grpc-js-xds/src/xds-client.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 2ae9618b3..020f767b5 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -602,9 +602,9 @@ class ClusterLoadReportMap { * Get the indicated map entry if it exists, or create a new one if it does * not. Increments the refcount of that entry, so a call to this method * should correspond to a later call to unref - * @param clusterName - * @param edsServiceName - * @returns + * @param clusterName + * @param edsServiceName + * @returns */ getOrCreate(clusterName: string, edsServiceName: string): ClusterLoadReport { for (const statsObj of this.statsMap) { @@ -924,8 +924,8 @@ class XdsSingleServerClient { } onLrsStreamReceivedMessage() { - this.adsBackoff.stop(); - this.adsBackoff.reset(); + this.lrsBackoff.stop(); + this.lrsBackoff.reset(); } handleLrsStreamEnd() { From a417e9bc3bba72ef99e0528c2b9704ade1dcf420 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 22 Aug 2023 13:49:52 -0700 Subject: [PATCH 570/694] proto-loader: Bump version to 0.7.9 --- packages/proto-loader/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index 69038b74e..c53976be6 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.9-pre.1", + "version": "0.7.9", "author": "Google Inc.", "contributors": [ { From 73260353637c422a41c229d58116aaeb2c1dbcb8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 23 Aug 2023 09:37:47 -0700 Subject: [PATCH 571/694] Fix tests --- .../src/load-balancer-xds-cluster-impl.ts | 10 +- .../grpc-js-xds/test/test-confg-parsing.ts | 126 +++++++----------- 2 files changed, 50 insertions(+), 86 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index eb3df8c38..4050a3cf8 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -70,14 +70,10 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { cluster: this.cluster, drop_categories: this.dropCategories, child_policy: [this.childPolicy.toJsonObject()], - max_concurrent_requests: this.maxConcurrentRequests + max_concurrent_requests: this.maxConcurrentRequests, + eds_service_name: this.edsServiceName, + lrs_load_reporting_server: this.lrsLoadReportingServer, }; - if (this.edsServiceName !== undefined) { - jsonObj.eds_service_name = this.edsServiceName; - } - if (this.lrsLoadReportingServer !== undefined) { - jsonObj.lrs_load_reporting_server_name = this.lrsLoadReportingServer; - } return { [TYPE_NAME]: jsonObj }; diff --git a/packages/grpc-js-xds/test/test-confg-parsing.ts b/packages/grpc-js-xds/test/test-confg-parsing.ts index ba91bb2e1..740934ba1 100644 --- a/packages/grpc-js-xds/test/test-confg-parsing.ts +++ b/packages/grpc-js-xds/test/test-confg-parsing.ts @@ -69,33 +69,22 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { name: 'empty fields', input: { discovery_mechanisms: [], - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] } }, { name: 'missing discovery_mechanisms', input: { - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] }, error: /discovery_mechanisms/ }, { - name: 'missing locality_picking_policy', + name: 'missing xds_lb_policy', input: { - discovery_mechanisms: [], - endpoint_picking_policy: [] - }, - error: /locality_picking_policy/ - }, - { - name: 'missing endpoint_picking_policy', - input: { - discovery_mechanisms: [], - locality_picking_policy: [] + discovery_mechanisms: [] }, - error: /endpoint_picking_policy/ + error: /xds_lb_policy/ }, { name: 'discovery_mechanism: EDS', @@ -104,8 +93,7 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { cluster: 'abc', type: 'EDS' }], - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] }, output: { discovery_mechanisms: [{ @@ -113,8 +101,7 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { type: 'EDS', lrs_load_reporting_server: undefined }], - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] } }, { @@ -124,8 +111,7 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { cluster: 'abc', type: 'LOGICAL_DNS' }], - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] }, output: { discovery_mechanisms: [{ @@ -133,8 +119,7 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { type: 'LOGICAL_DNS', lrs_load_reporting_server: undefined }], - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] } }, { @@ -148,8 +133,7 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { dns_hostname: undefined, lrs_load_reporting_server: undefined }], - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] } }, { @@ -170,8 +154,7 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { server_features: ['test'] } }], - locality_picking_policy: [], - endpoint_picking_policy: [] + xds_lb_policy: [] } } ], @@ -180,12 +163,30 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { name: 'only required fields', input: { cluster: 'abc', + eds_service_name: 'def', drop_categories: [], + lrs_load_reporting_server: { + server_uri: 'localhost:12345', + channel_creds: [{ + type: 'google_default', + config: {} + }], + server_features: ['test'] + }, child_policy: [{round_robin: {}}] }, output: { cluster: 'abc', + eds_service_name: 'def', drop_categories: [], + lrs_load_reporting_server: { + server_uri: 'localhost:12345', + channel_creds: [{ + type: 'google_default', + config: {} + }], + server_features: ['test'] + }, child_policy: [{round_robin: {}}], max_concurrent_requests: 1024 } @@ -194,40 +195,8 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { name: 'undefined optional fields', input: { cluster: 'abc', - drop_categories: [], - child_policy: [{round_robin: {}}], - eds_service_name: undefined, - max_concurrent_requests: undefined - }, - output: { - cluster: 'abc', - drop_categories: [], - child_policy: [{round_robin: {}}], - max_concurrent_requests: 1024 - } - }, - { - name: 'populated optional fields', - input: { - cluster: 'abc', - drop_categories: [{ - category: 'test', - requests_per_million: 100 - }], - child_policy: [{round_robin: {}}], - eds_service_name: 'def', - max_concurrent_requests: 123 - }, - } - ], - lrs: [ - { - name: 'only required fields', - input: { - cluster_name: 'abc', eds_service_name: 'def', - locality: {}, - child_policy: [{round_robin: {}}], + drop_categories: [], lrs_load_reporting_server: { server_uri: 'localhost:12345', channel_creds: [{ @@ -235,17 +204,14 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { config: {} }], server_features: ['test'] - } + }, + child_policy: [{round_robin: {}}], + max_concurrent_requests: undefined }, output: { - cluster_name: 'abc', + cluster: 'abc', eds_service_name: 'def', - locality: { - region: '', - zone: '', - sub_zone: '' - }, - child_policy: [{round_robin: {}}], + drop_categories: [], lrs_load_reporting_server: { server_uri: 'localhost:12345', channel_creds: [{ @@ -253,20 +219,20 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { config: {} }], server_features: ['test'] - } + }, + child_policy: [{round_robin: {}}], + max_concurrent_requests: 1024 } }, { name: 'populated optional fields', input: { - cluster_name: 'abc', + cluster: 'abc', eds_service_name: 'def', - locality: { - region: 'a', - zone: 'b', - sub_zone: 'c' - }, - child_policy: [{round_robin: {}}], + drop_categories: [{ + category: 'test', + requests_per_million: 100 + }], lrs_load_reporting_server: { server_uri: 'localhost:12345', channel_creds: [{ @@ -274,8 +240,10 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { config: {} }], server_features: ['test'] - } - } + }, + child_policy: [{round_robin: {}}], + max_concurrent_requests: 123 + }, } ], priority: [ From 9ca8302725373249ef774da502e70d5c303ffab4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 23 Aug 2023 14:32:15 -0700 Subject: [PATCH 572/694] Add tests and fix bugs --- packages/grpc-js-xds/gulpfile.ts | 1 + .../grpc-js-xds/interop/xds-interop-client.ts | 2 +- packages/grpc-js-xds/src/index.ts | 4 + .../grpc-js-xds/src/lb-policy-registry.ts | 20 ++- .../src/lb-policy-registry/round-robin.ts | 2 +- .../src/lb-policy-registry/typed-struct.ts | 15 +- .../src/load-balancer-xds-wrr-locality.ts | 8 +- .../cluster-resource-type.ts | 11 +- packages/grpc-js-xds/test/framework.ts | 21 ++- .../test/test-custom-lb-policies.ts | 166 ++++++++++++++++++ packages/grpc-js-xds/test/xds-server.ts | 9 +- packages/grpc-js/src/experimental.ts | 3 +- 12 files changed, 239 insertions(+), 23 deletions(-) create mode 100644 packages/grpc-js-xds/test/test-custom-lb-policies.ts diff --git a/packages/grpc-js-xds/gulpfile.ts b/packages/grpc-js-xds/gulpfile.ts index f2e77b8bd..6f17a4020 100644 --- a/packages/grpc-js-xds/gulpfile.ts +++ b/packages/grpc-js-xds/gulpfile.ts @@ -62,6 +62,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'; return gulp.src(`${outDir}/test/**/*.js`) .pipe(mocha({reporter: 'mocha-jenkins-reporter', require: ['ts-node/register']})); diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index c28e9cc28..2893acad9 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -44,7 +44,7 @@ import parseLoadBalancingConfig = grpc.experimental.parseLoadBalancingConfig; grpc_xds.register(); -const LB_POLICY_NAME = 'RpcBehaviorLoadBalancer'; +const LB_POLICY_NAME = 'test.RpcBehaviorLoadBalancer'; class RpcBehaviorLoadBalancingConfig implements TypedLoadBalancingConfig { constructor(private rpcBehavior: string) {} diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 319719bde..95c26a20a 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -26,6 +26,8 @@ import * as xds_wrr_locality from './load-balancer-xds-wrr-locality'; import * as router_filter from './http-filter/router-filter'; 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'; /** * Register the "xds:" name scheme with the @grpc/grpc-js library. @@ -42,4 +44,6 @@ export function register() { router_filter.setup(); fault_injection_filter.setup(); csds.setup(); + round_robin_lb.setup(); + typed_struct_lb.setup(); } diff --git a/packages/grpc-js-xds/src/lb-policy-registry.ts b/packages/grpc-js-xds/src/lb-policy-registry.ts index 18d86655e..80a06323e 100644 --- a/packages/grpc-js-xds/src/lb-policy-registry.ts +++ b/packages/grpc-js-xds/src/lb-policy-registry.ts @@ -26,8 +26,13 @@ function trace(text: string) { const MAX_RECURSION_DEPTH = 16; +/** + * Parse a protoPolicy to a LoadBalancingConfig. A null return value indicates + * that parsing failed, but that it should not be treated as an error, and + * instead the next policy should be used. + */ interface ProtoLbPolicyConverter { - (protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig + (protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig | null; } interface RegisteredLbPolicy { @@ -41,20 +46,29 @@ export function registerLbPolicy(typeUrl: string, converter: ProtoLbPolicyConver } export function convertToLoadBalancingConfig(protoPolicy: LoadBalancingPolicy__Output, recursionDepth = 0): LoadBalancingConfig { + trace('Registry entries: [' + Object.keys(registry) + ']'); if (recursionDepth > MAX_RECURSION_DEPTH) { throw new Error(`convertToLoadBalancingConfig: Max recursion depth ${MAX_RECURSION_DEPTH} reached`); } for (const policyCandidate of protoPolicy.policies) { + trace('Attempting to parse config ' + JSON.stringify(policyCandidate)); const extensionConfig = policyCandidate.typed_extension_config; if (!extensionConfig?.typed_config) { continue; } const typeUrl = extensionConfig.typed_config.type_url; + trace('Attempting to parse config with type_url=' + typeUrl); + let parseResult: LoadBalancingConfig | null; if (typeUrl in registry) { try { - return registry[typeUrl].convertToLoadBalancingPolicy(extensionConfig, childPolicy => convertToLoadBalancingConfig(childPolicy, recursionDepth + 1)); + parseResult = registry[typeUrl].convertToLoadBalancingPolicy(extensionConfig, childPolicy => convertToLoadBalancingConfig(childPolicy, recursionDepth + 1)); } catch (e) { - throw new Error(`Error parsing ${typeUrl} LoadBalancingPolicy: ${(e as Error).message}`); + throw new Error(`Error parsing ${typeUrl} LoadBalancingPolicy named ${extensionConfig.name}: ${(e as Error).message}`); + } + if (parseResult) { + return parseResult; + } else { + continue; } } } diff --git a/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts b/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts index 9585bfde2..311c8c38c 100644 --- a/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts +++ b/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts @@ -20,7 +20,7 @@ import { LoadBalancingPolicy__Output } from "../generated/envoy/config/cluster/v import { TypedExtensionConfig__Output } from "../generated/envoy/config/core/v3/TypedExtensionConfig"; import { registerLbPolicy } from "../lb-policy-registry"; -const ROUND_ROBIN_TYPE_URL = 'envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin'; +const ROUND_ROBIN_TYPE_URL = 'type.googleapis.com/envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin'; function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig { if (protoPolicy.typed_config?.type_url !== ROUND_ROBIN_TYPE_URL) { diff --git a/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts index 70a6c02b3..6b50d9e83 100644 --- a/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts +++ b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts @@ -15,7 +15,7 @@ * */ -import { LoadBalancingConfig } from "@grpc/grpc-js"; +import { LoadBalancingConfig, experimental } 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 { registerLbPolicy } from "../lb-policy-registry"; @@ -25,16 +25,16 @@ import { Struct__Output } from "../generated/google/protobuf/Struct"; import { Value__Output } from "../generated/google/protobuf/Value"; import { TypedStruct__Output } from "../generated/xds/type/v3/TypedStruct"; -const XDS_TYPED_STRUCT_TYPE_URL = 'xds.type.v3.TypedStruct'; -const UDPA_TYPED_STRUCT_TYPE_URL = 'udpa.type.v1.TypedStruct'; +const XDS_TYPED_STRUCT_TYPE_URL = 'type.googleapis.com/xds.type.v3.TypedStruct'; +const UDPA_TYPED_STRUCT_TYPE_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct'; const resourceRoot = loadProtosWithOptionsSync([ 'xds/type/v3/typed_struct.proto', 'udpa/type/v1/typed_struct.proto'], { keepCase: true, includeDirs: [ - // Paths are relative to src/build - __dirname + '/../../deps/xds/', + // Paths are relative to src/build/lb-policy-registry + __dirname + '/../../../deps/xds/', ], } ); @@ -90,7 +90,7 @@ function flattenStruct(struct: Struct__Output): FlatStruct { return result; } -function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig { +function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, selectChildPolicy: (childPolicy: LoadBalancingPolicy__Output) => LoadBalancingConfig): LoadBalancingConfig | null { if (protoPolicy.typed_config?.type_url !== XDS_TYPED_STRUCT_TYPE_URL && protoPolicy.typed_config?.type_url !== UDPA_TYPED_STRUCT_TYPE_URL) { throw new Error(`Typed struct LB policy parsing error: unexpected type URL ${protoPolicy.typed_config?.type_url}`); } @@ -99,6 +99,9 @@ function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, throw new Error(`Typed struct LB parsing error: unexpected value ${typedStruct.value}`); } const policyName = typedStruct.type_url.substring(typedStruct.type_url.lastIndexOf('/') + 1); + if (!experimental.isLoadBalancerNameRegistered(policyName)) { + return null; + } return { [policyName]: flattenStruct(typedStruct.value) }; diff --git a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts index 2a7d677b1..5faf8b8e5 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts @@ -113,14 +113,14 @@ class XdsWrrLocalityLoadBalancer implements LoadBalancer { } } -const WRR_LOCALITY_TYPE_URL = 'envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality'; +const WRR_LOCALITY_TYPE_URL = 'type.googleapis.com/envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality'; const resourceRoot = loadProtosWithOptionsSync([ - 'xds/type/v3/typed_struct.proto', - 'udpa/type/v1/typed_struct.proto'], { + 'envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto'], { keepCase: true, includeDirs: [ // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', __dirname + '/../../deps/xds/', __dirname + '/../../deps/protoc-gen-validate' ], @@ -155,7 +155,7 @@ function convertToLoadBalancingPolicy(protoPolicy: TypedExtensionConfig__Output, } return { [TYPE_NAME]: { - child_policy: selectChildPolicy(wrrLocalityMessage.endpoint_picking_policy) + child_policy: [selectChildPolicy(wrrLocalityMessage.endpoint_picking_policy)] } }; } diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index 726a5d2d2..1e8ea41f8 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -17,7 +17,7 @@ import { CDS_TYPE_URL, CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from "../resources"; import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; -import { LoadBalancingConfig, experimental } from "@grpc/grpc-js"; +import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; import { XdsServerConfig } from "../xds-bootstrap"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { OutlierDetection__Output } from "../generated/envoy/config/cluster/v3/OutlierDetection"; @@ -32,6 +32,13 @@ import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; +const TRACER_NAME = 'xds_client'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + + export interface CdsUpdate { type: 'AGGREGATE' | 'EDS' | 'LOGICAL_DNS'; name: string; @@ -128,11 +135,13 @@ export class ClusterResourceType extends XdsResourceType { try { lbPolicyConfig = convertToLoadBalancingConfig(message.load_balancing_policy); } catch (e) { + trace('LB policy config parsing failed with error ' + e); return null; } try { parseLoadBalancingConfig(lbPolicyConfig); } catch (e) { + trace('LB policy config parsing failed with error ' + e); return null; } } else if (message.lb_policy === 'ROUND_ROBIN') { diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index f5e7bc1de..f89a4680f 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -28,6 +28,7 @@ import { CLUSTER_CONFIG_TYPE_URL, HTTP_CONNECTION_MANGER_TYPE_URL } from "../src import { LocalityLbEndpoints } from "../src/generated/envoy/config/endpoint/v3/LocalityLbEndpoints"; import { LbEndpoint } from "../src/generated/envoy/config/endpoint/v3/LbEndpoint"; import { ClusterConfig } from "../src/generated/envoy/extensions/clusters/aggregate/v3/ClusterConfig"; +import { Any } from "../src/generated/google/protobuf/Any"; interface Endpoint { locality: Locality; @@ -69,7 +70,7 @@ export interface FakeCluster { } export class FakeEdsCluster implements FakeCluster { - constructor(private clusterName: string, private endpointName: string, private endpoints: Endpoint[]) {} + constructor(private clusterName: string, private endpointName: string, private endpoints: Endpoint[], private loadBalancingPolicyOverride?: Any) {} getEndpointConfig(): ClusterLoadAssignment { return { @@ -79,11 +80,10 @@ export class FakeEdsCluster implements FakeCluster { } getClusterConfig(): Cluster { - return { + const result: Cluster = { name: this.clusterName, type: 'EDS', eds_cluster_config: {eds_config: {ads: {}}, service_name: this.endpointName}, - lb_policy: 'ROUND_ROBIN', lrs_server: {self: {}}, circuit_breakers: { thresholds: [ @@ -93,7 +93,22 @@ export class FakeEdsCluster implements FakeCluster { } ] } + }; + if (this.loadBalancingPolicyOverride) { + result.load_balancing_policy = { + policies: [ + { + typed_extension_config: { + 'name': 'test', + typed_config: this.loadBalancingPolicyOverride + } + } + ] + } + } else { + result.lb_policy = 'ROUND_ROBIN'; } + return result; } getAllClusterConfigs(): Cluster[] { diff --git a/packages/grpc-js-xds/test/test-custom-lb-policies.ts b/packages/grpc-js-xds/test/test-custom-lb-policies.ts new file mode 100644 index 000000000..7493a23f0 --- /dev/null +++ b/packages/grpc-js-xds/test/test-custom-lb-policies.ts @@ -0,0 +1,166 @@ +/* + * 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. + * + */ + +import { AnyExtension } from "@grpc/proto-loader"; +import { Any } from "../src/generated/google/protobuf/Any"; +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeEdsCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; +import * as assert from 'assert'; +import { WrrLocality } from "../src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality"; +import { TypedStruct } from "../src/generated/xds/type/v3/TypedStruct"; + +describe('Custom LB policies', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }); + it('Should handle round_robin', done => { + const lbPolicy: Any = { + '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin' + }; + 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)); + }); + it('Should handle xds_wrr_locality with round_robin child', done => { + const lbPolicy: WrrLocality & AnyExtension = { + '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality', + endpoint_picking_policy: { + policies: [ + { + typed_extension_config: { + name: 'child', + typed_config: { + '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin' + } + } + } + ] + } + }; + 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)); + }); + it('Should handle a typed_struct policy', done => { + const lbPolicy: TypedStruct & AnyExtension = { + '@type': 'type.googleapis.com/xds.type.v3.TypedStruct', + type_url: 'round_robin', + value: { + fields: {} + } + }; + 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)); + }); + it('Should handle xds_wrr_locality with an unrecognized first child', done => { + const invalidChildPolicy: TypedStruct & AnyExtension = { + '@type': 'type.googleapis.com/xds.type.v3.TypedStruct', + type_url: 'test.ThisLoadBalancerDoesNotExist', + value: { + fields: {} + } + } + const lbPolicy: WrrLocality & AnyExtension = { + '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality', + endpoint_picking_policy: { + policies: [ + { + typed_extension_config: { + name: 'child', + typed_config: invalidChildPolicy + } + }, + { + typed_extension_config: { + name: 'child', + typed_config: { + '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin' + } + } + } + ] + } + }; + 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 c9b829642..6f021c176 100644 --- a/packages/grpc-js-xds/test/xds-server.ts +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -36,12 +36,15 @@ const loadedProtos = loadPackageDefinition(loadSync( [ 'envoy/service/discovery/v3/ads.proto', 'envoy/service/load_stats/v3/lrs.proto', - 'envoy/config/listener/v3/listener.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', - 'envoy/extensions/clusters/aggregate/v3/cluster.proto' + 'envoy/extensions/clusters/aggregate/v3/cluster.proto', + 'envoy/extensions/load_balancing_policies/round_robin/v3/round_robin.proto', + 'envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto', + 'xds/type/v3/typed_struct.proto' ], { keepCase: true, @@ -319,7 +322,7 @@ export class XdsServer { callback(error, port); }); } - + shutdownServer() { this.server?.forceShutdown(); } diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index e4bd164ec..42fd577ca 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -16,7 +16,8 @@ export { createChildChannelControlHelper, registerLoadBalancerType, selectLbConfigFromList, - parseLoadBalancingConfig + parseLoadBalancingConfig, + isLoadBalancerNameRegistered } from './load-balancer'; export { SubchannelAddress, From fa26f4f70f80979661429d5a9e44d07ffe4b6890 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 23 Aug 2023 14:36:49 -0700 Subject: [PATCH 573/694] Add spec links --- packages/grpc-js-xds/src/lb-policy-registry.ts | 2 ++ packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts | 2 ++ packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts | 2 ++ packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts | 2 ++ 4 files changed, 8 insertions(+) diff --git a/packages/grpc-js-xds/src/lb-policy-registry.ts b/packages/grpc-js-xds/src/lb-policy-registry.ts index 80a06323e..f7aa74254 100644 --- a/packages/grpc-js-xds/src/lb-policy-registry.ts +++ b/packages/grpc-js-xds/src/lb-policy-registry.ts @@ -15,6 +15,8 @@ * */ +// https://github.com/grpc/proposal/blob/master/A52-xds-custom-lb-policies.md + import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; import { LoadBalancingPolicy__Output } from "./generated/envoy/config/cluster/v3/LoadBalancingPolicy"; import { TypedExtensionConfig__Output } from "./generated/envoy/config/core/v3/TypedExtensionConfig"; diff --git a/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts b/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts index 311c8c38c..b43b1c9e3 100644 --- a/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts +++ b/packages/grpc-js-xds/src/lb-policy-registry/round-robin.ts @@ -15,6 +15,8 @@ * */ +// https://github.com/grpc/proposal/blob/master/A52-xds-custom-lb-policies.md + 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"; diff --git a/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts index 6b50d9e83..9ae0b32ee 100644 --- a/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts +++ b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts @@ -15,6 +15,8 @@ * */ +// https://github.com/grpc/proposal/blob/master/A52-xds-custom-lb-policies.md + import { LoadBalancingConfig, experimental } from "@grpc/grpc-js"; import { LoadBalancingPolicy__Output } from "../generated/envoy/config/cluster/v3/LoadBalancingPolicy"; import { TypedExtensionConfig__Output } from "../generated/envoy/config/core/v3/TypedExtensionConfig"; diff --git a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts index 5faf8b8e5..8636937fd 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts @@ -15,6 +15,8 @@ * */ +// https://github.com/grpc/proposal/blob/master/A52-xds-custom-lb-policies.md + import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; import { loadProtosWithOptionsSync } from "@grpc/proto-loader/build/src/util"; import { WeightedTargetRaw } from "./load-balancer-weighted-target"; From c8b5d3119b87dc5e4b0d3468aac8fea98768bb37 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 23 Aug 2023 16:13:00 -0700 Subject: [PATCH 574/694] Fix missing proto file references --- packages/grpc-js-xds/package.json | 2 ++ packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 3c61b7383..b43304cea 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -59,6 +59,7 @@ "build/src/**/*.{js,d.ts,js.map}", "deps/envoy-api/envoy/admin/v3/**/*.proto", "deps/envoy-api/envoy/config/**/*.proto", + "deps/envoy-api/envoy/data/**/*.proto", "deps/envoy-api/envoy/service/**/*.proto", "deps/envoy-api/envoy/type/**/*.proto", "deps/envoy-api/envoy/annotations/**/*.proto", @@ -66,6 +67,7 @@ "deps/googleapis/google/api/**/*.proto", "deps/googleapis/google/protobuf/**/*.proto", "deps/googleapis/google/rpc/**/*.proto", + "deps/protoc-gen-validate/**/*.proto", "deps/xds/udpa/annotations/**/*.proto", "deps/xds/udpa/type/**/*.proto", "deps/xds/xds/annotations/**/*.proto", diff --git a/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts index 9ae0b32ee..b310782d7 100644 --- a/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts +++ b/packages/grpc-js-xds/src/lb-policy-registry/typed-struct.ts @@ -37,6 +37,7 @@ const resourceRoot = loadProtosWithOptionsSync([ includeDirs: [ // Paths are relative to src/build/lb-policy-registry __dirname + '/../../../deps/xds/', + __dirname + '/../../../deps/protoc-gen-validate' ], } ); From 91631ba11c8837c9dc01c889716da604ade3b104 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 24 Aug 2023 10:02:30 -0700 Subject: [PATCH 575/694] Update XdsClusterImpl LB policy to accept unset LRS config --- .../src/load-balancer-xds-cluster-impl.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index 4050a3cf8..926c7a699 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -79,7 +79,7 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { }; } - constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: TypedLoadBalancingConfig, private edsServiceName: string, private lrsLoadReportingServer: XdsServerConfig, maxConcurrentRequests?: number) { + constructor(private cluster: string, private dropCategories: DropCategory[], private childPolicy: TypedLoadBalancingConfig, private edsServiceName: string, private lrsLoadReportingServer?: XdsServerConfig, maxConcurrentRequests?: number) { this.maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS; } @@ -127,7 +127,11 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig { if (!childConfig) { throw new Error('xds_cluster_impl config child_policy parsing failed'); } - return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), childConfig, obj.eds_service_name, validateXdsServerConfig(obj.lrs_load_reporting_server), obj.max_concurrent_requests); + let lrsServer: XdsServerConfig | undefined = undefined; + if (obj.lrs_load_reporting_server) { + lrsServer = validateXdsServerConfig(obj.lrs_load_reporting_server) + } + return new XdsClusterImplLoadBalancingConfig(obj.cluster, obj.drop_categories.map(validateDropCategory), childConfig, obj.eds_service_name, lrsServer, obj.max_concurrent_requests); } } @@ -156,7 +160,7 @@ class CallCounterMap { const callCounterMap = new CallCounterMap(); class LocalitySubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { - constructor(child: SubchannelInterface, private statsObject: XdsClusterLocalityStats) { + constructor(child: SubchannelInterface, private statsObject: XdsClusterLocalityStats | null) { super(child); } @@ -210,12 +214,12 @@ class XdsClusterImplPicker implements Picker { subchannel: pickSubchannel?.getWrappedSubchannel() ?? null, onCallStarted: () => { originalPick.onCallStarted?.(); - pickSubchannel?.getStatsObject().addCallStarted(); + pickSubchannel?.getStatsObject()?.addCallStarted(); callCounterMap.startCall(this.callCounterMapKey); }, onCallEnded: status => { originalPick.onCallEnded?.(status); - pickSubchannel?.getStatsObject().addCallFinished(status !== Status.OK) + pickSubchannel?.getStatsObject()?.addCallFinished(status !== Status.OK) callCounterMap.endCall(this.callCounterMapKey); } }; @@ -253,12 +257,16 @@ class XdsClusterImplBalancer implements LoadBalancer { } const locality = (subchannelAddress as LocalitySubchannelAddress).locality ?? ''; const wrapperChild = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); - const statsObj = this.xdsClient.addClusterLocalityStats( - this.latestConfig.getLrsLoadReportingServer(), - this.latestConfig.getCluster(), - this.latestConfig.getEdsServiceName(), - locality - ); + const lrsServer = this.latestConfig.getLrsLoadReportingServer(); + let statsObj: XdsClusterLocalityStats | null = null; + if (lrsServer) { + statsObj = this.xdsClient.addClusterLocalityStats( + lrsServer, + this.latestConfig.getCluster(), + this.latestConfig.getEdsServiceName(), + locality + ); + } return new LocalitySubchannelWrapper(wrapperChild, statsObj); }, updateState: (connectivityState, originalPicker) => { From d1f0d9f80d333fb64b99921c66fe177d02a418ae Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 24 Aug 2023 13:38:56 -0700 Subject: [PATCH 576/694] grpc-js-xds: interop: add custom_lb test, reformat test list --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 729fb9293..a1e24ff1f 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -165,7 +165,16 @@ main() { # Run tests cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 - test_suites=("baseline_test" "api_listener_test" "change_backend_service_test" "failover_test" "remove_neg_test" "round_robin_test" "outlier_detection_test") + test_suites=( + "api_listener_test" + "baseline_test" + "change_backend_service_test" + "custom_lb_test" + "failover_test" + "outlier_detection_test" + "remove_neg_test" + "round_robin_test" + ) for test in "${test_suites[@]}"; do run_test $test || (( ++failed_tests )) done From 04ef12518dafd0ec0c22755fde3528be3886adb3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 25 Aug 2023 10:19:01 -0700 Subject: [PATCH 577/694] Add custom LB test from interop test, fix a bug --- .../grpc-js-xds/interop/xds-interop-client.ts | 2 +- packages/grpc-js-xds/test/backend.ts | 16 ++- .../test/test-custom-lb-policies.ts | 135 ++++++++++++++++++ 3 files changed, 150 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 2893acad9..f9034ed8e 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -92,7 +92,7 @@ class RpcBehaviorLoadBalancer implements LoadBalancer { const childChannelControlHelper = createChildChannelControlHelper(channelControlHelper, { updateState: (connectivityState, picker) => { if (connectivityState === grpc.connectivityState.READY && this.latestConfig) { - picker = new RpcBehaviorPicker(picker, this.latestConfig.getLoadBalancerName()); + picker = new RpcBehaviorPicker(picker, this.latestConfig.getRpcBehavior()); } channelControlHelper.updateState(connectivityState, picker); } diff --git a/packages/grpc-js-xds/test/backend.ts b/packages/grpc-js-xds/test/backend.ts index 3f20f1f39..01474284b 100644 --- a/packages/grpc-js-xds/test/backend.ts +++ b/packages/grpc-js-xds/test/backend.ts @@ -48,6 +48,18 @@ export class Backend { Echo(call: ServerUnaryCall, callback: sendUnaryData) { // call.request.params is currently ignored this.addCall(); + for (const behaviorEntry of call.metadata.get('rpc-behavior')) { + if (typeof behaviorEntry !== 'string') { + continue; + } + for (const behavior of behaviorEntry.split(',')) { + if (behavior.startsWith('error-code-')) { + const errorCode = Number(behavior.substring('error-code-'.length)); + callback({code: errorCode, details: 'rpc-behavior error code'}); + return; + } + } + } callback(null, {message: call.request.message}); } @@ -87,7 +99,7 @@ export class Backend { }); }); } - + getPort(): number { if (this.port === null) { throw new Error('Port not set. Backend not yet started.'); @@ -125,4 +137,4 @@ export class Backend { }); }); } -} \ No newline at end of file +} 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 7493a23f0..16cca3a8a 100644 --- a/packages/grpc-js-xds/test/test-custom-lb-policies.ts +++ b/packages/grpc-js-xds/test/test-custom-lb-policies.ts @@ -24,6 +24,98 @@ import { XdsServer } from "./xds-server"; import * as assert from 'assert'; import { WrrLocality } from "../src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality"; import { TypedStruct } from "../src/generated/xds/type/v3/TypedStruct"; +import { connectivityState, experimental, logVerbosity } from "@grpc/grpc-js"; + +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; +import SubchannelAddress = experimental.SubchannelAddress; +import Picker = experimental.Picker; +import PickArgs = experimental.PickArgs; +import PickResult = experimental.PickResult; +import PickResultType = experimental.PickResultType; +import createChildChannelControlHelper = experimental.createChildChannelControlHelper; +import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; +import registerLoadBalancerType = experimental.registerLoadBalancerType; + +const LB_POLICY_NAME = 'test.RpcBehaviorLoadBalancer'; + +class RpcBehaviorLoadBalancingConfig implements TypedLoadBalancingConfig { + constructor(private rpcBehavior: string) {} + getLoadBalancerName(): string { + return LB_POLICY_NAME; + } + toJsonObject(): object { + return { + [LB_POLICY_NAME]: { + 'rpcBehavior': this.rpcBehavior + } + }; + } + getRpcBehavior() { + return this.rpcBehavior; + } + static createFromJson(obj: any): RpcBehaviorLoadBalancingConfig { + if (!('rpcBehavior' in obj && typeof obj.rpcBehavior === 'string')) { + throw new Error(`${LB_POLICY_NAME} parsing error: expected string field rpcBehavior`); + } + return new RpcBehaviorLoadBalancingConfig(obj.rpcBehavior); + } +} + +class RpcBehaviorPicker implements Picker { + constructor(private wrappedPicker: Picker, private rpcBehavior: string) {} + pick(pickArgs: PickArgs): PickResult { + const wrappedPick = this.wrappedPicker.pick(pickArgs); + if (wrappedPick.pickResultType === PickResultType.COMPLETE) { + pickArgs.metadata.add('rpc-behavior', this.rpcBehavior); + } + return wrappedPick; + } +} + +const RPC_BEHAVIOR_CHILD_CONFIG = parseLoadBalancingConfig({round_robin: {}}); + +/** + * Load balancer implementation for Custom LB policy test + */ +class RpcBehaviorLoadBalancer implements LoadBalancer { + private child: ChildLoadBalancerHandler; + private latestConfig: RpcBehaviorLoadBalancingConfig | null = null; + constructor(channelControlHelper: ChannelControlHelper) { + const childChannelControlHelper = createChildChannelControlHelper(channelControlHelper, { + updateState: (state, picker) => { + if (state === connectivityState.READY && this.latestConfig) { + picker = new RpcBehaviorPicker(picker, this.latestConfig.getRpcBehavior()); + } + channelControlHelper.updateState(state, picker); + } + }); + this.child = new ChildLoadBalancerHandler(childChannelControlHelper); + } + updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) { + return; + } + this.latestConfig = lbConfig; + this.child.updateAddressList(addressList, RPC_BEHAVIOR_CHILD_CONFIG, attributes); + } + exitIdle(): void { + this.child.exitIdle(); + } + resetBackoff(): void { + this.child.resetBackoff(); + } + destroy(): void { + this.child.destroy(); + } + getTypeName(): string { + return LB_POLICY_NAME; + } +} + +registerLoadBalancerType(LB_POLICY_NAME, RpcBehaviorLoadBalancer, RpcBehaviorLoadBalancingConfig); describe('Custom LB policies', () => { let xdsServer: XdsServer; @@ -163,4 +255,47 @@ describe('Custom LB policies', () => { client.sendOneCall(done); }, reason => done(reason)); }); + it('Should handle a custom LB policy', done => { + const childPolicy: TypedStruct & AnyExtension = { + '@type': 'type.googleapis.com/xds.type.v3.TypedStruct', + type_url: 'test.RpcBehaviorLoadBalancer', + value: { + fields: { + rpcBehavior: {stringValue: 'error-code-15'} + } + } + }; + const lbPolicy: WrrLocality & AnyExtension = { + '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.wrr_locality.v3.WrrLocality', + endpoint_picking_policy: { + policies: [ + { + typed_extension_config: { + name: 'child', + typed_config: childPolicy + } + } + ] + } + }; + 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(error => { + assert.strictEqual(error?.code, 15); + done(); + }); + }, reason => done(reason)); + }) }); From 613c9144d9c4ad691dcb2923b234bd0247b61d8e Mon Sep 17 00:00:00 2001 From: gusumuzhe Date: Tue, 29 Aug 2023 17:39:38 +0800 Subject: [PATCH 578/694] fix: pick first load balancer call doPick infinite --- packages/grpc-js/src/load-balancer-pick-first.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 8635482ce..1d9233d9c 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -315,7 +315,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { } private pickSubchannel(subchannel: SubchannelInterface) { - if (subchannel === this.currentPick) { + if (this.currentPick && subchannel.realSubchannelEquals(this.currentPick)) { return; } trace('Pick subchannel with address ' + subchannel.getAddress()); From 49b7c6af34cb7454ed0d4a85c3654db97a3cb6c4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 30 Aug 2023 14:46:08 -0700 Subject: [PATCH 579/694] grpc-js: Make pick_first the universal leaf policy, switch to endpoint lists --- packages/grpc-js/src/experimental.ts | 6 +- .../src/load-balancer-child-handler.ts | 8 +- .../src/load-balancer-outlier-detection.ts | 287 +++++++++++------- .../grpc-js/src/load-balancer-pick-first.ts | 148 +++++++-- .../grpc-js/src/load-balancer-round-robin.ts | 145 ++++----- packages/grpc-js/src/load-balancer.ts | 20 +- packages/grpc-js/src/picker.ts | 17 +- packages/grpc-js/src/resolver-dns.ts | 51 +--- packages/grpc-js/src/resolver-ip.ts | 10 +- packages/grpc-js/src/resolver-uds.ts | 8 +- packages/grpc-js/src/resolver.ts | 4 +- .../grpc-js/src/resolving-load-balancer.ts | 8 +- packages/grpc-js/src/server.ts | 5 +- packages/grpc-js/src/subchannel-address.ts | 36 +++ packages/grpc-js/src/subchannel-interface.ts | 42 ++- packages/grpc-js/src/subchannel.ts | 12 + packages/grpc-js/test/common.ts | 10 +- packages/grpc-js/test/test-pick-first.ts | 115 ++++--- packages/grpc-js/test/test-resolver.ts | 240 +++++---------- 19 files changed, 668 insertions(+), 504 deletions(-) diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 42fd577ca..0c7bc75e1 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -17,11 +17,15 @@ export { registerLoadBalancerType, selectLbConfigFromList, parseLoadBalancingConfig, - isLoadBalancerNameRegistered + isLoadBalancerNameRegistered, } from './load-balancer'; +export { LeafLoadBalancer } from './load-balancer-pick-first'; export { SubchannelAddress, subchannelAddressToString, + Endpoint, + endpointToString, + endpointHasAddress, } from './subchannel-address'; export { ChildLoadBalancerHandler } from './load-balancer-child-handler'; export { diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index b23f19263..11bfac211 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -21,7 +21,7 @@ import { TypedLoadBalancingConfig, createLoadBalancer, } from './load-balancer'; -import { SubchannelAddress } from './subchannel-address'; +import { Endpoint, SubchannelAddress } from './subchannel-address'; import { ChannelOptions } from './channel-options'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; @@ -95,12 +95,12 @@ export class ChildLoadBalancerHandler implements LoadBalancer { /** * Prerequisites: lbConfig !== null and lbConfig.name is registered - * @param addressList + * @param endpointList * @param lbConfig * @param attributes */ updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { @@ -131,7 +131,7 @@ export class ChildLoadBalancerHandler implements LoadBalancer { } } this.latestConfig = lbConfig; - childToUpdate.updateAddressList(addressList, lbConfig, attributes); + childToUpdate.updateAddressList(endpointList, lbConfig, attributes); } exitIdle(): void { if (this.currentChild) { diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 63a1d8f1d..b0c648efd 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -32,12 +32,14 @@ import { import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; import { PickArgs, Picker, PickResult, PickResultType } from './picker'; import { + Endpoint, SubchannelAddress, - subchannelAddressToString, + endpointHasAddress, + endpointToString, + subchannelAddressEqual, } from './subchannel-address'; import { BaseSubchannelWrapper, - ConnectivityStateListener, SubchannelInterface, } from './subchannel-interface'; import * as logging from './logging'; @@ -107,7 +109,11 @@ function validateFieldType( expectedType: TypeofValues, objectName?: string ) { - if (fieldName in obj && obj[fieldName] !== undefined && typeof obj[fieldName] !== expectedType) { + if ( + fieldName in obj && + obj[fieldName] !== undefined && + typeof obj[fieldName] !== expectedType + ) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; throw new Error( `outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[ @@ -149,7 +155,11 @@ function validatePositiveDuration( function validatePercentage(obj: any, fieldName: string, objectName?: string) { const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; validateFieldType(obj, fieldName, 'number', objectName); - if (fieldName in obj && obj[fieldName] !== undefined && !(obj[fieldName] >= 0 && obj[fieldName] <= 100)) { + if ( + fieldName in obj && + obj[fieldName] !== undefined && + !(obj[fieldName] >= 0 && obj[fieldName] <= 100) + ) { throw new Error( `outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)` ); @@ -175,9 +185,7 @@ export class OutlierDetectionLoadBalancingConfig failurePercentageEjection: Partial | null, private readonly childPolicy: TypedLoadBalancingConfig ) { - if ( - childPolicy.getLoadBalancerName() === 'pick_first' - ) { + if (childPolicy.getLoadBalancerName() === 'pick_first') { throw new Error( 'outlier_detection LB policy cannot have a pick_first child policy' ); @@ -207,9 +215,10 @@ export class OutlierDetectionLoadBalancingConfig max_ejection_time: msToDuration(this.maxEjectionTimeMs), max_ejection_percent: this.maxEjectionPercent, success_rate_ejection: this.successRateEjection ?? undefined, - failure_percentage_ejection: this.failurePercentageEjection ?? undefined, - child_policy: [this.childPolicy.toJsonObject()] - } + failure_percentage_ejection: + this.failurePercentageEjection ?? undefined, + child_policy: [this.childPolicy.toJsonObject()], + }, }; } @@ -240,7 +249,10 @@ export class OutlierDetectionLoadBalancingConfig validatePositiveDuration(obj, 'base_ejection_time'); validatePositiveDuration(obj, 'max_ejection_time'); validatePercentage(obj, 'max_ejection_percent'); - if ('success_rate_ejection' in obj && obj.success_rate_ejection !== undefined) { + if ( + 'success_rate_ejection' in obj && + obj.success_rate_ejection !== undefined + ) { if (typeof obj.success_rate_ejection !== 'object') { throw new Error( 'outlier detection config success_rate_ejection must be an object' @@ -270,7 +282,10 @@ export class OutlierDetectionLoadBalancingConfig 'success_rate_ejection' ); } - if ('failure_percentage_ejection' in obj && obj.failure_percentage_ejection !== undefined) { + if ( + 'failure_percentage_ejection' in obj && + obj.failure_percentage_ejection !== undefined + ) { if (typeof obj.failure_percentage_ejection !== 'object') { throw new Error( 'outlier detection config failure_percentage_ejection must be an object' @@ -305,7 +320,9 @@ export class OutlierDetectionLoadBalancingConfig } const childPolicy = selectLbConfigFromList(obj.child_policy); if (!childPolicy) { - throw new Error('outlier detection config child_policy: no valid recognized policy found'); + throw new Error( + 'outlier detection config child_policy: no valid recognized policy found' + ); } return new OutlierDetectionLoadBalancingConfig( @@ -324,55 +341,12 @@ class OutlierDetectionSubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface { - private childSubchannelState: ConnectivityState; - private stateListeners: ConnectivityStateListener[] = []; - private ejected = false; private refCount = 0; constructor( childSubchannel: SubchannelInterface, private mapEntry?: MapEntry ) { super(childSubchannel); - this.childSubchannelState = childSubchannel.getConnectivityState(); - childSubchannel.addConnectivityStateListener( - (subchannel, previousState, newState, keepaliveTime) => { - this.childSubchannelState = newState; - if (!this.ejected) { - for (const listener of this.stateListeners) { - listener(this, previousState, newState, keepaliveTime); - } - } - } - ); - } - - getConnectivityState(): ConnectivityState { - if (this.ejected) { - return ConnectivityState.TRANSIENT_FAILURE; - } else { - return this.childSubchannelState; - } - } - - /** - * Add a listener function to be called whenever the wrapper's - * connectivity state changes. - * @param listener - */ - addConnectivityStateListener(listener: ConnectivityStateListener) { - this.stateListeners.push(listener); - } - - /** - * Remove a listener previously added with `addConnectivityStateListener` - * @param listener A reference to a function previously passed to - * `addConnectivityStateListener` - */ - removeConnectivityStateListener(listener: ConnectivityStateListener) { - const listenerIndex = this.stateListeners.indexOf(listener); - if (listenerIndex > -1) { - this.stateListeners.splice(listenerIndex, 1); - } } ref() { @@ -394,27 +368,11 @@ class OutlierDetectionSubchannelWrapper } eject() { - this.ejected = true; - for (const listener of this.stateListeners) { - listener( - this, - this.childSubchannelState, - ConnectivityState.TRANSIENT_FAILURE, - -1 - ); - } + this.setHealthy(false); } uneject() { - this.ejected = false; - for (const listener of this.stateListeners) { - listener( - this, - ConnectivityState.TRANSIENT_FAILURE, - this.childSubchannelState, - -1 - ); - } + this.setHealthy(true); } getMapEntry(): MapEntry | undefined { @@ -459,13 +417,6 @@ class CallCounter { } } -interface MapEntry { - counter: CallCounter; - currentEjectionTimestamp: Date | null; - ejectionTimeMultiplier: number; - subchannelWrappers: OutlierDetectionSubchannelWrapper[]; -} - class OutlierDetectionPicker implements Picker { constructor(private wrappedPicker: Picker, private countCalls: boolean) {} pick(pickArgs: PickArgs): PickResult { @@ -503,9 +454,133 @@ class OutlierDetectionPicker implements Picker { } } +interface MapEntry { + counter: CallCounter; + currentEjectionTimestamp: Date | null; + ejectionTimeMultiplier: number; + subchannelWrappers: OutlierDetectionSubchannelWrapper[]; +} + +interface EndpointMapEntry { + key: Endpoint; + value: MapEntry; +} + +function endpointEqualUnordered( + endpoint1: Endpoint, + endpoint2: Endpoint +): boolean { + if (endpoint1.addresses.length !== endpoint2.addresses.length) { + return false; + } + for (const address1 of endpoint1.addresses) { + let matchFound = false; + for (const address2 of endpoint2.addresses) { + if (subchannelAddressEqual(address1, address2)) { + matchFound = true; + break; + } + } + if (!matchFound) { + return false; + } + } + return true; +} + +class EndpointMap { + private map: Set = new Set(); + + get size() { + return this.map.size; + } + + getForSubchannelAddress(address: SubchannelAddress): MapEntry | undefined { + for (const entry of this.map) { + if (endpointHasAddress(entry.key, address)) { + return entry.value; + } + } + return undefined; + } + + /** + * Delete any entries in this map with keys that are not in endpoints + * @param endpoints + */ + deleteMissing(endpoints: Endpoint[]) { + for (const entry of this.map) { + let foundEntry = false; + for (const endpoint of endpoints) { + if (endpointEqualUnordered(endpoint, entry.key)) { + foundEntry = true; + } + } + if (!foundEntry) { + this.map.delete(entry); + } + } + } + + get(endpoint: Endpoint): MapEntry | undefined { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + return entry.value; + } + } + return undefined; + } + + set(endpoint: Endpoint, mapEntry: MapEntry) { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + entry.value = mapEntry; + return; + } + } + this.map.add({ key: endpoint, value: mapEntry }); + } + + delete(endpoint: Endpoint) { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + this.map.delete(entry); + return; + } + } + } + + has(endpoint: Endpoint): boolean { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + return true; + } + } + return false; + } + + *keys(): IterableIterator { + for (const entry of this.map) { + yield entry.key; + } + } + + *values(): IterableIterator { + for (const entry of this.map) { + yield entry.value; + } + } + + *entries(): IterableIterator<[Endpoint, MapEntry]> { + for (const entry of this.map) { + yield [entry.key, entry.value]; + } + } +} + export class OutlierDetectionLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; - private addressMap: Map = new Map(); + private entryMap = new EndpointMap(); private latestConfig: OutlierDetectionLoadBalancingConfig | null = null; private ejectionTimer: NodeJS.Timeout; private timerStartTime: Date | null = null; @@ -521,9 +596,8 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { subchannelAddress, subchannelArgs ); - const mapEntry = this.addressMap.get( - subchannelAddressToString(subchannelAddress) - ); + const mapEntry = + this.entryMap.getForSubchannelAddress(subchannelAddress); const subchannelWrapper = new OutlierDetectionSubchannelWrapper( originalSubchannel, mapEntry @@ -561,12 +635,12 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private getCurrentEjectionPercent() { let ejectionCount = 0; - for (const mapEntry of this.addressMap.values()) { + for (const mapEntry of this.entryMap.values()) { if (mapEntry.currentEjectionTimestamp !== null) { ejectionCount += 1; } } - return (ejectionCount * 100) / this.addressMap.size; + return (ejectionCount * 100) / this.entryMap.size; } private runSuccessRateCheck(ejectionTimestamp: Date) { @@ -582,12 +656,12 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { const targetRequestVolume = successRateConfig.request_volume; let addresesWithTargetVolume = 0; const successRates: number[] = []; - for (const [address, mapEntry] of this.addressMap) { + for (const [endpoint, mapEntry] of this.entryMap.entries()) { const successes = mapEntry.counter.getLastSuccesses(); const failures = mapEntry.counter.getLastFailures(); trace( 'Stats for ' + - address + + endpointToString(endpoint) + ': successes=' + successes + ' failures=' + @@ -631,7 +705,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { ); // Step 3 - for (const [address, mapEntry] of this.addressMap.entries()) { + for (const [address, mapEntry] of this.entryMap.entries()) { // Step 3.i if ( this.getCurrentEjectionPercent() >= @@ -683,7 +757,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { ); // Step 1 let addressesWithTargetVolume = 0; - for (const mapEntry of this.addressMap.values()) { + for (const mapEntry of this.entryMap.values()) { const successes = mapEntry.counter.getLastSuccesses(); const failures = mapEntry.counter.getLastFailures(); if (successes + failures >= failurePercentageConfig.request_volume) { @@ -695,7 +769,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } // Step 2 - for (const [address, mapEntry] of this.addressMap.entries()) { + for (const [address, mapEntry] of this.entryMap.entries()) { // Step 2.i if ( this.getCurrentEjectionPercent() >= @@ -746,7 +820,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } private switchAllBuckets() { - for (const mapEntry of this.addressMap.values()) { + for (const mapEntry of this.entryMap.values()) { mapEntry.counter.switchBuckets(); } } @@ -771,7 +845,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { this.runSuccessRateCheck(ejectionTimestamp); this.runFailurePercentageCheck(ejectionTimestamp); - for (const [address, mapEntry] of this.addressMap.entries()) { + for (const [address, mapEntry] of this.entryMap.entries()) { if (mapEntry.currentEjectionTimestamp === null) { if (mapEntry.ejectionTimeMultiplier > 0) { mapEntry.ejectionTimeMultiplier -= 1; @@ -798,21 +872,17 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { } updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) { return; } - const subchannelAddresses = new Set(); - for (const address of addressList) { - subchannelAddresses.add(subchannelAddressToString(address)); - } - for (const address of subchannelAddresses) { - if (!this.addressMap.has(address)) { - trace('Adding map entry for ' + address); - this.addressMap.set(address, { + for (const endpoint of endpointList) { + if (!this.entryMap.has(endpoint)) { + trace('Adding map entry for ' + endpointToString(endpoint)); + this.entryMap.set(endpoint, { counter: new CallCounter(), currentEjectionTimestamp: null, ejectionTimeMultiplier: 0, @@ -820,14 +890,9 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { }); } } - for (const key of this.addressMap.keys()) { - if (!subchannelAddresses.has(key)) { - trace('Removing map entry for ' + key); - this.addressMap.delete(key); - } - } + this.entryMap.deleteMissing(endpointList); const childPolicy = lbConfig.getChildPolicy(); - this.childBalancer.updateAddressList(addressList, childPolicy, attributes); + this.childBalancer.updateAddressList(endpointList, childPolicy, attributes); if ( lbConfig.getSuccessRateEjectionConfig() || @@ -850,7 +915,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { trace('Counting disabled. Cancelling timer.'); this.timerStartTime = null; clearTimeout(this.ejectionTimer); - for (const mapEntry of this.addressMap.values()) { + for (const mapEntry of this.entryMap.values()) { this.uneject(mapEntry); mapEntry.ejectionTimeMultiplier = 0; } diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 8635482ce..7d3948216 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -21,6 +21,7 @@ import { TypedLoadBalancingConfig, registerDefaultLoadBalancerType, registerLoadBalancerType, + createChildChannelControlHelper, } from './load-balancer'; import { ConnectivityState } from './connectivity-state'; import { @@ -31,13 +32,16 @@ import { PickResultType, UnavailablePicker, } from './picker'; -import { SubchannelAddress } from './subchannel-address'; +import { Endpoint, SubchannelAddress } from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; import { SubchannelInterface, ConnectivityStateListener, + HealthListener, } from './subchannel-interface'; +import { isTcpSubchannelAddress } from './subchannel-address'; +import { isIPv6 } from 'net'; const TRACER_NAME = 'pick_first'; @@ -125,6 +129,39 @@ export function shuffled(list: T[]): T[] { return result; } +/** + * Interleave addresses in addressList by family in accordance with RFC-8304 section 4 + * @param addressList + * @returns + */ +function interleaveAddressFamilies( + addressList: SubchannelAddress[] +): SubchannelAddress[] { + const result: SubchannelAddress[] = []; + const ipv6Addresses: SubchannelAddress[] = []; + const ipv4Addresses: SubchannelAddress[] = []; + const ipv6First = + isTcpSubchannelAddress(addressList[0]) && isIPv6(addressList[0].host); + for (const address of addressList) { + if (isTcpSubchannelAddress(address) && isIPv6(address.host)) { + ipv6Addresses.push(address); + } else { + ipv4Addresses.push(address); + } + } + const firstList = ipv6First ? ipv6Addresses : ipv4Addresses; + const secondList = ipv6First ? ipv4Addresses : ipv6Addresses; + for (let i = 0; i < Math.max(firstList.length, secondList.length); i++) { + if (i < firstList.length) { + result.push(firstList[i]); + } + if (i < secondList.length) { + result.push(secondList[i]); + } + } + return result; +} + export class PickFirstLoadBalancer implements LoadBalancer { /** * The list of subchannels this load balancer is currently attempting to @@ -157,6 +194,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { ) => { this.onSubchannelStateUpdate(subchannel, previousState, newState); }; + + private pickedSubchannelHealthListener: HealthListener = () => + this.calculateAndReportNewState(); /** * Timer reference for the timer tracking when to start */ @@ -179,7 +219,10 @@ export class PickFirstLoadBalancer implements LoadBalancer { * @param channelControlHelper `ChannelControlHelper` instance provided by * this load balancer's owner. */ - constructor(private readonly channelControlHelper: ChannelControlHelper) { + constructor( + private readonly channelControlHelper: ChannelControlHelper, + private reportHealthStatus = false + ) { this.connectionDelayTimeout = setTimeout(() => {}, 0); clearTimeout(this.connectionDelayTimeout); } @@ -190,10 +233,19 @@ export class PickFirstLoadBalancer implements LoadBalancer { private calculateAndReportNewState() { if (this.currentPick) { - this.updateState( - ConnectivityState.READY, - new PickFirstPicker(this.currentPick) - ); + if (this.reportHealthStatus && !this.currentPick.isHealthy()) { + this.updateState( + ConnectivityState.TRANSIENT_FAILURE, + new UnavailablePicker({ + details: `Picked subchannel ${this.currentPick.getAddress()} is unhealthy`, + }) + ); + } else { + this.updateState( + ConnectivityState.READY, + new PickFirstPicker(this.currentPick) + ); + } } else if (this.children.length === 0) { this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); } else { @@ -235,6 +287,11 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.channelControlHelper.removeChannelzChild( currentPick.getChannelzRef() ); + if (this.reportHealthStatus) { + currentPick.removeHealthStateWatcher( + this.pickedSubchannelHealthListener + ); + } } } @@ -306,7 +363,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.children[subchannelIndex].subchannel.getAddress() ); process.nextTick(() => { - this.children[subchannelIndex].subchannel.startConnecting(); + this.children[subchannelIndex]?.subchannel.startConnecting(); }); } this.connectionDelayTimeout = setTimeout(() => { @@ -320,17 +377,12 @@ export class PickFirstLoadBalancer implements LoadBalancer { } trace('Pick subchannel with address ' + subchannel.getAddress()); this.stickyTransientFailureMode = false; - if (this.currentPick !== null) { - this.currentPick.unref(); - this.channelControlHelper.removeChannelzChild( - this.currentPick.getChannelzRef() - ); - this.currentPick.removeConnectivityStateListener( - this.subchannelStateListener - ); - } + this.removeCurrentPick(); this.currentPick = subchannel; subchannel.ref(); + if (this.reportHealthStatus) { + subchannel.addHealthStateWatcher(this.pickedSubchannelHealthListener); + } this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); this.resetSubchannelList(); clearTimeout(this.connectionDelayTimeout); @@ -373,7 +425,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { } updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig ): void { if (!(lbConfig instanceof PickFirstLoadBalancingConfig)) { @@ -383,8 +435,15 @@ export class PickFirstLoadBalancer implements LoadBalancer { * previous update, to minimize churn. Now the DNS resolver is * rate-limited, so that is less of a concern. */ if (lbConfig.getShuffleAddressList()) { - addressList = shuffled(addressList); + endpointList = shuffled(endpointList); } + const rawAddressList = ([] as SubchannelAddress[]).concat( + ...endpointList.map(endpoint => endpoint.addresses) + ); + if (rawAddressList.length === 0) { + throw new Error('No addresses in endpoint list passed to pick_first'); + } + const addressList = interleaveAddressFamilies(rawAddressList); const newChildrenList = addressList.map(address => ({ subchannel: this.channelControlHelper.createSubchannel(address, {}), hasReportedTransientFailure: false, @@ -438,6 +497,59 @@ export class PickFirstLoadBalancer implements LoadBalancer { } } +const LEAF_CONFIG = new PickFirstLoadBalancingConfig(false); + +/** + * This class handles the leaf load balancing operations for a single endpoint. + * It is a thin wrapper around a PickFirstLoadBalancer with a different API + * that more closely reflects how it will be used as a leaf balancer. + */ +export class LeafLoadBalancer { + private pickFirstBalancer: PickFirstLoadBalancer; + private latestState: ConnectivityState = ConnectivityState.IDLE; + private latestPicker: Picker; + constructor( + private endpoint: Endpoint, + channelControlHelper: ChannelControlHelper + ) { + const childChannelControlHelper = createChildChannelControlHelper( + channelControlHelper, + { + updateState: (connectivityState, picker) => { + this.latestState = connectivityState; + this.latestPicker = picker; + channelControlHelper.updateState(connectivityState, picker); + }, + } + ); + this.pickFirstBalancer = new PickFirstLoadBalancer( + childChannelControlHelper, + /* reportHealthStatus= */ true + ); + this.latestPicker = new QueuePicker(this.pickFirstBalancer); + } + + startConnecting() { + this.pickFirstBalancer.updateAddressList([this.endpoint], LEAF_CONFIG); + } + + getConnectivityState() { + return this.latestState; + } + + getPicker() { + return this.latestPicker; + } + + getEndpoint() { + return this.endpoint; + } + + destroy() { + this.pickFirstBalancer.destroy(); + } +} + export function setup(): void { registerLoadBalancerType( TYPE_NAME, diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index a611cfd64..986181a84 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -20,26 +20,24 @@ import { ChannelControlHelper, TypedLoadBalancingConfig, registerLoadBalancerType, + createChildChannelControlHelper, } from './load-balancer'; import { ConnectivityState } from './connectivity-state'; import { QueuePicker, Picker, PickArgs, - CompletePickResult, - PickResultType, UnavailablePicker, + PickResult, } from './picker'; -import { - SubchannelAddress, - subchannelAddressToString, -} from './subchannel-address'; import * as logging from './logging'; import { LogVerbosity } from './constants'; import { - ConnectivityStateListener, - SubchannelInterface, -} from './subchannel-interface'; + Endpoint, + endpointEqual, + endpointToString, +} from './subchannel-address'; +import { LeafLoadBalancer } from './load-balancer-pick-first'; const TRACER_NAME = 'round_robin'; @@ -70,20 +68,14 @@ class RoundRobinLoadBalancingConfig implements TypedLoadBalancingConfig { class RoundRobinPicker implements Picker { constructor( - private readonly subchannelList: SubchannelInterface[], + private readonly children: { endpoint: Endpoint; picker: Picker }[], private nextIndex = 0 ) {} - pick(pickArgs: PickArgs): CompletePickResult { - const pickedSubchannel = this.subchannelList[this.nextIndex]; - this.nextIndex = (this.nextIndex + 1) % this.subchannelList.length; - return { - pickResultType: PickResultType.COMPLETE, - subchannel: pickedSubchannel, - status: null, - onCallStarted: null, - onCallEnded: null, - }; + pick(pickArgs: PickArgs): PickResult { + const childPicker = this.children[this.nextIndex].picker; + this.nextIndex = (this.nextIndex + 1) % this.children.length; + return childPicker.pick(pickArgs); } /** @@ -91,54 +83,51 @@ class RoundRobinPicker implements Picker { * balancer implementation to preserve this part of the picker state if * possible when a subchannel connects or disconnects. */ - peekNextSubchannel(): SubchannelInterface { - return this.subchannelList[this.nextIndex]; + peekNextEndpoint(): Endpoint { + return this.children[this.nextIndex].endpoint; } } export class RoundRobinLoadBalancer implements LoadBalancer { - private subchannels: SubchannelInterface[] = []; + private children: LeafLoadBalancer[] = []; private currentState: ConnectivityState = ConnectivityState.IDLE; - private subchannelStateListener: ConnectivityStateListener; - private currentReadyPicker: RoundRobinPicker | null = null; + private updatesPaused = false; + + private childChannelControlHelper: ChannelControlHelper; + constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.subchannelStateListener = ( - subchannel: SubchannelInterface, - previousState: ConnectivityState, - newState: ConnectivityState - ) => { - this.calculateAndUpdateState(); - - if ( - newState === ConnectivityState.TRANSIENT_FAILURE || - newState === ConnectivityState.IDLE - ) { - this.channelControlHelper.requestReresolution(); - subchannel.startConnecting(); + this.childChannelControlHelper = createChildChannelControlHelper( + channelControlHelper, + { + updateState: (connectivityState, picker) => { + this.calculateAndUpdateState(); + }, } - }; + ); } - private countSubchannelsWithState(state: ConnectivityState) { - return this.subchannels.filter( - subchannel => subchannel.getConnectivityState() === state - ).length; + private countChildrenWithState(state: ConnectivityState) { + return this.children.filter(child => child.getConnectivityState() === state) + .length; } private calculateAndUpdateState() { - if (this.countSubchannelsWithState(ConnectivityState.READY) > 0) { - const readySubchannels = this.subchannels.filter( - subchannel => - subchannel.getConnectivityState() === ConnectivityState.READY + if (this.updatesPaused) { + return; + } + if (this.countChildrenWithState(ConnectivityState.READY) > 0) { + const readyChildren = this.children.filter( + child => child.getConnectivityState() === ConnectivityState.READY ); let index = 0; if (this.currentReadyPicker !== null) { - index = readySubchannels.indexOf( - this.currentReadyPicker.peekNextSubchannel() + const nextPickedEndpoint = this.currentReadyPicker.peekNextEndpoint(); + index = readyChildren.findIndex(child => + endpointEqual(child.getEndpoint(), nextPickedEndpoint) ); if (index < 0) { index = 0; @@ -146,14 +135,18 @@ export class RoundRobinLoadBalancer implements LoadBalancer { } this.updateState( ConnectivityState.READY, - new RoundRobinPicker(readySubchannels, index) + new RoundRobinPicker( + readyChildren.map(child => ({ + endpoint: child.getEndpoint(), + picker: child.getPicker(), + })), + index + ) ); - } else if ( - this.countSubchannelsWithState(ConnectivityState.CONNECTING) > 0 - ) { + } else if (this.countChildrenWithState(ConnectivityState.CONNECTING) > 0) { this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); } else if ( - this.countSubchannelsWithState(ConnectivityState.TRANSIENT_FAILURE) > 0 + this.countChildrenWithState(ConnectivityState.TRANSIENT_FAILURE) > 0 ) { this.updateState( ConnectivityState.TRANSIENT_FAILURE, @@ -180,51 +173,35 @@ export class RoundRobinLoadBalancer implements LoadBalancer { } private resetSubchannelList() { - for (const subchannel of this.subchannels) { - subchannel.removeConnectivityStateListener(this.subchannelStateListener); - subchannel.unref(); - this.channelControlHelper.removeChannelzChild( - subchannel.getChannelzRef() - ); + for (const child of this.children) { + child.destroy(); } - this.subchannels = []; } updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig ): void { this.resetSubchannelList(); - trace( - 'Connect to address list ' + - addressList.map(address => subchannelAddressToString(address)) - ); - this.subchannels = addressList.map(address => - this.channelControlHelper.createSubchannel(address, {}) + trace('Connect to endpoint list ' + endpointList.map(endpointToString)); + this.updatesPaused = true; + this.children = endpointList.map( + endpoint => new LeafLoadBalancer(endpoint, this.childChannelControlHelper) ); - for (const subchannel of this.subchannels) { - subchannel.ref(); - subchannel.addConnectivityStateListener(this.subchannelStateListener); - this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef()); - const subchannelState = subchannel.getConnectivityState(); - if ( - subchannelState === ConnectivityState.IDLE || - subchannelState === ConnectivityState.TRANSIENT_FAILURE - ) { - subchannel.startConnecting(); - } + for (const child of this.children) { + child.startConnecting(); } + this.updatesPaused = false; this.calculateAndUpdateState(); } exitIdle(): void { - for (const subchannel of this.subchannels) { - subchannel.startConnecting(); - } + /* The round_robin LB policy is only in the IDLE state if it has no + * addresses to try to connect to and it has no picked subchannel. + * In that case, there is no meaningful action that can be taken here. */ } resetBackoff(): void { - /* The pick first load balancer does not have a connection backoff, so this - * does nothing */ + // This LB policy has no backoff to reset } destroy(): void { this.resetSubchannelList(); diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index d5d69543f..1145fdc90 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -16,7 +16,7 @@ */ import { ChannelOptions } from './channel-options'; -import { SubchannelAddress } from './subchannel-address'; +import { Endpoint, SubchannelAddress } from './subchannel-address'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; import { ChannelRef, SubchannelRef } from './channelz'; @@ -95,12 +95,12 @@ export interface LoadBalancer { * The load balancer will start establishing connections with the new list, * but will continue using any existing connections until the new connections * are established - * @param addressList The new list of addresses to connect to + * @param endpointList The new list of addresses to connect to * @param lbConfig The load balancing config object from the service config, * if one was provided */ updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void; @@ -185,7 +185,9 @@ export function isLoadBalancerNameRegistered(typeName: string): boolean { return typeName in registeredLoadBalancerTypes; } -export function parseLoadBalancingConfig(rawConfig: LoadBalancingConfig): TypedLoadBalancingConfig { +export function parseLoadBalancingConfig( + rawConfig: LoadBalancingConfig +): TypedLoadBalancingConfig { const keys = Object.keys(rawConfig); if (keys.length !== 1) { throw new Error( @@ -210,7 +212,9 @@ export function getDefaultConfig() { if (!defaultLoadBalancerType) { throw new Error('No default load balancer type registered'); } - return new registeredLoadBalancerTypes[defaultLoadBalancerType]!.LoadBalancingConfig(); + return new registeredLoadBalancerTypes[ + defaultLoadBalancerType + ]!.LoadBalancingConfig(); } export function selectLbConfigFromList( @@ -221,7 +225,11 @@ export function selectLbConfigFromList( try { return parseLoadBalancingConfig(config); } catch (e) { - log(LogVerbosity.DEBUG, 'Config parsing failed with error', (e as Error).message); + log( + LogVerbosity.DEBUG, + 'Config parsing failed with error', + (e as Error).message + ); continue; } } diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index d95eca21b..6474269f7 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -97,16 +97,13 @@ export interface Picker { */ export class UnavailablePicker implements Picker { private status: StatusObject; - constructor(status?: StatusObject) { - if (status !== undefined) { - this.status = status; - } else { - this.status = { - code: Status.UNAVAILABLE, - details: 'No connection established', - metadata: new Metadata(), - }; - } + constructor(status?: Partial) { + this.status = { + code: Status.UNAVAILABLE, + details: 'No connection established', + metadata: new Metadata(), + ...status, + }; } pick(pickArgs: PickArgs): TransientFailurePickResult { return { diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index c40cb8ec5..0956b460c 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -28,7 +28,7 @@ import { StatusObject } from './call-interface'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { SubchannelAddress, TcpSubchannelAddress } from './subchannel-address'; +import { Endpoint, TcpSubchannelAddress } from './subchannel-address'; import { GrpcUri, uriToString, splitHostPort } from './uri-parser'; import { isIPv6, isIPv4 } from 'net'; import { ChannelOptions } from './channel-options'; @@ -50,35 +50,11 @@ const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000; const resolveTxtPromise = util.promisify(dns.resolveTxt); const dnsLookupPromise = util.promisify(dns.lookup); -/** - * Merge any number of arrays into a single alternating array - * @param arrays - */ -function mergeArrays(...arrays: T[][]): T[] { - const result: T[] = []; - for ( - let i = 0; - i < - Math.max.apply( - null, - arrays.map(array => array.length) - ); - i++ - ) { - for (const array of arrays) { - if (i < array.length) { - result.push(array[i]); - } - } - } - return result; -} - /** * Resolver implementation that handles DNS names and IP addresses. */ class DnsResolver implements Resolver { - private readonly ipResult: SubchannelAddress[] | null; + private readonly ipResult: Endpoint[] | null; private readonly dnsHostname: string | null; private readonly port: number | null; /** @@ -89,7 +65,7 @@ class DnsResolver implements Resolver { private readonly minTimeBetweenResolutionsMs: number; private pendingLookupPromise: Promise | null = null; private pendingTxtPromise: Promise | null = null; - private latestLookupResult: TcpSubchannelAddress[] | null = null; + private latestLookupResult: Endpoint[] | null = null; private latestServiceConfig: ServiceConfig | null = null; private latestServiceConfigError: StatusObject | null = null; private percentage: number; @@ -114,8 +90,12 @@ class DnsResolver implements Resolver { if (isIPv4(hostPort.host) || isIPv6(hostPort.host)) { this.ipResult = [ { - host: hostPort.host, - port: hostPort.port ?? DEFAULT_PORT, + addresses: [ + { + host: hostPort.host, + port: hostPort.port ?? DEFAULT_PORT, + }, + ], }, ]; this.dnsHostname = null; @@ -213,18 +193,15 @@ class DnsResolver implements Resolver { this.pendingLookupPromise = null; this.backoff.reset(); this.backoff.stop(); - const ip4Addresses: dns.LookupAddress[] = addressList.filter( - addr => addr.family === 4 - ); - const ip6Addresses: dns.LookupAddress[] = addressList.filter( - addr => addr.family === 6 - ); - this.latestLookupResult = mergeArrays(ip6Addresses, ip4Addresses).map( + const subchannelAddresses: TcpSubchannelAddress[] = addressList.map( addr => ({ host: addr.address, port: +this.port! }) ); + this.latestLookupResult = subchannelAddresses.map(address => ({ + addresses: [address], + })); const allAddressesString: string = '[' + - this.latestLookupResult + subchannelAddresses .map(addr => addr.host + ':' + addr.port) .join(',') + ']'; diff --git a/packages/grpc-js/src/resolver-ip.ts b/packages/grpc-js/src/resolver-ip.ts index 0704131e1..cda35d3b9 100644 --- a/packages/grpc-js/src/resolver-ip.ts +++ b/packages/grpc-js/src/resolver-ip.ts @@ -20,7 +20,7 @@ import { ChannelOptions } from './channel-options'; import { LogVerbosity, Status } from './constants'; import { Metadata } from './metadata'; import { registerResolver, Resolver, ResolverListener } from './resolver'; -import { SubchannelAddress } from './subchannel-address'; +import { Endpoint, SubchannelAddress } from './subchannel-address'; import { GrpcUri, splitHostPort, uriToString } from './uri-parser'; import * as logging from './logging'; @@ -39,7 +39,7 @@ const IPV6_SCHEME = 'ipv6'; const DEFAULT_PORT = 443; class IpResolver implements Resolver { - private addresses: SubchannelAddress[] = []; + private endpoints: Endpoint[] = []; private error: StatusObject | null = null; constructor( target: GrpcUri, @@ -83,8 +83,8 @@ class IpResolver implements Resolver { port: hostPort.port ?? DEFAULT_PORT, }); } - this.addresses = addresses; - trace('Parsed ' + target.scheme + ' address list ' + this.addresses); + this.endpoints = addresses.map(address => ({ addresses: [address] })); + trace('Parsed ' + target.scheme + ' address list ' + addresses); } updateResolution(): void { process.nextTick(() => { @@ -92,7 +92,7 @@ class IpResolver implements Resolver { this.listener.onError(this.error); } else { this.listener.onSuccessfulResolution( - this.addresses, + this.endpoints, null, null, null, diff --git a/packages/grpc-js/src/resolver-uds.ts b/packages/grpc-js/src/resolver-uds.ts index 24095ec29..4fa1944b1 100644 --- a/packages/grpc-js/src/resolver-uds.ts +++ b/packages/grpc-js/src/resolver-uds.ts @@ -15,12 +15,12 @@ */ import { Resolver, ResolverListener, registerResolver } from './resolver'; -import { SubchannelAddress } from './subchannel-address'; +import { Endpoint } from './subchannel-address'; import { GrpcUri } from './uri-parser'; import { ChannelOptions } from './channel-options'; class UdsResolver implements Resolver { - private addresses: SubchannelAddress[] = []; + private endpoints: Endpoint[] = []; constructor( target: GrpcUri, private listener: ResolverListener, @@ -32,12 +32,12 @@ class UdsResolver implements Resolver { } else { path = target.path; } - this.addresses = [{ path }]; + this.endpoints = [{ addresses: [{ path }] }]; } updateResolution(): void { process.nextTick( this.listener.onSuccessfulResolution, - this.addresses, + this.endpoints, null, null, null, diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index 435086255..4dfa8d133 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -17,7 +17,7 @@ import { MethodConfig, ServiceConfig } from './service-config'; import { StatusObject } from './call-interface'; -import { SubchannelAddress } from './subchannel-address'; +import { Endpoint } from './subchannel-address'; import { GrpcUri, uriToString } from './uri-parser'; import { ChannelOptions } from './channel-options'; import { Metadata } from './metadata'; @@ -55,7 +55,7 @@ export interface ResolverListener { * service configuration was invalid */ onSuccessfulResolution( - addressList: SubchannelAddress[], + addressList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null, configSelector: ConfigSelector | null, diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index e2b2c1fa5..22808dc32 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -32,7 +32,7 @@ import { StatusObject } from './call-interface'; import { Metadata } from './metadata'; import * as logging from './logging'; import { LogVerbosity } from './constants'; -import { SubchannelAddress } from './subchannel-address'; +import { Endpoint } from './subchannel-address'; import { GrpcUri, uriToString } from './uri-parser'; import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; import { ChannelOptions } from './channel-options'; @@ -177,7 +177,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { target, { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: ServiceError | null, configSelector: ConfigSelector | null, @@ -226,7 +226,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { return; } this.childLoadBalancer.updateAddressList( - addressList, + endpointList, loadBalancingConfig, attributes ); @@ -307,7 +307,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { } updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig | null ): never { throw new Error('updateAddressList not supported on ResolvingLoadBalancer'); diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index c9308ca62..4099f1350 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -631,12 +631,15 @@ export class Server { const resolverListener: ResolverListener = { onSuccessfulResolution: ( - addressList, + endpointList, serviceConfig, serviceConfigError ) => { // We only want one resolution result. Discard all future results resolverListener.onSuccessfulResolution = () => {}; + const addressList = ([] as SubchannelAddress[]).concat( + ...endpointList.map(endpoint => endpoint.addresses) + ); if (addressList.length === 0) { deferredCallback( new Error(`No addresses resolved for port ${port}`), diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts index 1ab88f45d..36fd99ea6 100644 --- a/packages/grpc-js/src/subchannel-address.ts +++ b/packages/grpc-js/src/subchannel-address.ts @@ -86,3 +86,39 @@ export function stringToSubchannelAddress( }; } } + +export interface Endpoint { + addresses: SubchannelAddress[]; +} + +export function endpointEqual(endpoint1: Endpoint, endpoint2: Endpoint) { + if (endpoint1.addresses.length !== endpoint2.addresses.length) { + return false; + } + for (let i = 0; i < endpoint1.addresses.length; i++) { + if ( + !subchannelAddressEqual(endpoint1.addresses[i], endpoint2.addresses[i]) + ) { + return false; + } + } + return true; +} + +export function endpointToString(endpoint: Endpoint): string { + return ( + '[' + endpoint.addresses.map(subchannelAddressToString).join(', ') + ']' + ); +} + +export function endpointHasAddress( + endpoint: Endpoint, + expectedAddress: SubchannelAddress +): boolean { + for (const address of endpoint.addresses) { + if (subchannelAddressEqual(address, expectedAddress)) { + return true; + } + } + return false; +} diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts index 9b947ad32..c7fdca4fd 100644 --- a/packages/grpc-js/src/subchannel-interface.ts +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -26,6 +26,8 @@ export type ConnectivityStateListener = ( keepaliveTime: number ) => void; +export type HealthListener = (healthy: boolean) => void; + /** * This is an interface for load balancing policies to use to interact with * subchannels. This allows load balancing policies to wrap and unwrap @@ -45,6 +47,9 @@ export interface SubchannelInterface { ref(): void; unref(): void; getChannelzRef(): SubchannelRef; + isHealthy(): boolean; + addHealthStateWatcher(listener: HealthListener): void; + removeHealthStateWatcher(listener: HealthListener): void; /** * If this is a wrapper, return the wrapped subchannel, otherwise return this */ @@ -58,7 +63,23 @@ export interface SubchannelInterface { } export abstract class BaseSubchannelWrapper implements SubchannelInterface { - constructor(protected child: SubchannelInterface) {} + private healthy = true; + private healthListeners: Set = new Set(); + constructor(protected child: SubchannelInterface) { + child.addHealthStateWatcher(childHealthy => { + /* A change to the child health state only affects this wrapper's overall + * health state if this wrapper is reporting healthy. */ + if (this.healthy) { + this.updateHealthListeners(); + } + }); + } + + private updateHealthListeners(): void { + for (const listener of this.healthListeners) { + listener(this.isHealthy()); + } + } getConnectivityState(): ConnectivityState { return this.child.getConnectivityState(); @@ -87,6 +108,25 @@ export abstract class BaseSubchannelWrapper implements SubchannelInterface { getChannelzRef(): SubchannelRef { return this.child.getChannelzRef(); } + isHealthy(): boolean { + return this.healthy && this.child.isHealthy(); + } + addHealthStateWatcher(listener: HealthListener): void { + this.healthListeners.add(listener); + } + removeHealthStateWatcher(listener: HealthListener): void { + this.healthListeners.delete(listener); + } + protected setHealthy(healthy: boolean): void { + if (healthy !== this.healthy) { + this.healthy = healthy; + /* A change to this wrapper's health state only affects the overall + * reported health state if the child is healthy. */ + if (this.child.isHealthy()) { + this.updateHealthListeners(); + } + } + } getRealSubchannel(): Subchannel { return this.child.getRealSubchannel(); } diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 6fad9500a..cf49ced77 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -461,6 +461,18 @@ export class Subchannel { return this.channelzRef; } + isHealthy(): boolean { + return true; + } + + addHealthStateWatcher(listener: (healthy: boolean) => void): void { + // Do nothing with the listener + } + + removeHealthStateWatcher(listener: (healthy: boolean) => void): void { + // Do nothing with the listener + } + getRealSubchannel(): this { return this; } diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index d15a9d5ed..20352fb2f 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -27,7 +27,10 @@ import { loadPackageDefinition, } from '../src/make-client'; import { readFileSync } from 'fs'; -import { SubchannelInterface } from '../src/subchannel-interface'; +import { + HealthListener, + SubchannelInterface, +} from '../src/subchannel-interface'; import { SubchannelRef } from '../src/channelz'; import { Subchannel } from '../src/subchannel'; import { ConnectivityState } from '../src/connectivity-state'; @@ -198,6 +201,11 @@ export class MockSubchannel implements SubchannelInterface { realSubchannelEquals(other: grpc.experimental.SubchannelInterface): boolean { return this === other; } + isHealthy(): boolean { + return true; + } + addHealthStateWatcher(listener: HealthListener): void {} + removeHealthStateWatcher(listener: HealthListener): void {} } export { assert2 }; diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index e9e9e5601..7e862de58 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -29,10 +29,7 @@ import { } from '../src/load-balancer-pick-first'; import { Metadata } from '../src/metadata'; import { Picker } from '../src/picker'; -import { - SubchannelAddress, - subchannelAddressToString, -} from '../src/subchannel-address'; +import { Endpoint, subchannelAddressToString } from '../src/subchannel-address'; import { MockSubchannel, TestClient, TestServer } from './common'; function updateStateCallBackForExpectedStateSequence( @@ -120,7 +117,10 @@ describe('pick_first load balancing policy', () => { } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper); - pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { subchannels[0].transitionToState(ConnectivityState.READY); }); @@ -144,7 +144,10 @@ describe('pick_first load balancing policy', () => { } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper); - pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); }); it('Should stay CONNECTING if only some subchannels fail to connect', done => { const channelControlHelper = createChildChannelControlHelper( @@ -159,8 +162,8 @@ describe('pick_first load balancing policy', () => { const pickFirst = new PickFirstLoadBalancer(channelControlHelper); pickFirst.updateAddressList( [ - { host: 'localhost', port: 1 }, - { host: 'localhost', port: 2 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); @@ -181,8 +184,8 @@ describe('pick_first load balancing policy', () => { const pickFirst = new PickFirstLoadBalancer(channelControlHelper); pickFirst.updateAddressList( [ - { host: 'localhost', port: 1 }, - { host: 'localhost', port: 2 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); @@ -206,8 +209,8 @@ describe('pick_first load balancing policy', () => { const pickFirst = new PickFirstLoadBalancer(channelControlHelper); pickFirst.updateAddressList( [ - { host: 'localhost', port: 1 }, - { host: 'localhost', port: 2 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); @@ -245,8 +248,8 @@ describe('pick_first load balancing policy', () => { const pickFirst = new PickFirstLoadBalancer(channelControlHelper); pickFirst.updateAddressList( [ - { host: 'localhost', port: 1 }, - { host: 'localhost', port: 2 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); @@ -272,8 +275,8 @@ describe('pick_first load balancing policy', () => { const pickFirst = new PickFirstLoadBalancer(channelControlHelper); pickFirst.updateAddressList( [ - { host: 'localhost', port: 1 }, - { host: 'localhost', port: 2 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); @@ -303,8 +306,8 @@ describe('pick_first load balancing policy', () => { const pickFirst = new PickFirstLoadBalancer(channelControlHelper); pickFirst.updateAddressList( [ - { host: 'localhost', port: 1 }, - { host: 'localhost', port: 2 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); @@ -312,8 +315,8 @@ describe('pick_first load balancing policy', () => { currentStartState = ConnectivityState.CONNECTING; pickFirst.updateAddressList( [ - { host: 'localhost', port: 3 }, - { host: 'localhost', port: 4 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); @@ -341,14 +344,17 @@ describe('pick_first load balancing policy', () => { const pickFirst = new PickFirstLoadBalancer(channelControlHelper); pickFirst.updateAddressList( [ - { host: 'localhost', port: 1 }, - { host: 'localhost', port: 2 }, + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, ], config ); process.nextTick(() => { currentStartState = ConnectivityState.READY; - pickFirst.updateAddressList([{ host: 'localhost', port: 3 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 3 }] }], + config + ); }); }); it('Should transition from READY to IDLE if the connected subchannel disconnects', done => { @@ -371,7 +377,10 @@ describe('pick_first load balancing policy', () => { } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper); - pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { subchannels[0].transitionToState(ConnectivityState.IDLE); }); @@ -396,10 +405,16 @@ describe('pick_first load balancing policy', () => { } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper); - pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { currentStartState = ConnectivityState.IDLE; - pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 2 }] }], + config + ); process.nextTick(() => { subchannels[0].transitionToState(ConnectivityState.IDLE); }); @@ -425,10 +440,16 @@ describe('pick_first load balancing policy', () => { } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper); - pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { currentStartState = ConnectivityState.TRANSIENT_FAILURE; - pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 2 }] }], + config + ); process.nextTick(() => { subchannels[0].transitionToState(ConnectivityState.IDLE); }); @@ -454,9 +475,15 @@ describe('pick_first load balancing policy', () => { } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper); - pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { - pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 2 }] }], + config + ); process.nextTick(() => { subchannels[0].transitionToState(ConnectivityState.IDLE); }); @@ -490,24 +517,24 @@ describe('pick_first load balancing policy', () => { }, } ); - const addresses: SubchannelAddress[] = []; + const endpoints: Endpoint[] = []; for (let i = 0; i < 10; i++) { - addresses.push({ host: 'localhost', port: i + 1 }); + endpoints.push({ addresses: [{ host: 'localhost', port: i + 1 }] }); } const pickFirst = new PickFirstLoadBalancer(channelControlHelper); /* Pick from 10 subchannels 5 times, with address randomization enabled, * and verify that at least two different subchannels are picked. The * probability choosing the same address every time is 1/10,000, which * I am considering an acceptable flake rate */ - pickFirst.updateAddressList(addresses, shuffleConfig); + pickFirst.updateAddressList(endpoints, shuffleConfig); process.nextTick(() => { - pickFirst.updateAddressList(addresses, shuffleConfig); + pickFirst.updateAddressList(endpoints, shuffleConfig); process.nextTick(() => { - pickFirst.updateAddressList(addresses, shuffleConfig); + pickFirst.updateAddressList(endpoints, shuffleConfig); process.nextTick(() => { - pickFirst.updateAddressList(addresses, shuffleConfig); + pickFirst.updateAddressList(endpoints, shuffleConfig); process.nextTick(() => { - pickFirst.updateAddressList(addresses, shuffleConfig); + pickFirst.updateAddressList(endpoints, shuffleConfig); process.nextTick(() => { assert(pickedSubchannels.size > 1); done(); @@ -546,20 +573,20 @@ describe('pick_first load balancing policy', () => { }, } ); - const addresses: SubchannelAddress[] = []; + const endpoints: Endpoint[] = []; for (let i = 0; i < 10; i++) { - addresses.push({ host: 'localhost', port: i + 1 }); + endpoints.push({ addresses: [{ host: 'localhost', port: i + 1 }] }); } const pickFirst = new PickFirstLoadBalancer(channelControlHelper); - pickFirst.updateAddressList(addresses, config); + pickFirst.updateAddressList(endpoints, config); process.nextTick(() => { - pickFirst.updateAddressList(addresses, config); + pickFirst.updateAddressList(endpoints, config); process.nextTick(() => { - pickFirst.updateAddressList(addresses, config); + pickFirst.updateAddressList(endpoints, config); process.nextTick(() => { - pickFirst.updateAddressList(addresses, config); + pickFirst.updateAddressList(endpoints, config); process.nextTick(() => { - pickFirst.updateAddressList(addresses, config); + pickFirst.updateAddressList(endpoints, config); process.nextTick(() => { assert(pickedSubchannels.size === 1); done(); diff --git a/packages/grpc-js/test/test-resolver.ts b/packages/grpc-js/test/test-resolver.ts index 98d74823b..c88367285 100644 --- a/packages/grpc-js/test/test-resolver.ts +++ b/packages/grpc-js/test/test-resolver.ts @@ -25,12 +25,27 @@ import * as resolver_ip from '../src/resolver-ip'; import { ServiceConfig } from '../src/service-config'; import { StatusObject } from '../src/call-interface'; import { + Endpoint, SubchannelAddress, - isTcpSubchannelAddress, - subchannelAddressToString, + endpointToString, + subchannelAddressEqual, } from '../src/subchannel-address'; import { parseUri, GrpcUri } from '../src/uri-parser'; +function hasMatchingAddress( + endpointList: Endpoint[], + expectedAddress: SubchannelAddress +): boolean { + for (const endpoint of endpointList) { + for (const address of endpoint.addresses) { + if (subchannelAddressEqual(address, expectedAddress)) { + return true; + } + } + } + return false; +} + describe('Name Resolver', () => { before(() => { resolver_dns.setup(); @@ -46,27 +61,17 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 50051 - ) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 }) ); assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 50051 - ) + hasMatchingAddress(endpointList, { host: '::1', port: 50051 }) ); done(); }, @@ -83,28 +88,16 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 443 - ) - ); - assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 443 - ) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }) ); + assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 })); done(); }, onError: (error: StatusObject) => { @@ -118,19 +111,14 @@ describe('Name Resolver', () => { const target = resolverManager.mapUriDefaultScheme(parseUri('1.2.3.4')!)!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '1.2.3.4' && - addr.port === 443 - ) + hasMatchingAddress(endpointList, { host: '1.2.3.4', port: 443 }) ); done(); }, @@ -145,20 +133,13 @@ describe('Name Resolver', () => { const target = resolverManager.mapUriDefaultScheme(parseUri('::1')!)!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; - assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 443 - ) - ); + assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 })); done(); }, onError: (error: StatusObject) => { @@ -174,19 +155,14 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 50051 - ) + hasMatchingAddress(endpointList, { host: '::1', port: 50051 }) ); done(); }, @@ -203,13 +179,13 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; - assert(addressList.length > 0); + assert(endpointList.length > 0); done(); }, onError: (error: StatusObject) => { @@ -227,7 +203,7 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { @@ -253,7 +229,7 @@ describe('Name Resolver', () => { let count = 0; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { @@ -290,21 +266,16 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 443 - ), - `None of [${addressList.map(addr => - subchannelAddressToString(addr) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }), + `None of [${endpointList.map(addr => + endpointToString(addr) )}] matched '127.0.0.1:443'` ); done(); @@ -324,20 +295,13 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; - assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 443 - ) - ); + assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 })); done(); }, onError: (error: StatusObject) => { @@ -356,21 +320,16 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 443 - ), - `None of [${addressList.map(addr => - subchannelAddressToString(addr) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }), + `None of [${endpointList.map(addr => + endpointToString(addr) )}] matched '127.0.0.1:443'` ); /* TODO(murgatroid99): check for IPv6 result, once we can get that @@ -392,13 +351,13 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; - assert(addressList.length > 0); + assert(endpointList.length > 0); done(); }, onError: (error: StatusObject) => { @@ -422,11 +381,11 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { - assert(addressList.length > 0); + assert(endpointList.length > 0); completeCount += 1; if (completeCount === 2) { // Only handle the first resolution result @@ -452,25 +411,15 @@ describe('Name Resolver', () => { target, { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 443 - ) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }) ); assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 443 - ) + hasMatchingAddress(endpointList, { host: '::1', port: 443 }) ); resultCount += 1; if (resultCount === 1) { @@ -498,7 +447,7 @@ describe('Name Resolver', () => { target, { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { @@ -527,17 +476,13 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; - assert( - addressList.some( - addr => !isTcpSubchannelAddress(addr) && addr.path === 'socket' - ) - ); + assert(hasMatchingAddress(endpointList, { path: 'socket' })); done(); }, onError: (error: StatusObject) => { @@ -553,18 +498,13 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; - assert( - addressList.some( - addr => - !isTcpSubchannelAddress(addr) && addr.path === '/tmp/socket' - ) - ); + assert(hasMatchingAddress(endpointList, { path: '/tmp/socket' })); done(); }, onError: (error: StatusObject) => { @@ -582,19 +522,14 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 443 - ) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }) ); done(); }, @@ -611,19 +546,14 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 50051 - ) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 }) ); done(); }, @@ -640,27 +570,17 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 50051 - ) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 }) ); assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '127.0.0.1' && - addr.port === 50052 - ) + hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50052 }) ); done(); }, @@ -677,20 +597,13 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; - assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 443 - ) - ); + assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 })); done(); }, onError: (error: StatusObject) => { @@ -706,19 +619,14 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 50051 - ) + hasMatchingAddress(endpointList, { host: '::1', port: 50051 }) ); done(); }, @@ -735,27 +643,17 @@ describe('Name Resolver', () => { )!; const listener: resolverManager.ResolverListener = { onSuccessfulResolution: ( - addressList: SubchannelAddress[], + endpointList: Endpoint[], serviceConfig: ServiceConfig | null, serviceConfigError: StatusObject | null ) => { // Only handle the first resolution result listener.onSuccessfulResolution = () => {}; assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 50051 - ) + hasMatchingAddress(endpointList, { host: '::1', port: 50051 }) ); assert( - addressList.some( - addr => - isTcpSubchannelAddress(addr) && - addr.host === '::1' && - addr.port === 50052 - ) + hasMatchingAddress(endpointList, { host: '::1', port: 50052 }) ); done(); }, From e919aa7aa375385bba9661496e67f9ca243bf032 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 30 Aug 2023 14:47:06 -0700 Subject: [PATCH 580/694] grpc-js-xds: Update LB policies to handle grpc-js changes --- .../grpc-js-xds/interop/xds-interop-client.ts | 6 +-- packages/grpc-js-xds/src/load-balancer-cds.ts | 11 +---- .../grpc-js-xds/src/load-balancer-priority.ts | 44 +++++++++---------- .../src/load-balancer-weighted-target.ts | 30 ++++++------- .../src/load-balancer-xds-cluster-impl.ts | 27 +++++++++--- .../src/load-balancer-xds-cluster-manager.ts | 12 ++--- .../src/load-balancer-xds-cluster-resolver.ts | 40 +++++++++-------- .../src/load-balancer-xds-wrr-locality.ts | 12 ++--- .../test/test-custom-lb-policies.ts | 6 +-- 9 files changed, 99 insertions(+), 89 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index f9034ed8e..dc70034f9 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -34,7 +34,7 @@ import TypedLoadBalancingConfig = grpc.experimental.TypedLoadBalancingConfig; import LoadBalancer = grpc.experimental.LoadBalancer; import ChannelControlHelper = grpc.experimental.ChannelControlHelper; import ChildLoadBalancerHandler = grpc.experimental.ChildLoadBalancerHandler; -import SubchannelAddress = grpc.experimental.SubchannelAddress; +import Endpoint = grpc.experimental.Endpoint; import Picker = grpc.experimental.Picker; import PickArgs = grpc.experimental.PickArgs; import PickResult = grpc.experimental.PickResult; @@ -99,12 +99,12 @@ class RpcBehaviorLoadBalancer implements LoadBalancer { }); this.child = new ChildLoadBalancerHandler(childChannelControlHelper); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) { return; } this.latestConfig = lbConfig; - this.child.updateAddressList(addressList, RPC_BEHAVIOR_CHILD_CONFIG, attributes); + this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, attributes); } exitIdle(): void { this.child.exitIdle(); diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 647ab2b97..1e4f63b4b 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -18,23 +18,16 @@ import { connectivityState, status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from '@grpc/grpc-js'; import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; -import SubchannelAddress = experimental.SubchannelAddress; +import Endpoint = experimental.Endpoint; import UnavailablePicker = experimental.UnavailablePicker; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; -import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig; -import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig; import QueuePicker = experimental.QueuePicker; -import OutlierDetectionRawConfig = experimental.OutlierDetectionRawConfig; import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; -import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection'; -import { Duration__Output } from './generated/google/protobuf/Duration'; -import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment'; import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler } from './load-balancer-xds-cluster-resolver'; -import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources'; import { CdsUpdate, ClusterResourceType } from './xds-resource-type/cluster-resource-type'; const TRACER_NAME = 'cds_balancer'; @@ -258,7 +251,7 @@ export class CdsLoadBalancer implements LoadBalancer { } updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index 4a3e41a11..ba4fd2cfa 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -19,8 +19,8 @@ import { connectivityState as ConnectivityState, status as Status, Metadata, log import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; -import SubchannelAddress = experimental.SubchannelAddress; -import subchannelAddressToString = experimental.subchannelAddressToString; +import Endpoint = experimental.Endpoint; +import endpointToString = experimental.endpointToString; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import Picker = experimental.Picker; import QueuePicker = experimental.QueuePicker; @@ -40,16 +40,16 @@ const TYPE_NAME = 'priority'; const DEFAULT_FAILOVER_TIME_MS = 10_000; const DEFAULT_RETENTION_INTERVAL_MS = 15 * 60 * 1000; -export type LocalitySubchannelAddress = SubchannelAddress & { +export interface LocalityEndpoint extends Endpoint { localityPath: string[]; locality: Locality__Output; weight: number; }; -export function isLocalitySubchannelAddress( - address: SubchannelAddress -): address is LocalitySubchannelAddress { - return Array.isArray((address as LocalitySubchannelAddress).localityPath); +export function isLocalityEndpoint( + address: Endpoint +): address is LocalityEndpoint { + return Array.isArray((address as LocalityEndpoint).localityPath); } /** @@ -138,7 +138,7 @@ class PriorityLoadBalancingConfig implements TypedLoadBalancingConfig { interface PriorityChildBalancer { updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void; @@ -154,7 +154,7 @@ interface PriorityChildBalancer { } interface UpdateArgs { - subchannelAddress: SubchannelAddress[]; + subchannelAddress: Endpoint[]; lbConfig: TypedLoadBalancingConfig; ignoreReresolutionRequests: boolean; } @@ -218,11 +218,11 @@ export class PriorityLoadBalancer implements LoadBalancer { } updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { - this.childBalancer.updateAddressList(addressList, lbConfig, attributes); + this.childBalancer.updateAddressList(endpointList, lbConfig, attributes); } exitIdle() { @@ -412,7 +412,7 @@ export class PriorityLoadBalancer implements LoadBalancer { } updateAddressList( - addressList: SubchannelAddress[], + endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown } ): void { @@ -425,23 +425,23 @@ export class PriorityLoadBalancer implements LoadBalancer { * which child it belongs to. So we bucket those addresses by that first * element, and pass along the rest of the localityPath for that child * to use. */ - const childAddressMap: Map = new Map< + const childAddressMap: Map = new Map< string, - LocalitySubchannelAddress[] + LocalityEndpoint[] >(); - for (const address of addressList) { - if (!isLocalitySubchannelAddress(address)) { + for (const endpoint of endpointList) { + if (!isLocalityEndpoint(endpoint)) { // Reject address that cannot be prioritized return; } - if (address.localityPath.length < 1) { + if (endpoint.localityPath.length < 1) { // Reject address that cannot be prioritized return; } - const childName = address.localityPath[0]; - const childAddress: LocalitySubchannelAddress = { - ...address, - localityPath: address.localityPath.slice(1), + const childName = endpoint.localityPath[0]; + const childAddress: LocalityEndpoint = { + ...endpoint, + localityPath: endpoint.localityPath.slice(1), }; let childAddressList = childAddressMap.get(childName); if (childAddressList === undefined) { @@ -458,7 +458,7 @@ export class PriorityLoadBalancer implements LoadBalancer { * update all existing children with their new configs */ for (const [childName, childConfig] of lbConfig.getChildren()) { const childAddresses = childAddressMap.get(childName) ?? []; - trace('Assigning child ' + childName + ' address list ' + childAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')) + trace('Assigning child ' + childName + ' endpoint list ' + childAddresses.map(endpoint => '(' + endpointToString(endpoint) + ' path=' + endpoint.localityPath + ')')) this.latestUpdates.set(childName, { subchannelAddress: childAddresses, lbConfig: childConfig.config, diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts index 231f3b179..16874e01b 100644 --- a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -16,7 +16,7 @@ */ import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from "@grpc/grpc-js"; -import { isLocalitySubchannelAddress, LocalitySubchannelAddress } from "./load-balancer-priority"; +import { isLocalityEndpoint, LocalityEndpoint } from "./load-balancer-priority"; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; @@ -27,8 +27,8 @@ import PickResult = experimental.PickResult; import PickArgs = experimental.PickArgs; import QueuePicker = experimental.QueuePicker; import UnavailablePicker = experimental.UnavailablePicker; -import SubchannelAddress = experimental.SubchannelAddress; -import subchannelAddressToString = experimental.subchannelAddressToString; +import Endpoint = experimental.Endpoint; +import endpointToString = experimental.endpointToString; import selectLbConfigFromList = experimental.selectLbConfigFromList; const TRACER_NAME = 'weighted_target'; @@ -154,7 +154,7 @@ class WeightedTargetPicker implements Picker { } interface WeightedChild { - updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void; + updateAddressList(endpointList: Endpoint[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void; exitIdle(): void; resetBackoff(): void; destroy(): void; @@ -190,9 +190,9 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { this.parent.maybeUpdateState(); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void { + updateAddressList(endpointList: Endpoint[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void { this.weight = lbConfig.weight; - this.childBalancer.updateAddressList(addressList, lbConfig.child_policy, attributes); + this.childBalancer.updateAddressList(endpointList, lbConfig.child_policy, attributes); } exitIdle(): void { this.childBalancer.exitIdle(); @@ -319,7 +319,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { this.channelControlHelper.updateState(connectivityState, picker); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(addressList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof WeightedTargetLoadBalancingConfig)) { // Reject a config of the wrong type trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); @@ -330,9 +330,9 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { * which child it belongs to. So we bucket those addresses by that first * element, and pass along the rest of the localityPath for that child * to use. */ - const childAddressMap = new Map(); + const childEndpointMap = new Map(); for (const address of addressList) { - if (!isLocalitySubchannelAddress(address)) { + if (!isLocalityEndpoint(address)) { // Reject address that cannot be associated with targets return; } @@ -341,14 +341,14 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { return; } const childName = address.localityPath[0]; - const childAddress: LocalitySubchannelAddress = { + const childAddress: LocalityEndpoint = { ...address, localityPath: address.localityPath.slice(1), }; - let childAddressList = childAddressMap.get(childName); + let childAddressList = childEndpointMap.get(childName); if (childAddressList === undefined) { childAddressList = []; - childAddressMap.set(childName, childAddressList); + childEndpointMap.set(childName, childAddressList); } childAddressList.push(childAddress); } @@ -363,9 +363,9 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { } else { target.maybeReactivate(); } - const targetAddresses = childAddressMap.get(targetName) ?? []; - trace('Assigning target ' + targetName + ' address list ' + targetAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); - target.updateAddressList(targetAddresses, targetConfig, attributes); + const targetEndpoints = childEndpointMap.get(targetName) ?? []; + trace('Assigning target ' + targetName + ' address list ' + targetEndpoints.map(endpoint => '(' + endpointToString(endpoint) + ' path=' + endpoint.localityPath + ')')); + target.updateAddressList(targetEndpoints, targetConfig, attributes); } // Deactivate targets that are not in the new config diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index 926c7a699..81e365624 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -18,11 +18,13 @@ import { experimental, logVerbosity, status as Status, Metadata, connectivityState } from "@grpc/grpc-js"; import { validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; import { getSingletonXdsClient, XdsClient, XdsClusterDropStats, XdsClusterLocalityStats } from "./xds-client"; -import { LocalitySubchannelAddress } from "./load-balancer-priority"; +import { LocalityEndpoint } from "./load-balancer-priority"; import LoadBalancer = experimental.LoadBalancer; import registerLoadBalancerType = experimental.registerLoadBalancerType; -import SubchannelAddress = experimental.SubchannelAddress; +import Endpoint = experimental.Endpoint; +import endpointHasAddress = experimental.endpointHasAddress; +import subchannelAddressToString = experimental.subchannelAddressToString; import Picker = experimental.Picker; import PickArgs = experimental.PickArgs; import PickResult = experimental.PickResult; @@ -34,6 +36,7 @@ import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import selectLbConfigFromList = experimental.selectLbConfigFromList; import SubchannelInterface = experimental.SubchannelInterface; import BaseSubchannelWrapper = experimental.BaseSubchannelWrapper; +import { Locality__Output } from "./generated/envoy/config/core/v3/Locality"; const TRACER_NAME = 'xds_cluster_impl'; @@ -245,6 +248,7 @@ function getCallCounterMapKey(cluster: string, edsServiceName?: string): string class XdsClusterImplBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; + private lastestEndpointList: Endpoint[] | null = null; private latestConfig: XdsClusterImplLoadBalancingConfig | null = null; private clusterDropStats: XdsClusterDropStats | null = null; private xdsClient: XdsClient | null = null; @@ -252,11 +256,20 @@ class XdsClusterImplBalancer implements LoadBalancer { constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { createSubchannel: (subchannelAddress, subchannelArgs) => { - if (!this.xdsClient || !this.latestConfig) { + if (!this.xdsClient || !this.latestConfig || !this.lastestEndpointList) { throw new Error('xds_cluster_impl: invalid state: createSubchannel called with xdsClient or latestConfig not populated'); } - const locality = (subchannelAddress as LocalitySubchannelAddress).locality ?? ''; const wrapperChild = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); + let locality: Locality__Output | null = null; + for (const endpoint of this.lastestEndpointList) { + if (endpointHasAddress(endpoint, subchannelAddress)) { + locality = (endpoint as LocalityEndpoint).locality; + } + } + if (locality === null) { + trace('Not reporting load for address ' + subchannelAddressToString(subchannelAddress) + ' because it has unknown locality.'); + return wrapperChild; + } const lrsServer = this.latestConfig.getLrsLoadReportingServer(); let statsObj: XdsClusterLocalityStats | null = null; if (lrsServer) { @@ -279,15 +292,15 @@ class XdsClusterImplBalancer implements LoadBalancer { } })); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsClusterImplLoadBalancingConfig)) { trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); return; } trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2)); + this.lastestEndpointList = endpointList; this.latestConfig = lbConfig; this.xdsClient = attributes.xdsClient as XdsClient; - if (lbConfig.getLrsLoadReportingServer()) { this.clusterDropStats = this.xdsClient.addClusterDropStats( lbConfig.getLrsLoadReportingServer()!, @@ -296,7 +309,7 @@ class XdsClusterImplBalancer implements LoadBalancer { ); } - this.childBalancer.updateAddressList(addressList, lbConfig.getChildPolicy(), attributes); + this.childBalancer.updateAddressList(endpointList, lbConfig.getChildPolicy(), attributes); } exitIdle(): void { this.childBalancer.exitIdle(); diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index ce3207dfd..a1855d142 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -25,7 +25,7 @@ import PickArgs = experimental.PickArgs; import PickResultType = experimental.PickResultType; import UnavailablePicker = experimental.UnavailablePicker; import QueuePicker = experimental.QueuePicker; -import SubchannelAddress = experimental.SubchannelAddress; +import Endpoint = experimental.Endpoint; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import ChannelControlHelper = experimental.ChannelControlHelper; import selectLbConfigFromList = experimental.selectLbConfigFromList; @@ -111,7 +111,7 @@ class XdsClusterManagerPicker implements Picker { } interface XdsClusterManagerChild { - updateAddressList(addressList: SubchannelAddress[], childConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void; + updateAddressList(endpointList: Endpoint[], childConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void; exitIdle(): void; resetBackoff(): void; destroy(): void; @@ -142,8 +142,8 @@ class XdsClusterManager implements LoadBalancer { this.picker = picker; this.parent.maybeUpdateState(); } - updateAddressList(addressList: SubchannelAddress[], childConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { - this.childBalancer.updateAddressList(addressList, childConfig, attributes); + updateAddressList(endpointList: Endpoint[], childConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + this.childBalancer.updateAddressList(endpointList, childConfig, attributes); } exitIdle(): void { this.childBalancer.exitIdle(); @@ -235,7 +235,7 @@ class XdsClusterManager implements LoadBalancer { this.channelControlHelper.updateState(connectivityState, picker); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsClusterManagerLoadBalancingConfig)) { // Reject a config of the wrong type trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); @@ -259,7 +259,7 @@ class XdsClusterManager implements LoadBalancer { for (const [name, childConfig] of configChildren.entries()) { if (!this.children.has(name)) { const newChild = new this.XdsClusterManagerChildImpl(this, name); - newChild.updateAddressList(addressList, childConfig, attributes); + newChild.updateAddressList(endpointList, childConfig, attributes); this.children.set(name, newChild); } } diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index 6c11c52bf..752919c98 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -20,7 +20,7 @@ import { registerLoadBalancerType } from "@grpc/grpc-js/build/src/load-balancer" import { EXPERIMENTAL_OUTLIER_DETECTION } from "./environment"; import { Locality__Output } from "./generated/envoy/config/core/v3/Locality"; import { ClusterLoadAssignment__Output } from "./generated/envoy/config/endpoint/v3/ClusterLoadAssignment"; -import { LocalitySubchannelAddress, PriorityChildRaw } from "./load-balancer-priority"; +import { LocalityEndpoint, PriorityChildRaw } from "./load-balancer-priority"; import { getSingletonXdsClient, Watcher, XdsClient } from "./xds-client"; import { DropCategory } from "./load-balancer-xds-cluster-impl"; @@ -28,11 +28,13 @@ import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import Resolver = experimental.Resolver; import SubchannelAddress = experimental.SubchannelAddress; +import Endpoint = experimental.Endpoint; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; import createResolver = experimental.createResolver; import ChannelControlHelper = experimental.ChannelControlHelper; import OutlierDetectionRawConfig = experimental.OutlierDetectionRawConfig; import subchannelAddressToString = experimental.subchannelAddressToString; +import endpointToString = experimental.endpointToString; import selectLbConfigFromList = experimental.selectLbConfigFromList; import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; import UnavailablePicker = experimental.UnavailablePicker; @@ -116,7 +118,7 @@ class XdsClusterResolverLoadBalancingConfig implements TypedLoadBalancingConfig interface LocalityEntry { locality: Locality__Output; weight: number; - addresses: SubchannelAddress[]; + endpoints: Endpoint[]; } interface PriorityEntry { @@ -164,18 +166,20 @@ function getEdsPriorities(edsUpdate: ClusterLoadAssignment__Output): PriorityEnt if (!endpoint.load_balancing_weight) { continue; } - const addresses: SubchannelAddress[] = endpoint.lb_endpoints.filter(lbEndpoint => lbEndpoint.health_status === 'UNKNOWN' || lbEndpoint.health_status === 'HEALTHY').map( + const endpoints: Endpoint[] = endpoint.lb_endpoints.filter(lbEndpoint => lbEndpoint.health_status === 'UNKNOWN' || lbEndpoint.health_status === 'HEALTHY').map( (lbEndpoint) => { /* The validator in the XdsClient class ensures that each endpoint has * a socket_address with an IP address and a port_value. */ const socketAddress = lbEndpoint.endpoint!.address!.socket_address!; return { - host: socketAddress.address!, - port: socketAddress.port_value!, + addresses: [{ + host: socketAddress.address!, + port: socketAddress.port_value!, + }] }; } ); - if (addresses.length === 0) { + if (endpoints.length === 0) { continue; } let priorityEntry: PriorityEntry; @@ -190,7 +194,7 @@ function getEdsPriorities(edsUpdate: ClusterLoadAssignment__Output): PriorityEnt } priorityEntry.localities.push({ locality: endpoint.locality!, - addresses: addresses, + endpoints: endpoints, weight: endpoint.load_balancing_weight.value }); } @@ -198,7 +202,7 @@ function getEdsPriorities(edsUpdate: ClusterLoadAssignment__Output): PriorityEnt return result.filter(priority => priority); } -function getDnsPriorities(addresses: SubchannelAddress[]): PriorityEntry[] { +function getDnsPriorities(endpoints: Endpoint[]): PriorityEntry[] { return [{ localities: [{ locality: { @@ -207,7 +211,7 @@ function getDnsPriorities(addresses: SubchannelAddress[]): PriorityEntry[] { sub_zone: '' }, weight: 1, - addresses: addresses + endpoints: endpoints }], dropCategories: [] }]; @@ -249,7 +253,7 @@ export class XdsClusterResolver implements LoadBalancer { } const fullPriorityList: string[] = []; const priorityChildren: {[name: string]: PriorityChildRaw} = {}; - const addressList: LocalitySubchannelAddress[] = []; + const endpointList: LocalityEndpoint[] = []; const edsChildPolicy = this.latestConfig.getXdsLbPolicy(); for (const entry of this.discoveryMechanismList) { const newPriorityNames: string[] = []; @@ -291,15 +295,15 @@ export class XdsClusterResolver implements LoadBalancer { newPriorityNames[priority] = newPriorityName; for (const localityObj of priorityEntry.localities) { - for (const address of localityObj.addresses) { - addressList.push({ + for (const endpoint of localityObj.endpoints) { + endpointList.push({ localityPath: [ newPriorityName, localityToName(localityObj.locality), ], locality: localityObj.locality, weight: localityObj.weight, - ...address, + ...endpoint }); } newLocalityPriorities.set(localityToName(localityObj.locality), priority); @@ -349,16 +353,16 @@ export class XdsClusterResolver implements LoadBalancer { this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `LB policy config parsing failed with error ${(e as Error).message}`, metadata: new Metadata()})); return; } - trace('Child update addresses: ' + addressList.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')')); + trace('Child update addresses: ' + endpointList.map(endpoint => '(' + endpointToString(endpoint) + ' path=' + endpoint.localityPath + ')')); trace('Child update priority config: ' + JSON.stringify(childConfig, undefined, 2)); this.childBalancer.updateAddressList( - addressList, + endpointList, typedChildConfig, this.latestAttributes ); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(addressList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsClusterResolverLoadBalancingConfig)) { trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2)); return; @@ -399,8 +403,8 @@ export class XdsClusterResolver implements LoadBalancer { } } else { const resolver = createResolver({scheme: 'dns', path: mechanism.dns_hostname!}, { - onSuccessfulResolution: addressList => { - mechanismEntry.latestUpdate = getDnsPriorities(addressList); + onSuccessfulResolution: endpointList => { + mechanismEntry.latestUpdate = getDnsPriorities(endpointList); this.maybeUpdateChild(); }, onError: error => { diff --git a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts index 8636937fd..3ef789395 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts @@ -20,13 +20,13 @@ import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; import { loadProtosWithOptionsSync } from "@grpc/proto-loader/build/src/util"; import { WeightedTargetRaw } from "./load-balancer-weighted-target"; -import { isLocalitySubchannelAddress } from "./load-balancer-priority"; +import { isLocalityEndpoint } from "./load-balancer-priority"; import { localityToName } from "./load-balancer-xds-cluster-resolver"; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; -import SubchannelAddress = experimental.SubchannelAddress; +import Endpoint = experimental.Endpoint; import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; import registerLoadBalancerType = experimental.registerLoadBalancerType; import { Any__Output } from "./generated/google/protobuf/Any"; @@ -76,14 +76,14 @@ class XdsWrrLocalityLoadBalancer implements LoadBalancer { constructor(private readonly channelControlHelper: ChannelControlHelper) { this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsWrrLocalityLoadBalancingConfig)) { trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2)); return; } const targets: {[localityName: string]: WeightedTargetRaw} = {}; - for (const address of addressList) { - if (!isLocalitySubchannelAddress(address)) { + for (const address of endpointList) { + if (!isLocalityEndpoint(address)) { return; } const localityName = localityToName(address.locality); @@ -99,7 +99,7 @@ class XdsWrrLocalityLoadBalancer implements LoadBalancer { targets: targets } }; - this.childBalancer.updateAddressList(addressList, parseLoadBalancingConfig(childConfig), attributes); + this.childBalancer.updateAddressList(endpointList, parseLoadBalancingConfig(childConfig), attributes); } exitIdle(): void { this.childBalancer.exitIdle(); 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 16cca3a8a..6994bf392 100644 --- a/packages/grpc-js-xds/test/test-custom-lb-policies.ts +++ b/packages/grpc-js-xds/test/test-custom-lb-policies.ts @@ -30,7 +30,7 @@ import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler; -import SubchannelAddress = experimental.SubchannelAddress; +import Endpoint = experimental.Endpoint; import Picker = experimental.Picker; import PickArgs = experimental.PickArgs; import PickResult = experimental.PickResult; @@ -94,12 +94,12 @@ class RpcBehaviorLoadBalancer implements LoadBalancer { }); this.child = new ChildLoadBalancerHandler(childChannelControlHelper); } - updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { + updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) { return; } this.latestConfig = lbConfig; - this.child.updateAddressList(addressList, RPC_BEHAVIOR_CHILD_CONFIG, attributes); + this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, attributes); } exitIdle(): void { this.child.exitIdle(); From 3ff8b674bb6360f686636c33478a0c9ba4feadb7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 30 Aug 2023 14:57:52 -0700 Subject: [PATCH 581/694] Export HealthListener type in experimental --- packages/grpc-js/src/experimental.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 0c7bc75e1..870fbe285 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -44,6 +44,7 @@ export { SubchannelInterface, BaseSubchannelWrapper, ConnectivityStateListener, + HealthListener, } from './subchannel-interface'; export { OutlierDetectionRawConfig, From 266af4c19f766ba304213b009b7131b8efd36f4f Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 30 Aug 2023 15:16:25 -0700 Subject: [PATCH 582/694] Add pick_first tests --- packages/grpc-js/test/test-pick-first.ts | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index 7e862de58..8ab4f4d40 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -125,6 +125,54 @@ describe('pick_first load balancing policy', () => { subchannels[0].transitionToState(ConnectivityState.READY); }); }); + it('Should report READY when a subchannel other than the first connects', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.CONNECTING, ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { addresses: [{ host: 'localhost', port: 1 }] }, + { addresses: [{ host: 'localhost', port: 2 }] }, + ], + config + ); + process.nextTick(() => { + subchannels[1].transitionToState(ConnectivityState.READY); + }); + }); + it('Should report READY when a subchannel other than the first in the same endpoint connects', done => { + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.CONNECTING, ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList( + [ + { + addresses: [ + { host: 'localhost', port: 1 }, + { host: 'localhost', port: 2 }, + ], + }, + ], + config + ); + process.nextTick(() => { + subchannels[1].transitionToState(ConnectivityState.READY); + }); + }); it('Should report READY when updated with a subchannel that is already READY', done => { const channelControlHelper = createChildChannelControlHelper( baseChannelControlHelper, From 83789c15dbe9de3bc9069bc0d7c63f13d71f5b6e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 31 Aug 2023 09:30:26 -0700 Subject: [PATCH 583/694] grpc-js: Handle keepalive ping error --- packages/grpc-js/src/transport.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 18d83cbfe..49ec01ddc 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -426,6 +426,10 @@ class Http2Transport implements Transport { try { this.session!.ping( (err: Error | null, duration: number, payload: Buffer) => { + if (err) { + this.keepaliveTrace('Ping failed with error ' + err.message); + this.handleDisconnect(); + } this.keepaliveTrace('Received ping response'); this.clearKeepaliveTimeout(); this.maybeStartKeepalivePingTimer(); From f5218edf820802c5649107c018d27796b438055f Mon Sep 17 00:00:00 2001 From: gusumuzhe Date: Tue, 29 Aug 2023 17:39:38 +0800 Subject: [PATCH 584/694] fix: pick first load balancer call doPick infinite --- packages/grpc-js/src/load-balancer-pick-first.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 37bc8e0ff..0f2d7d654 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -315,7 +315,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { } private pickSubchannel(subchannel: SubchannelInterface) { - if (subchannel === this.currentPick) { + if (this.currentPick && subchannel.realSubchannelEquals(this.currentPick)) { return; } trace('Pick subchannel with address ' + subchannel.getAddress()); From 2fe961d5b10017183c231bc6161e768e6551a74a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 31 Aug 2023 09:37:34 -0700 Subject: [PATCH 585/694] grpc-js: Bump to version 1.9.2 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 196ce8f4a..974f196c4 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.1", + "version": "1.9.2", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 00e1ac46a8db0e5a6d98f0f688ade51c96c07ce5 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 6 Sep 2023 10:31:53 -0700 Subject: [PATCH 586/694] grpc-js: Pass channel options to LoadBalancer constructors --- .../grpc-js-xds/interop/xds-interop-client.ts | 4 +- packages/grpc-js-xds/src/load-balancer-cds.ts | 6 +-- .../grpc-js-xds/src/load-balancer-priority.ts | 6 +-- .../src/load-balancer-weighted-target.ts | 6 +-- .../src/load-balancer-xds-cluster-impl.ts | 6 +-- .../src/load-balancer-xds-cluster-manager.ts | 6 +-- .../src/load-balancer-xds-cluster-resolver.ts | 6 +-- .../src/load-balancer-xds-wrr-locality.ts | 6 +-- .../test/test-custom-lb-policies.ts | 6 +-- .../src/load-balancer-child-handler.ts | 7 ++- .../src/load-balancer-outlier-detection.ts | 8 ++- .../grpc-js/src/load-balancer-pick-first.ts | 14 ++++-- .../grpc-js/src/load-balancer-round-robin.ts | 13 ++++- packages/grpc-js/src/load-balancer.ts | 11 +++-- .../grpc-js/src/resolving-load-balancer.ts | 49 ++++++++++--------- packages/grpc-js/test/test-pick-first.ts | 34 ++++++------- 16 files changed, 110 insertions(+), 78 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index dc70034f9..f26278abb 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -88,7 +88,7 @@ const RPC_BEHAVIOR_CHILD_CONFIG = parseLoadBalancingConfig({round_robin: {}}); class RpcBehaviorLoadBalancer implements LoadBalancer { private child: ChildLoadBalancerHandler; private latestConfig: RpcBehaviorLoadBalancingConfig | null = null; - constructor(channelControlHelper: ChannelControlHelper) { + constructor(channelControlHelper: ChannelControlHelper, options: grpc.ChannelOptions) { const childChannelControlHelper = createChildChannelControlHelper(channelControlHelper, { updateState: (connectivityState, picker) => { if (connectivityState === grpc.connectivityState.READY && this.latestConfig) { @@ -97,7 +97,7 @@ class RpcBehaviorLoadBalancer implements LoadBalancer { channelControlHelper.updateState(connectivityState, picker); } }); - this.child = new ChildLoadBalancerHandler(childChannelControlHelper); + this.child = new ChildLoadBalancerHandler(childChannelControlHelper, options); } updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) { diff --git a/packages/grpc-js-xds/src/load-balancer-cds.ts b/packages/grpc-js-xds/src/load-balancer-cds.ts index 1e4f63b4b..bebca6fe3 100644 --- a/packages/grpc-js-xds/src/load-balancer-cds.ts +++ b/packages/grpc-js-xds/src/load-balancer-cds.ts @@ -15,7 +15,7 @@ * */ -import { connectivityState, status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from '@grpc/grpc-js'; +import { connectivityState, status, Metadata, logVerbosity, experimental, LoadBalancingConfig, ChannelOptions } from '@grpc/grpc-js'; import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client'; import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster'; import Endpoint = experimental.Endpoint; @@ -155,8 +155,8 @@ export class CdsLoadBalancer implements LoadBalancer { private updatedChild = false; - constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.childBalancer = new XdsClusterResolverChildPolicyHandler(channelControlHelper); + constructor(private readonly channelControlHelper: ChannelControlHelper, options: ChannelOptions) { + this.childBalancer = new XdsClusterResolverChildPolicyHandler(channelControlHelper, options); } private reportError(errorMessage: string) { diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index ba4fd2cfa..4a4acb477 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -15,7 +15,7 @@ * */ -import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, LoadBalancingConfig } from '@grpc/grpc-js'; +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity as LogVerbosity, experimental, LoadBalancingConfig, ChannelOptions } from '@grpc/grpc-js'; import LoadBalancer = experimental.LoadBalancer; import ChannelControlHelper = experimental.ChannelControlHelper; import registerLoadBalancerType = experimental.registerLoadBalancerType; @@ -180,7 +180,7 @@ export class PriorityLoadBalancer implements LoadBalancer { this.parent.channelControlHelper.requestReresolution(); } } - })); + }), parent.options); this.picker = new QueuePicker(this.childBalancer); this.startFailoverTimer(); } @@ -306,7 +306,7 @@ export class PriorityLoadBalancer implements LoadBalancer { private updatesPaused = false; - constructor(private channelControlHelper: ChannelControlHelper) {} + constructor(private channelControlHelper: ChannelControlHelper, private options: ChannelOptions) {} private updateState(state: ConnectivityState, picker: Picker) { trace( diff --git a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts index 16874e01b..89192b622 100644 --- a/packages/grpc-js-xds/src/load-balancer-weighted-target.ts +++ b/packages/grpc-js-xds/src/load-balancer-weighted-target.ts @@ -15,7 +15,7 @@ * */ -import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from "@grpc/grpc-js"; +import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental, LoadBalancingConfig, ChannelOptions } from "@grpc/grpc-js"; import { isLocalityEndpoint, LocalityEndpoint } from "./load-balancer-priority"; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; @@ -178,7 +178,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.updateState(connectivityState, picker); }, - })); + }), parent.options); this.picker = new QueuePicker(this.childBalancer); } @@ -243,7 +243,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer { private targetList: string[] = []; private updatesPaused = false; - constructor(private channelControlHelper: ChannelControlHelper) {} + constructor(private channelControlHelper: ChannelControlHelper, private options: ChannelOptions) {} private maybeUpdateState() { if (!this.updatesPaused) { diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts index 81e365624..f163b6fe2 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts @@ -15,7 +15,7 @@ * */ -import { experimental, logVerbosity, status as Status, Metadata, connectivityState } from "@grpc/grpc-js"; +import { experimental, logVerbosity, status as Status, Metadata, connectivityState, ChannelOptions } from "@grpc/grpc-js"; import { validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap"; import { getSingletonXdsClient, XdsClient, XdsClusterDropStats, XdsClusterLocalityStats } from "./xds-client"; import { LocalityEndpoint } from "./load-balancer-priority"; @@ -253,7 +253,7 @@ class XdsClusterImplBalancer implements LoadBalancer { private clusterDropStats: XdsClusterDropStats | null = null; private xdsClient: XdsClient | null = null; - constructor(private readonly channelControlHelper: ChannelControlHelper) { + constructor(private readonly channelControlHelper: ChannelControlHelper, options: ChannelOptions) { this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, { createSubchannel: (subchannelAddress, subchannelArgs) => { if (!this.xdsClient || !this.latestConfig || !this.lastestEndpointList) { @@ -290,7 +290,7 @@ class XdsClusterImplBalancer implements LoadBalancer { channelControlHelper.updateState(connectivityState, picker); } } - })); + }), options); } updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsClusterImplLoadBalancingConfig)) { diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index a1855d142..3560d8481 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -15,7 +15,7 @@ * */ -import { connectivityState as ConnectivityState, status as Status, experimental, logVerbosity, Metadata, status } from "@grpc/grpc-js/"; +import { connectivityState as ConnectivityState, status as Status, experimental, logVerbosity, Metadata, status, ChannelOptions } from "@grpc/grpc-js/"; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; @@ -131,7 +131,7 @@ class XdsClusterManager implements LoadBalancer { updateState: (connectivityState: ConnectivityState, picker: Picker) => { this.updateState(connectivityState, picker); }, - })); + }), parent.options); this.picker = new QueuePicker(this.childBalancer); } @@ -167,7 +167,7 @@ class XdsClusterManager implements LoadBalancer { // Shutdown is a placeholder value that will never appear in normal operation. private currentState: ConnectivityState = ConnectivityState.SHUTDOWN; private updatesPaused = false; - constructor(private channelControlHelper: ChannelControlHelper) {} + constructor(private channelControlHelper: ChannelControlHelper, private options: ChannelOptions) {} private maybeUpdateState() { if (!this.updatesPaused) { diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index 752919c98..c4bf984f7 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -15,7 +15,7 @@ * */ -import { LoadBalancingConfig, Metadata, connectivityState, experimental, logVerbosity, status } from "@grpc/grpc-js"; +import { ChannelOptions, LoadBalancingConfig, Metadata, connectivityState, experimental, logVerbosity, status } from "@grpc/grpc-js"; import { registerLoadBalancerType } from "@grpc/grpc-js/build/src/load-balancer"; import { EXPERIMENTAL_OUTLIER_DETECTION } from "./environment"; import { Locality__Output } from "./generated/envoy/config/core/v3/Locality"; @@ -232,14 +232,14 @@ export class XdsClusterResolver implements LoadBalancer { private xdsClient: XdsClient | null = null; private childBalancer: ChildLoadBalancerHandler; - constructor(private readonly channelControlHelper: ChannelControlHelper) { + constructor(private readonly channelControlHelper: ChannelControlHelper, options: ChannelOptions) { this.childBalancer = new ChildLoadBalancerHandler(experimental.createChildChannelControlHelper(channelControlHelper, { requestReresolution: () => { for (const entry of this.discoveryMechanismList) { entry.resolver?.updateResolution(); } } - })); + }), options); } private maybeUpdateChild() { diff --git a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts index 3ef789395..f3fbcd513 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts @@ -17,7 +17,7 @@ // https://github.com/grpc/proposal/blob/master/A52-xds-custom-lb-policies.md -import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; +import { ChannelOptions, LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; import { loadProtosWithOptionsSync } from "@grpc/proto-loader/build/src/util"; import { WeightedTargetRaw } from "./load-balancer-weighted-target"; import { isLocalityEndpoint } from "./load-balancer-priority"; @@ -73,8 +73,8 @@ class XdsWrrLocalityLoadBalancingConfig implements TypedLoadBalancingConfig { class XdsWrrLocalityLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; - constructor(private readonly channelControlHelper: ChannelControlHelper) { - this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper); + constructor(private readonly channelControlHelper: ChannelControlHelper, options: ChannelOptions) { + this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper, options); } updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof XdsWrrLocalityLoadBalancingConfig)) { 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 6994bf392..6da6fecc8 100644 --- a/packages/grpc-js-xds/test/test-custom-lb-policies.ts +++ b/packages/grpc-js-xds/test/test-custom-lb-policies.ts @@ -24,7 +24,7 @@ import { XdsServer } from "./xds-server"; import * as assert from 'assert'; import { WrrLocality } from "../src/generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality"; import { TypedStruct } from "../src/generated/xds/type/v3/TypedStruct"; -import { connectivityState, experimental, logVerbosity } from "@grpc/grpc-js"; +import { ChannelOptions, connectivityState, experimental, logVerbosity } from "@grpc/grpc-js"; import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; import LoadBalancer = experimental.LoadBalancer; @@ -83,7 +83,7 @@ const RPC_BEHAVIOR_CHILD_CONFIG = parseLoadBalancingConfig({round_robin: {}}); class RpcBehaviorLoadBalancer implements LoadBalancer { private child: ChildLoadBalancerHandler; private latestConfig: RpcBehaviorLoadBalancingConfig | null = null; - constructor(channelControlHelper: ChannelControlHelper) { + constructor(channelControlHelper: ChannelControlHelper, options: ChannelOptions) { const childChannelControlHelper = createChildChannelControlHelper(channelControlHelper, { updateState: (state, picker) => { if (state === connectivityState.READY && this.latestConfig) { @@ -92,7 +92,7 @@ class RpcBehaviorLoadBalancer implements LoadBalancer { channelControlHelper.updateState(state, picker); } }); - this.child = new ChildLoadBalancerHandler(childChannelControlHelper); + this.child = new ChildLoadBalancerHandler(childChannelControlHelper, options); } updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) { diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index 11bfac211..a29d6c92b 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -84,7 +84,10 @@ export class ChildLoadBalancerHandler implements LoadBalancer { } }; - constructor(private readonly channelControlHelper: ChannelControlHelper) {} + constructor( + private readonly channelControlHelper: ChannelControlHelper, + private readonly options: ChannelOptions + ) {} protected configUpdateRequiresNewPolicyInstance( oldConfig: TypedLoadBalancingConfig, @@ -111,7 +114,7 @@ export class ChildLoadBalancerHandler implements LoadBalancer { this.configUpdateRequiresNewPolicyInstance(this.latestConfig, lbConfig) ) { const newHelper = new this.ChildPolicyHelper(this); - const newChild = createLoadBalancer(lbConfig, newHelper)!; + const newChild = createLoadBalancer(lbConfig, newHelper, this.options)!; newHelper.setChild(newChild); if (this.currentChild === null) { this.currentChild = newChild; diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index b0c648efd..8e0513611 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -585,7 +585,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { private ejectionTimer: NodeJS.Timeout; private timerStartTime: Date | null = null; - constructor(channelControlHelper: ChannelControlHelper) { + constructor( + channelControlHelper: ChannelControlHelper, + options: ChannelOptions + ) { this.childBalancer = new ChildLoadBalancerHandler( createChildChannelControlHelper(channelControlHelper, { createSubchannel: ( @@ -619,7 +622,8 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer { channelControlHelper.updateState(connectivityState, picker); } }, - }) + }), + options ); this.ejectionTimer = setInterval(() => {}, 0); clearInterval(this.ejectionTimer); diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 2cf108675..9af662662 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -42,6 +42,7 @@ import { } from './subchannel-interface'; import { isTcpSubchannelAddress } from './subchannel-address'; import { isIPv6 } from 'net'; +import { ChannelOptions } from './channel-options'; const TRACER_NAME = 'pick_first'; @@ -162,6 +163,9 @@ function interleaveAddressFamilies( return result; } +const REPORT_HEALTH_STATUS_OPTION_NAME = + 'grpc-node.internal.pick-first.report_health_status'; + export class PickFirstLoadBalancer implements LoadBalancer { /** * The list of subchannels this load balancer is currently attempting to @@ -212,6 +216,8 @@ export class PickFirstLoadBalancer implements LoadBalancer { */ private stickyTransientFailureMode = false; + private reportHealthStatus: boolean; + /** * Load balancer that attempts to connect to each backend in the address list * in order, and picks the first one that connects, using it for every @@ -221,10 +227,11 @@ export class PickFirstLoadBalancer implements LoadBalancer { */ constructor( private readonly channelControlHelper: ChannelControlHelper, - private reportHealthStatus = false + options: ChannelOptions ) { this.connectionDelayTimeout = setTimeout(() => {}, 0); clearTimeout(this.connectionDelayTimeout); + this.reportHealthStatus = options[REPORT_HEALTH_STATUS_OPTION_NAME]; } private allChildrenHaveReportedTF(): boolean { @@ -510,7 +517,8 @@ export class LeafLoadBalancer { private latestPicker: Picker; constructor( private endpoint: Endpoint, - channelControlHelper: ChannelControlHelper + channelControlHelper: ChannelControlHelper, + options: ChannelOptions ) { const childChannelControlHelper = createChildChannelControlHelper( channelControlHelper, @@ -524,7 +532,7 @@ export class LeafLoadBalancer { ); this.pickFirstBalancer = new PickFirstLoadBalancer( childChannelControlHelper, - /* reportHealthStatus= */ true + { ...options, [REPORT_HEALTH_STATUS_OPTION_NAME]: true } ); this.latestPicker = new QueuePicker(this.pickFirstBalancer); } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 986181a84..9f093596a 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -38,6 +38,7 @@ import { endpointToString, } from './subchannel-address'; import { LeafLoadBalancer } from './load-balancer-pick-first'; +import { ChannelOptions } from './channel-options'; const TRACER_NAME = 'round_robin'; @@ -99,7 +100,10 @@ export class RoundRobinLoadBalancer implements LoadBalancer { private childChannelControlHelper: ChannelControlHelper; - constructor(private readonly channelControlHelper: ChannelControlHelper) { + constructor( + private readonly channelControlHelper: ChannelControlHelper, + private readonly options: ChannelOptions + ) { this.childChannelControlHelper = createChildChannelControlHelper( channelControlHelper, { @@ -186,7 +190,12 @@ export class RoundRobinLoadBalancer implements LoadBalancer { trace('Connect to endpoint list ' + endpointList.map(endpointToString)); this.updatesPaused = true; this.children = endpointList.map( - endpoint => new LeafLoadBalancer(endpoint, this.childChannelControlHelper) + endpoint => + new LeafLoadBalancer( + endpoint, + this.childChannelControlHelper, + this.options + ) ); for (const child of this.children) { child.startConnecting(); diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index 1145fdc90..f8071317a 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -128,7 +128,10 @@ export interface LoadBalancer { } export interface LoadBalancerConstructor { - new (channelControlHelper: ChannelControlHelper): LoadBalancer; + new ( + channelControlHelper: ChannelControlHelper, + options: ChannelOptions + ): LoadBalancer; } export interface TypedLoadBalancingConfig { @@ -169,12 +172,14 @@ export function registerDefaultLoadBalancerType(typeName: string) { export function createLoadBalancer( config: TypedLoadBalancingConfig, - channelControlHelper: ChannelControlHelper + channelControlHelper: ChannelControlHelper, + options: ChannelOptions ): LoadBalancer | null { const typeName = config.getLoadBalancerName(); if (typeName in registeredLoadBalancerTypes) { return new registeredLoadBalancerTypes[typeName].LoadBalancer( - channelControlHelper + channelControlHelper, + options ); } else { return null; diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 22808dc32..df480400a 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -149,30 +149,33 @@ export class ResolvingLoadBalancer implements LoadBalancer { }; } this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); - this.childLoadBalancer = new ChildLoadBalancerHandler({ - createSubchannel: - channelControlHelper.createSubchannel.bind(channelControlHelper), - requestReresolution: () => { - /* If the backoffTimeout is running, we're still backing off from - * making resolve requests, so we shouldn't make another one here. - * In that case, the backoff timer callback will call - * updateResolution */ - if (this.backoffTimeout.isRunning()) { - this.continueResolving = true; - } else { - this.updateResolution(); - } - }, - updateState: (newState: ConnectivityState, picker: Picker) => { - this.latestChildState = newState; - this.latestChildPicker = picker; - this.updateState(newState, picker); + this.childLoadBalancer = new ChildLoadBalancerHandler( + { + createSubchannel: + channelControlHelper.createSubchannel.bind(channelControlHelper), + requestReresolution: () => { + /* If the backoffTimeout is running, we're still backing off from + * making resolve requests, so we shouldn't make another one here. + * In that case, the backoff timer callback will call + * updateResolution */ + if (this.backoffTimeout.isRunning()) { + this.continueResolving = true; + } else { + this.updateResolution(); + } + }, + updateState: (newState: ConnectivityState, picker: Picker) => { + this.latestChildState = newState; + this.latestChildPicker = picker; + this.updateState(newState, picker); + }, + addChannelzChild: + channelControlHelper.addChannelzChild.bind(channelControlHelper), + removeChannelzChild: + channelControlHelper.removeChannelzChild.bind(channelControlHelper), }, - addChannelzChild: - channelControlHelper.addChannelzChild.bind(channelControlHelper), - removeChannelzChild: - channelControlHelper.removeChannelzChild.bind(channelControlHelper), - }); + channelOptions + ); this.innerResolver = createResolver( target, { diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index 8ab4f4d40..18dbf9d2d 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -116,7 +116,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [{ addresses: [{ host: 'localhost', port: 1 }] }], config @@ -135,7 +135,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -157,7 +157,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { @@ -191,7 +191,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [{ addresses: [{ host: 'localhost', port: 1 }] }], config @@ -207,7 +207,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -229,7 +229,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -254,7 +254,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -293,7 +293,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -320,7 +320,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -351,7 +351,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -389,7 +389,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [ { addresses: [{ host: 'localhost', port: 1 }] }, @@ -424,7 +424,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [{ addresses: [{ host: 'localhost', port: 1 }] }], config @@ -452,7 +452,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [{ addresses: [{ host: 'localhost', port: 1 }] }], config @@ -487,7 +487,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [{ addresses: [{ host: 'localhost', port: 1 }] }], config @@ -522,7 +522,7 @@ describe('pick_first load balancing policy', () => { ), } ); - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList( [{ addresses: [{ host: 'localhost', port: 1 }] }], config @@ -569,7 +569,7 @@ describe('pick_first load balancing policy', () => { for (let i = 0; i < 10; i++) { endpoints.push({ addresses: [{ host: 'localhost', port: i + 1 }] }); } - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); /* Pick from 10 subchannels 5 times, with address randomization enabled, * and verify that at least two different subchannels are picked. The * probability choosing the same address every time is 1/10,000, which @@ -625,7 +625,7 @@ describe('pick_first load balancing policy', () => { for (let i = 0; i < 10; i++) { endpoints.push({ addresses: [{ host: 'localhost', port: i + 1 }] }); } - const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); pickFirst.updateAddressList(endpoints, config); process.nextTick(() => { pickFirst.updateAddressList(endpoints, config); From 3096f22ba6333f33a4e1299733345b496876f951 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Sep 2023 17:12:58 -0700 Subject: [PATCH 587/694] grpc-js-xds: Add xxhash-wasm dependency, generate ring_hash code --- packages/grpc-js-xds/package.json | 7 +- .../common/v3/ConsistentHashingLbConfig.ts | 67 +++++++ .../common/v3/LocalityLbConfig.ts | 101 ++++++++++ .../common/v3/SlowStartConfig.ts | 71 ++++++++ .../ring_hash/v3/RingHash.ts | 163 +++++++++++++++++ .../generated/google/protobuf/FieldOptions.ts | 5 - .../grpc-js-xds/src/generated/ring_hash.ts | 172 ++++++++++++++++++ 7 files changed, 578 insertions(+), 8 deletions(-) create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/ConsistentHashingLbConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/SlowStartConfig.ts create mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts create mode 100644 packages/grpc-js-xds/src/generated/ring_hash.ts diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index b43304cea..c240bfee5 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", + "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-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" }, @@ -39,14 +39,15 @@ "@types/node": "^13.11.1", "@types/yargs": "^15.0.5", "gts": "^2.0.2", - "typescript": "^3.8.3", + "typescript": "^4.9.5", "yargs": "^15.4.1" }, "dependencies": { "@grpc/proto-loader": "^0.6.0", "google-auth-library": "^7.0.2", "re2-wasm": "^1.0.1", - "vscode-uri": "^3.0.7" + "vscode-uri": "^3.0.7", + "xxhash-wasm": "^1.0.2" }, "peerDependencies": { "@grpc/grpc-js": "~1.8.0" diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/ConsistentHashingLbConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/ConsistentHashingLbConfig.ts new file mode 100644 index 000000000..c216720f1 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/ConsistentHashingLbConfig.ts @@ -0,0 +1,67 @@ +// Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/common/v3/common.proto + +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../google/protobuf/UInt32Value'; + +/** + * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) + */ +export interface ConsistentHashingLbConfig { + /** + * If set to ``true``, the cluster will use hostname instead of the resolved + * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. + */ + 'use_hostname_for_hashing'?: (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * Applies to both Ring Hash and Maglev load balancers. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * ``hash_balance_factor``, requests to any upstream host are capped at ``hash_balance_factor/100`` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower ``hash_balance_factor`` results in more hosts + * being probed, so use a higher value if you require better performance. + */ + 'hash_balance_factor'?: (_google_protobuf_UInt32Value | null); +} + +/** + * Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) + */ +export interface ConsistentHashingLbConfig__Output { + /** + * If set to ``true``, the cluster will use hostname instead of the resolved + * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. + */ + 'use_hostname_for_hashing': (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * Applies to both Ring Hash and Maglev load balancers. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * ``hash_balance_factor``, requests to any upstream host are capped at ``hash_balance_factor/100`` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower ``hash_balance_factor`` results in more hosts + * being probed, so use a higher value if you require better performance. + */ + 'hash_balance_factor': (_google_protobuf_UInt32Value__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts new file mode 100644 index 000000000..d6fecd36b --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts @@ -0,0 +1,101 @@ +// Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/common/v3/common.proto + +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../../envoy/type/v3/Percent'; +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../../google/protobuf/UInt64Value'; +import type { Long } from '@grpc/proto-loader'; + +/** + * Configuration for :ref:`locality weighted load balancing + * ` + */ +export interface _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig { +} + +/** + * Configuration for :ref:`locality weighted load balancing + * ` + */ +export interface _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig__Output { +} + +/** + * Configuration for :ref:`zone aware routing + * `. + */ +export interface _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_ZoneAwareLbConfig { + /** + * Configures percentage of requests that will be considered for zone aware routing + * if zone aware routing is configured. If not specified, the default is 100%. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'routing_enabled'?: (_envoy_type_v3_Percent | null); + /** + * Configures minimum upstream cluster size required for zone aware routing + * If upstream cluster size is less than specified, zone aware routing is not performed + * even if zone aware routing is configured. If not specified, the default is 6. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'min_cluster_size'?: (_google_protobuf_UInt64Value | null); + /** + * If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic + * mode`. Instead, the cluster will fail all + * requests as if all hosts are unhealthy. This can help avoid potentially overwhelming a + * failing service. + */ + 'fail_traffic_on_panic'?: (boolean); +} + +/** + * Configuration for :ref:`zone aware routing + * `. + */ +export interface _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_ZoneAwareLbConfig__Output { + /** + * Configures percentage of requests that will be considered for zone aware routing + * if zone aware routing is configured. If not specified, the default is 100%. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'routing_enabled': (_envoy_type_v3_Percent__Output | null); + /** + * Configures minimum upstream cluster size required for zone aware routing + * If upstream cluster size is less than specified, zone aware routing is not performed + * even if zone aware routing is configured. If not specified, the default is 6. + * * :ref:`runtime values `. + * * :ref:`Zone aware routing support `. + */ + 'min_cluster_size': (_google_protobuf_UInt64Value__Output | null); + /** + * If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic + * mode`. Instead, the cluster will fail all + * requests as if all hosts are unhealthy. This can help avoid potentially overwhelming a + * failing service. + */ + 'fail_traffic_on_panic': (boolean); +} + +export interface LocalityLbConfig { + /** + * Configuration for local zone aware load balancing. + */ + 'zone_aware_lb_config'?: (_envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_ZoneAwareLbConfig | null); + /** + * Enable locality weighted load balancing. + */ + 'locality_weighted_lb_config'?: (_envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig | null); + 'locality_config_specifier'?: "zone_aware_lb_config"|"locality_weighted_lb_config"; +} + +export interface LocalityLbConfig__Output { + /** + * Configuration for local zone aware load balancing. + */ + 'zone_aware_lb_config'?: (_envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_ZoneAwareLbConfig__Output | null); + /** + * Enable locality weighted load balancing. + */ + 'locality_weighted_lb_config'?: (_envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig__Output | null); + 'locality_config_specifier': "zone_aware_lb_config"|"locality_weighted_lb_config"; +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/SlowStartConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/SlowStartConfig.ts new file mode 100644 index 000000000..bc222ff89 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/SlowStartConfig.ts @@ -0,0 +1,71 @@ +// Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/common/v3/common.proto + +import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../../google/protobuf/Duration'; +import type { RuntimeDouble as _envoy_config_core_v3_RuntimeDouble, RuntimeDouble__Output as _envoy_config_core_v3_RuntimeDouble__Output } from '../../../../../envoy/config/core/v3/RuntimeDouble'; +import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../../envoy/type/v3/Percent'; + +/** + * Configuration for :ref:`slow start mode `. + */ +export interface SlowStartConfig { + /** + * Represents the size of slow start window. + * If set, the newly created host remains in slow start mode starting from its creation time + * for the duration of slow start window. + */ + 'slow_start_window'?: (_google_protobuf_Duration | null); + /** + * This parameter controls the speed of traffic increase over the slow start window. Defaults to 1.0, + * so that endpoint would get linearly increasing amount of traffic. + * When increasing the value for this parameter, the speed of traffic ramp-up increases non-linearly. + * The value of aggression parameter should be greater than 0.0. + * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. + * + * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: + * ``new_weight = weight * max(min_weight_percent, time_factor ^ (1 / aggression))``, + * where ``time_factor=(time_since_start_seconds / slow_start_time_seconds)``. + * + * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. + * Once host exits slow start, time_factor and aggression no longer affect its weight. + */ + 'aggression'?: (_envoy_config_core_v3_RuntimeDouble | null); + /** + * Configures the minimum percentage of origin weight that avoids too small new weight, + * which may cause endpoints in slow start mode receive no traffic in slow start window. + * If not specified, the default is 10%. + */ + 'min_weight_percent'?: (_envoy_type_v3_Percent | null); +} + +/** + * Configuration for :ref:`slow start mode `. + */ +export interface SlowStartConfig__Output { + /** + * Represents the size of slow start window. + * If set, the newly created host remains in slow start mode starting from its creation time + * for the duration of slow start window. + */ + 'slow_start_window': (_google_protobuf_Duration__Output | null); + /** + * This parameter controls the speed of traffic increase over the slow start window. Defaults to 1.0, + * so that endpoint would get linearly increasing amount of traffic. + * When increasing the value for this parameter, the speed of traffic ramp-up increases non-linearly. + * The value of aggression parameter should be greater than 0.0. + * By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. + * + * During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: + * ``new_weight = weight * max(min_weight_percent, time_factor ^ (1 / aggression))``, + * where ``time_factor=(time_since_start_seconds / slow_start_time_seconds)``. + * + * As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. + * Once host exits slow start, time_factor and aggression no longer affect its weight. + */ + 'aggression': (_envoy_config_core_v3_RuntimeDouble__Output | null); + /** + * Configures the minimum percentage of origin weight that avoids too small new weight, + * which may cause endpoints in slow start mode receive no traffic in slow start window. + * If not specified, the default is 10%. + */ + 'min_weight_percent': (_envoy_type_v3_Percent__Output | null); +} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts new file mode 100644 index 000000000..4e2a73031 --- /dev/null +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts @@ -0,0 +1,163 @@ +// Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto + +import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../../google/protobuf/UInt64Value'; +import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../google/protobuf/UInt32Value'; +import type { ConsistentHashingLbConfig as _envoy_extensions_load_balancing_policies_common_v3_ConsistentHashingLbConfig, ConsistentHashingLbConfig__Output as _envoy_extensions_load_balancing_policies_common_v3_ConsistentHashingLbConfig__Output } from '../../../../../envoy/extensions/load_balancing_policies/common/v3/ConsistentHashingLbConfig'; +import type { _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig, _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig__Output } from '../../../../../envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig'; +import type { Long } from '@grpc/proto-loader'; + +// Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto + +/** + * The hash function used to hash hosts onto the ketama ring. + */ +export enum _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction { + /** + * Currently defaults to XX_HASH. + */ + DEFAULT_HASH = 0, + /** + * Use `xxHash `_. + */ + XX_HASH = 1, + /** + * Use `MurmurHash2 `_, this is compatible with + * std:hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled + * on Linux and not macOS. + */ + MURMUR_HASH_2 = 2, +} + +/** + * This configuration allows the built-in RING_HASH LB policy to be configured via the LB policy + * extension point. See the :ref:`load balancing architecture overview + * ` for more information. + * [#next-free-field: 8] + */ +export interface RingHash { + /** + * The hash function used to hash hosts onto the ketama ring. The value defaults to + * :ref:`XX_HASH`. + */ + 'hash_function'?: (_envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction | keyof typeof _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction); + /** + * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each + * provided host) the better the request distribution will reflect the desired weights. Defaults + * to 1024 entries, and limited to 8M entries. See also + * :ref:`maximum_ring_size`. + */ + 'minimum_ring_size'?: (_google_protobuf_UInt64Value | null); + /** + * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered + * to further constrain resource use. See also + * :ref:`minimum_ring_size`. + */ + 'maximum_ring_size'?: (_google_protobuf_UInt64Value | null); + /** + * If set to `true`, the cluster will use hostname instead of the resolved + * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. + * + * ..note:: + * This is deprecated and please use :ref:`consistent_hashing_lb_config + * ` instead. + */ + 'use_hostname_for_hashing'?: (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * being probed, so use a higher value if you require better performance. + * + * ..note:: + * This is deprecated and please use :ref:`consistent_hashing_lb_config + * ` instead. + */ + 'hash_balance_factor'?: (_google_protobuf_UInt32Value | null); + /** + * Common configuration for hashing-based load balancing policies. + */ + 'consistent_hashing_lb_config'?: (_envoy_extensions_load_balancing_policies_common_v3_ConsistentHashingLbConfig | null); + /** + * Enable locality weighted load balancing for ring hash lb explicitly. + */ + 'locality_weighted_lb_config'?: (_envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig | null); +} + +/** + * This configuration allows the built-in RING_HASH LB policy to be configured via the LB policy + * extension point. See the :ref:`load balancing architecture overview + * ` for more information. + * [#next-free-field: 8] + */ +export interface RingHash__Output { + /** + * The hash function used to hash hosts onto the ketama ring. The value defaults to + * :ref:`XX_HASH`. + */ + 'hash_function': (keyof typeof _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction); + /** + * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each + * provided host) the better the request distribution will reflect the desired weights. Defaults + * to 1024 entries, and limited to 8M entries. See also + * :ref:`maximum_ring_size`. + */ + 'minimum_ring_size': (_google_protobuf_UInt64Value__Output | null); + /** + * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered + * to further constrain resource use. See also + * :ref:`minimum_ring_size`. + */ + 'maximum_ring_size': (_google_protobuf_UInt64Value__Output | null); + /** + * If set to `true`, the cluster will use hostname instead of the resolved + * address as the key to consistently hash to an upstream host. Only valid for StrictDNS clusters with hostnames which resolve to a single IP address. + * + * ..note:: + * This is deprecated and please use :ref:`consistent_hashing_lb_config + * ` instead. + */ + 'use_hostname_for_hashing': (boolean); + /** + * Configures percentage of average cluster load to bound per upstream host. For example, with a value of 150 + * no upstream host will get a load more than 1.5 times the average load of all the hosts in the cluster. + * If not specified, the load is not bounded for any upstream host. Typical value for this parameter is between 120 and 200. + * Minimum is 100. + * + * This is implemented based on the method described in the paper https://arxiv.org/abs/1608.01350. For the specified + * `hash_balance_factor`, requests to any upstream host are capped at `hash_balance_factor/100` times the average number of requests + * across the cluster. When a request arrives for an upstream host that is currently serving at its max capacity, linear probing + * is used to identify an eligible host. Further, the linear probe is implemented using a random jump in hosts ring/table to identify + * the eligible host (this technique is as described in the paper https://arxiv.org/abs/1908.08762 - the random jump avoids the + * cascading overflow effect when choosing the next host in the ring/table). + * + * If weights are specified on the hosts, they are respected. + * + * This is an O(N) algorithm, unlike other load balancers. Using a lower `hash_balance_factor` results in more hosts + * being probed, so use a higher value if you require better performance. + * + * ..note:: + * This is deprecated and please use :ref:`consistent_hashing_lb_config + * ` instead. + */ + 'hash_balance_factor': (_google_protobuf_UInt32Value__Output | null); + /** + * Common configuration for hashing-based load balancing policies. + */ + 'consistent_hashing_lb_config': (_envoy_extensions_load_balancing_policies_common_v3_ConsistentHashingLbConfig__Output | null); + /** + * Enable locality weighted load balancing for ring hash lb explicitly. + */ + 'locality_weighted_lb_config': (_envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig__Output | null); +} 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 d62db88d0..f59acfbfe 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -2,7 +2,6 @@ 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 { FieldSecurityAnnotation as _udpa_annotations_FieldSecurityAnnotation, FieldSecurityAnnotation__Output as _udpa_annotations_FieldSecurityAnnotation__Output } from '../../udpa/annotations/FieldSecurityAnnotation'; 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'; @@ -31,8 +30,6 @@ export interface FieldOptions { 'weak'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; '.validate.rules'?: (_validate_FieldRules | null); - '.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation | null); - '.udpa.annotations.sensitive'?: (boolean); '.envoy.annotations.deprecated_at_minor_version'?: (string); '.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation | null); '.envoy.annotations.disallowed_by_default'?: (boolean); @@ -48,8 +45,6 @@ export interface FieldOptions__Output { 'weak': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; '.validate.rules': (_validate_FieldRules__Output | null); - '.udpa.annotations.security': (_udpa_annotations_FieldSecurityAnnotation__Output | null); - '.udpa.annotations.sensitive': (boolean); '.envoy.annotations.deprecated_at_minor_version': (string); '.udpa.annotations.field_migrate': (_udpa_annotations_FieldMigrateAnnotation__Output | null); '.envoy.annotations.disallowed_by_default': (boolean); diff --git a/packages/grpc-js-xds/src/generated/ring_hash.ts b/packages/grpc-js-xds/src/generated/ring_hash.ts new file mode 100644 index 000000000..d298067df --- /dev/null +++ b/packages/grpc-js-xds/src/generated/ring_hash.ts @@ -0,0 +1,172 @@ +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: { + annotations: { + } + config: { + core: { + v3: { + Address: MessageTypeDefinition + AsyncDataSource: MessageTypeDefinition + BackoffStrategy: MessageTypeDefinition + BindConfig: MessageTypeDefinition + BuildVersion: MessageTypeDefinition + CidrRange: MessageTypeDefinition + ControlPlane: MessageTypeDefinition + DataSource: MessageTypeDefinition + EnvoyInternalAddress: MessageTypeDefinition + Extension: MessageTypeDefinition + ExtraSourceAddress: MessageTypeDefinition + HeaderMap: MessageTypeDefinition + HeaderValue: MessageTypeDefinition + HeaderValueOption: MessageTypeDefinition + HttpUri: MessageTypeDefinition + Locality: MessageTypeDefinition + Metadata: MessageTypeDefinition + Node: MessageTypeDefinition + Pipe: MessageTypeDefinition + QueryParameter: MessageTypeDefinition + RemoteDataSource: MessageTypeDefinition + RequestMethod: EnumTypeDefinition + RetryPolicy: MessageTypeDefinition + RoutingPriority: EnumTypeDefinition + RuntimeDouble: MessageTypeDefinition + RuntimeFeatureFlag: MessageTypeDefinition + RuntimeFractionalPercent: MessageTypeDefinition + RuntimePercent: MessageTypeDefinition + RuntimeUInt32: MessageTypeDefinition + SocketAddress: MessageTypeDefinition + SocketOption: MessageTypeDefinition + SocketOptionsOverride: MessageTypeDefinition + TcpKeepalive: MessageTypeDefinition + TrafficDirection: EnumTypeDefinition + TransportSocket: MessageTypeDefinition + WatchedDirectory: MessageTypeDefinition + } + } + } + extensions: { + load_balancing_policies: { + common: { + v3: { + ConsistentHashingLbConfig: MessageTypeDefinition + LocalityLbConfig: MessageTypeDefinition + SlowStartConfig: MessageTypeDefinition + } + } + ring_hash: { + v3: { + RingHash: MessageTypeDefinition + } + } + } + } + type: { + v3: { + FractionalPercent: MessageTypeDefinition + Percent: MessageTypeDefinition + SemanticVersion: MessageTypeDefinition + } + } + } + google: { + protobuf: { + Any: MessageTypeDefinition + BoolValue: MessageTypeDefinition + BytesValue: MessageTypeDefinition + DescriptorProto: MessageTypeDefinition + DoubleValue: MessageTypeDefinition + Duration: 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 + VersioningAnnotation: 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 + } + xds: { + annotations: { + v3: { + FieldStatusAnnotation: MessageTypeDefinition + FileStatusAnnotation: MessageTypeDefinition + MessageStatusAnnotation: MessageTypeDefinition + PackageVersionStatus: EnumTypeDefinition + StatusAnnotation: MessageTypeDefinition + } + } + core: { + v3: { + ContextParams: MessageTypeDefinition + } + } + } +} + From 3a43cba3a3a5afb5c0bbfc621922ee023f977e5e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Sep 2023 17:14:39 -0700 Subject: [PATCH 588/694] grpc-js-xds: Implement ring_hash LB policy --- packages/grpc-js-xds/gulpfile.ts | 1 + packages/grpc-js-xds/src/environment.ts | 1 + packages/grpc-js-xds/src/http-filter.ts | 4 +- packages/grpc-js-xds/src/index.ts | 2 + .../grpc-js-xds/src/load-balancer-priority.ts | 21 +- .../src/load-balancer-ring-hash.ts | 507 ++++++++++++++++++ .../src/load-balancer-xds-cluster-manager.ts | 30 +- .../src/load-balancer-xds-cluster-resolver.ts | 29 +- .../src/load-balancer-xds-wrr-locality.ts | 2 +- packages/grpc-js-xds/src/matcher.ts | 4 +- packages/grpc-js-xds/src/resolver-xds.ts | 44 +- packages/grpc-js-xds/src/route-action.ts | 92 +++- packages/grpc-js-xds/src/xds-bootstrap.ts | 6 +- packages/grpc-js-xds/src/xds-client.ts | 4 +- .../cluster-resource-type.ts | 23 +- packages/grpc-js-xds/src/xxhash.ts | 31 ++ packages/grpc-js-xds/test/framework.ts | 27 +- .../grpc-js-xds/test/test-confg-parsing.ts | 31 ++ packages/grpc-js-xds/test/test-ring-hash.ts | 108 ++++ packages/grpc-js-xds/test/xds-server.ts | 1 + packages/grpc-js-xds/tsconfig.json | 4 +- packages/grpc-js/src/channel-options.ts | 2 + packages/grpc-js/src/experimental.ts | 1 + packages/grpc-js/src/internal-channel.ts | 11 +- .../src/load-balancer-outlier-detection.ts | 122 +---- .../grpc-js/src/load-balancer-pick-first.ts | 13 + packages/grpc-js/src/picker.ts | 36 +- packages/grpc-js/src/resolver.ts | 2 +- .../grpc-js/src/resolving-load-balancer.ts | 2 +- packages/grpc-js/src/subchannel-address.ts | 124 +++++ 30 files changed, 1081 insertions(+), 204 deletions(-) create mode 100644 packages/grpc-js-xds/src/load-balancer-ring-hash.ts create mode 100644 packages/grpc-js-xds/src/xxhash.ts create mode 100644 packages/grpc-js-xds/test/test-ring-hash.ts diff --git a/packages/grpc-js-xds/gulpfile.ts b/packages/grpc-js-xds/gulpfile.ts index 6f17a4020..2bb43a7f1 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_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'true'; return gulp.src(`${outDir}/test/**/*.js`) .pipe(mocha({reporter: 'mocha-jenkins-reporter', require: ['ts-node/register']})); diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 530bb256c..858903111 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -20,3 +20,4 @@ export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENA export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; 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'; diff --git a/packages/grpc-js-xds/src/http-filter.ts b/packages/grpc-js-xds/src/http-filter.ts index 29ce5958f..f8da5b828 100644 --- a/packages/grpc-js-xds/src/http-filter.ts +++ b/packages/grpc-js-xds/src/http-filter.ts @@ -116,7 +116,7 @@ export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean try { typeUrl = getTopLevelFilterUrl(encodedConfig); } catch (e) { - trace(httpFilter.name + ' validation failed with error ' + e.message); + trace(httpFilter.name + ' validation failed with error ' + (e as Error).message); return false; } const registryEntry = FILTER_REGISTRY.get(typeUrl); @@ -243,4 +243,4 @@ export function createHttpFilter(config: HttpFilterConfig, overrideConfig?: Http } else { return null; } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/index.ts b/packages/grpc-js-xds/src/index.ts index 95c26a20a..70aa5bef2 100644 --- a/packages/grpc-js-xds/src/index.ts +++ b/packages/grpc-js-xds/src/index.ts @@ -23,6 +23,7 @@ import * as load_balancer_priority from './load-balancer-priority'; import * as load_balancer_weighted_target from './load-balancer-weighted-target'; import * as load_balancer_xds_cluster_manager from './load-balancer-xds-cluster-manager'; import * as xds_wrr_locality from './load-balancer-xds-wrr-locality'; +import * as ring_hash from './load-balancer-ring-hash'; import * as router_filter from './http-filter/router-filter'; import * as fault_injection_filter from './http-filter/fault-injection-filter'; import * as csds from './csds'; @@ -41,6 +42,7 @@ export function register() { load_balancer_weighted_target.setup(); load_balancer_xds_cluster_manager.setup(); xds_wrr_locality.setup(); + ring_hash.setup(); router_filter.setup(); fault_injection_filter.setup(); csds.setup(); diff --git a/packages/grpc-js-xds/src/load-balancer-priority.ts b/packages/grpc-js-xds/src/load-balancer-priority.ts index 4a4acb477..54e01fa84 100644 --- a/packages/grpc-js-xds/src/load-balancer-priority.ts +++ b/packages/grpc-js-xds/src/load-balancer-priority.ts @@ -41,9 +41,26 @@ const DEFAULT_FAILOVER_TIME_MS = 10_000; const DEFAULT_RETENTION_INTERVAL_MS = 15 * 60 * 1000; export interface LocalityEndpoint extends Endpoint { + /** + * A sequence of strings that determines how to divide endpoints up in priority and + * weighted_target. + */ localityPath: string[]; + /** + * The locality this endpoint is in. Used in wrr_locality and xds_cluster_impl. + */ locality: Locality__Output; - weight: number; + /** + * The load balancing weight for the entire locality that contains this + * endpoint. Used in xds_wrr_locality. + */ + localityWeight: number; + /** + * The overall load balancing weight for this endpoint, calculated as the + * product of the load balancing weight for this endpoint within its locality + * and the load balancing weight of the locality. Used in ring_hash. + */ + endpointWeight: number; }; export function isLocalityEndpoint( @@ -317,7 +334,7 @@ export class PriorityLoadBalancer implements LoadBalancer { * so that when the picker calls exitIdle, that in turn calls exitIdle on * the PriorityChildImpl, which will start the failover timer. */ if (state === ConnectivityState.IDLE) { - picker = new QueuePicker(this); + picker = new QueuePicker(this, picker); } this.channelControlHelper.updateState(state, picker); } diff --git a/packages/grpc-js-xds/src/load-balancer-ring-hash.ts b/packages/grpc-js-xds/src/load-balancer-ring-hash.ts new file mode 100644 index 000000000..a124d3b88 --- /dev/null +++ b/packages/grpc-js-xds/src/load-balancer-ring-hash.ts @@ -0,0 +1,507 @@ +/* + * 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. + * + */ + +import { experimental, logVerbosity, connectivityState, status, Metadata, ChannelOptions, LoadBalancingConfig } from '@grpc/grpc-js'; +import { isLocalityEndpoint } from './load-balancer-priority'; +import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig; +import LeafLoadBalancer = experimental.LeafLoadBalancer; +import Endpoint = experimental.Endpoint; +import Picker = experimental.Picker; +import PickArgs = experimental.PickArgs; +import PickResult = experimental.PickResult; +import PickResultType = experimental.PickResultType; +import LoadBalancer = experimental.LoadBalancer; +import ChannelControlHelper = experimental.ChannelControlHelper; +import createChildChannelControlHelper = experimental.createChildChannelControlHelper; +import UnavailablePicker = experimental.UnavailablePicker; +import subchannelAddressToString = experimental.subchannelAddressToString; +import registerLoadBalancerType = experimental.registerLoadBalancerType; +import EndpointMap = experimental.EndpointMap; +import { loadXxhashApi, xxhashApi } from './xxhash'; +import { EXPERIMENTAL_RING_HASH } from './environment'; +import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util'; +import { RingHash__Output } from './generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash'; +import { Any__Output } from './generated/google/protobuf/Any'; +import { TypedExtensionConfig__Output } from './generated/envoy/config/core/v3/TypedExtensionConfig'; +import { LoadBalancingPolicy__Output } from './generated/envoy/config/cluster/v3/LoadBalancingPolicy'; +import { registerLbPolicy } from './lb-policy-registry'; + +const TRACER_NAME = 'ring_hash'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} + +const TYPE_NAME = 'ring_hash'; + +const DEFAULT_MIN_RING_SIZE = 1024; +const DEFAULT_MAX_RING_SIZE = 4096; +const ABSOLUTE_MAX_RING_SIZE = 8_388_608; +const DEFAULT_RING_SIZE_CAP = 4096; + +class RingHashLoadBalancingConfig implements TypedLoadBalancingConfig { + private minRingSize: number; + private maxRingSize: number; + constructor(minRingSize?: number, maxRingSize?: number) { + this.minRingSize = Math.min( + minRingSize ?? DEFAULT_MIN_RING_SIZE, + ABSOLUTE_MAX_RING_SIZE + ); + this.maxRingSize = Math.min( + maxRingSize ?? DEFAULT_MAX_RING_SIZE, + ABSOLUTE_MAX_RING_SIZE + ); + } + getLoadBalancerName(): string { + return TYPE_NAME; + } + toJsonObject(): object { + return { + [TYPE_NAME]: { + min_ring_size: this.minRingSize, + max_ring_size: this.maxRingSize, + } + }; + } + getMinRingSize() { + return this.minRingSize; + } + getMaxRingSize() { + return this.maxRingSize; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static createFromJson(obj: any): TypedLoadBalancingConfig { + if ('min_ring_size' in obj) { + if (typeof obj.min_ring_size === 'number') { + if (obj.min_ring_size > ABSOLUTE_MAX_RING_SIZE) { + throw new Error(`ring_hash config field min_ring_size exceeds the cap of ${ABSOLUTE_MAX_RING_SIZE}: ${obj.min_ring_size}`); + } + } else { + throw new Error( + 'ring_hash config field min_ring_size must be a number if provided' + ); + } + } + if ('max_ring_size' in obj) { + if (typeof obj.max_ring_size === 'number') { + if (obj.max_ring_size > ABSOLUTE_MAX_RING_SIZE) { + throw new Error(`ring_hash config field max_ring_size exceeds the cap of ${ABSOLUTE_MAX_RING_SIZE}: ${obj.max_ring_size}`); + } + } else { + throw new Error( + 'ring_hash config field max_ring_size must be a number if provided' + ); + } + } + return new RingHashLoadBalancingConfig( + obj.min_ring_size, + obj.max_ring_size + ); + } +} + +interface RingEntry { + leafBalancer: LeafLoadBalancer; + hash: bigint; +} + +interface EndpointWeight { + endpoint: Endpoint; + weight: number; + normalizedWeight: number; +} + +class RingHashPicker implements Picker { + constructor(private ring: RingEntry[]) {} + /** + * Find the least index in the ring with a hash greater than or equal to the + * hash parameter, or 0 if no such index exists. + * @param hash + */ + private findIndexForHash(hash: bigint): number { + // Binary search to find the target index + let low = 0; + let high = this.ring.length; + let index = 0; + while (low <= high) { + /* Commonly in binary search, this operation can overflow and result in + * the wrong value. However, in this case the ring size is absolutely + * limtied to 1<<23, so low+high < MAX_SAFE_INTEGER */ + index = Math.floor((low + high) / 2); + if (index === this.ring.length) { + index = 0; + break; + } + const midval = this.ring[index].hash; + const midval1 = index === 0 ? 0n : this.ring[index - 1].hash; + if (hash <= midval && hash > midval1) { + break; + } + if (midval < hash) { + low = index + 1; + } else { + high = index - 1; + } + if (low > high) { + index = 0; + break; + } + } + return index; + } + pick(pickArgs: PickArgs): PickResult { + trace('Pick called. Hash=' + pickArgs.extraPickInfo.hash); + const firstIndex = this.findIndexForHash( + BigInt(pickArgs.extraPickInfo.hash) + ); + for (let i = 0; i < this.ring.length; i++) { + const index = (firstIndex + i) % this.ring.length; + const entryState = this.ring[index].leafBalancer.getConnectivityState(); + if (entryState === connectivityState.READY) { + return this.ring[index].leafBalancer.getPicker().pick(pickArgs); + } + if (entryState === connectivityState.IDLE) { + this.ring[index].leafBalancer.startConnecting(); + return { + pickResultType: PickResultType.QUEUE, + subchannel: null, + status: null, + onCallStarted: null, + onCallEnded: null, + }; + } + if (entryState === connectivityState.CONNECTING) { + return { + pickResultType: PickResultType.QUEUE, + subchannel: null, + status: null, + onCallStarted: null, + onCallEnded: null, + }; + } + } + return { + pickResultType: PickResultType.TRANSIENT_FAILURE, + status: { + code: status.UNAVAILABLE, + details: + 'ring_hash: invalid state: all child balancers in TRANSIENT_FAILURE', + metadata: new Metadata(), + }, + subchannel: null, + onCallStarted: null, + onCallEnded: null, + }; + } +} + +class RingHashLoadBalancer implements LoadBalancer { + /** + * Tracks endpoint repetition across address updates, to use an appropriate + * existing leaf load balancer for the same endpoint when possible. + */ + private leafMap = new EndpointMap(); + /** + * Tracks endpoints from a single address update, with their associated + * weights aggregated from all weights associated with that endpoint in that + * update. + */ + private leafWeightMap = new EndpointMap(); + private childChannelControlHelper: ChannelControlHelper; + private updatesPaused = false; + private currentState: connectivityState = connectivityState.IDLE; + private ring: RingEntry[] = []; + private ringHashSizeCap = DEFAULT_RING_SIZE_CAP; + constructor(private channelControlHelper: ChannelControlHelper, private options: ChannelOptions) { + this.childChannelControlHelper = createChildChannelControlHelper( + channelControlHelper, + { + updateState: (state, picker) => { + this.calculateAndUpdateState(); + /* If this LB policy is in the TRANSIENT_FAILURE state, requests will + * not trigger new connections, so we need to explicitly try connecting + * to other endpoints that are currently IDLE to try to eventually + * connect to something. */ + if ( + state === connectivityState.TRANSIENT_FAILURE && + this.currentState === connectivityState.TRANSIENT_FAILURE + ) { + for (const leaf of this.leafMap.values()) { + const leafState = leaf.getConnectivityState(); + if (leafState === connectivityState.CONNECTING) { + break; + } + if (leafState === connectivityState.IDLE) { + leaf.startConnecting(); + break; + } + } + } + }, + } + ); + if (options['grpc.lb.ring_hash.ring_size_cap'] !== undefined) { + this.ringHashSizeCap = options['grpc.lb.ring_hash.ring_size_cap']; + } + } + + private calculateAndUpdateState() { + if (this.updatesPaused) { + return; + } + const stateCounts = { + [connectivityState.READY]: 0, + [connectivityState.TRANSIENT_FAILURE]: 0, + [connectivityState.CONNECTING]: 0, + [connectivityState.IDLE]: 0, + [connectivityState.SHUTDOWN]: 0, + }; + for (const leaf of this.leafMap.values()) { + stateCounts[leaf.getConnectivityState()] += 1; + } + if (stateCounts[connectivityState.READY] > 0) { + this.updateState(connectivityState.READY, new RingHashPicker(this.ring)); + // REPORT READY + } else if (stateCounts[connectivityState.TRANSIENT_FAILURE] > 1) { + this.updateState( + connectivityState.TRANSIENT_FAILURE, + new UnavailablePicker() + ); + } else if (stateCounts[connectivityState.CONNECTING] > 0) { + this.updateState( + connectivityState.CONNECTING, + new RingHashPicker(this.ring) + ); + } else if ( + stateCounts[connectivityState.TRANSIENT_FAILURE] > 0 && + this.leafMap.size > 1 + ) { + this.updateState( + connectivityState.CONNECTING, + new RingHashPicker(this.ring) + ); + } else if (stateCounts[connectivityState.IDLE] > 0) { + this.updateState(connectivityState.IDLE, new RingHashPicker(this.ring)); + } else { + this.updateState( + connectivityState.TRANSIENT_FAILURE, + new UnavailablePicker() + ); + } + } + + private updateState(newState: connectivityState, picker: Picker) { + trace( + connectivityState[this.currentState] + + ' -> ' + + connectivityState[newState] + ); + this.currentState = newState; + this.channelControlHelper.updateState(newState, picker); + } + + private constructRing( + endpointList: Endpoint[], + config: RingHashLoadBalancingConfig + ) { + this.ring = []; + const endpointWeights: EndpointWeight[] = []; + let weightSum = 0; + for (const endpoint of endpointList) { + const weight = this.leafWeightMap.get(endpoint) ?? 1; + endpointWeights.push({ endpoint, weight, normalizedWeight: 0 }); + weightSum += weight; + } + /* The normalized weights sum to 1, with some small potential error due to + * the limitation of floating point precision. */ + let minNormalizedWeight = 1; + for (const endpointWeight of endpointWeights) { + endpointWeight.normalizedWeight = endpointWeight.weight / weightSum; + minNormalizedWeight = Math.min( + endpointWeight.normalizedWeight, + minNormalizedWeight + ); + } + const minRingSize = Math.min(config.getMinRingSize(), this.ringHashSizeCap); + const maxRingSize = Math.min(config.getMaxRingSize(), this.ringHashSizeCap); + /* Calculate a scale factor that meets the following conditions: + * 1. The result is between minRingSize and maxRingSize, inclusive + * 2. The smallest normalized weight is scaled to a whole number, if it + * does not violate the previous condition. + * The size of the ring is ceil(scale) + */ + const scale = Math.min( + Math.ceil(minNormalizedWeight * minRingSize) / minNormalizedWeight, + maxRingSize + ); + trace('Creating a ring with size ' + Math.ceil(scale)); + /* For each endpoint, create a number of entries proportional to its + * weight, such that the total number of entries is equal to ceil(scale). + */ + let currentHashes = 0; + let targetHashes = 0; + for (const endpointWeight of endpointWeights) { + const addressString = subchannelAddressToString( + endpointWeight.endpoint.addresses[0] + ); + targetHashes += scale * endpointWeight.normalizedWeight; + const leafBalancer = this.leafMap.get(endpointWeight.endpoint); + if (!leafBalancer) { + throw new Error( + 'ring_hash: Invalid state: endpoint found in leafWeightMap but not in leafMap' + ); + } + let count = 0; + while (currentHashes < targetHashes) { + const hashKey = `${addressString}_${count}`; + const hash = xxhashApi!.h64(hashKey, 0n); + this.ring.push({ hash, leafBalancer }); + currentHashes++; + count++; + } + } + /* The ring is sorted by the hash so that it can be efficiently searched + * for a hash that is closest to any arbitrary hash. */ + this.ring.sort((a, b) => { + if (a.hash > b.hash) { + return 1; + } else if (a.hash < b.hash) { + return -1; + } else { + return 0; + } + }); + } + + updateAddressList( + endpointList: Endpoint[], + lbConfig: TypedLoadBalancingConfig, + attributes: { [key: string]: unknown } + ): void { + if (!(lbConfig instanceof RingHashLoadBalancingConfig)) { + trace('Discarding address update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + return; + } + trace('Received update with config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); + this.updatesPaused = true; + this.leafWeightMap.clear(); + const dedupedEndpointList: Endpoint[] = []; + for (const endpoint of endpointList) { + const leafBalancer = this.leafMap.get(endpoint); + if (leafBalancer) { + leafBalancer.updateEndpoint(endpoint); + } else { + this.leafMap.set( + endpoint, + new LeafLoadBalancer(endpoint, this.childChannelControlHelper, this.options) + ); + } + const weight = this.leafWeightMap.get(endpoint); + if (weight === undefined) { + dedupedEndpointList.push(endpoint); + } + this.leafWeightMap.set(endpoint, (weight ?? 0) + (isLocalityEndpoint(endpoint) ? endpoint.endpointWeight : 1)); + } + const removedLeaves = this.leafMap.deleteMissing(endpointList); + for (const leaf of removedLeaves) { + leaf.destroy(); + } + loadXxhashApi().then(() => { + this.constructRing(dedupedEndpointList, lbConfig); + this.updatesPaused = false; + this.calculateAndUpdateState(); + }); + } + exitIdle(): void { + /* This operation does not make sense here. We don't want to make the whole + * balancer exit idle, and instead propagate that to individual chlidren as + * relevant. */ + } + resetBackoff(): void { + // There is no backoff to reset here + } + destroy(): void { + this.ring = []; + for (const child of this.leafMap.values()) { + child.destroy(); + } + this.leafMap.clear(); + this.leafWeightMap.clear(); + } + getTypeName(): string { + return TYPE_NAME; + } +} + +const RING_HASH_TYPE_URL = 'type.googleapis.com/envoy.extensions.load_balancing_policies.ring_hash.v3.RingHash'; + +const resourceRoot = loadProtosWithOptionsSync([ + 'envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto'], { + keepCase: true, + includeDirs: [ + // Paths are relative to src/build + __dirname + '/../../deps/envoy-api/', + __dirname + '/../../deps/xds/', + __dirname + '/../../deps/protoc-gen-validate' + ], + } +); + +const toObjectOptions = { + longs: String, + enums: String, + defaults: true, + oneofs: true +} + +function decodeRingHash(message: Any__Output): RingHash__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 RingHash__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 { + if (protoPolicy.typed_config?.type_url !== RING_HASH_TYPE_URL) { + throw new Error(`Ring Hash LB policy parsing error: unexpected type URL ${protoPolicy.typed_config?.type_url}`); + } + const ringHashMessage = decodeRingHash(protoPolicy.typed_config); + if (ringHashMessage.hash_function !== 'XX_HASH') { + throw new Error(`Ring Hash LB policy parsing error: unexpected hash function ${ringHashMessage.hash_function}`); + } + return { + [TYPE_NAME]: { + min_ring_size: ringHashMessage.minimum_ring_size?.value ?? 1024, + max_ring_size: ringHashMessage.maximum_ring_size?.value ?? 8_388_608 + } + }; +} + +export function setup() { + if (EXPERIMENTAL_RING_HASH) { + registerLoadBalancerType( + TYPE_NAME, + RingHashLoadBalancer, + RingHashLoadBalancingConfig + ); + registerLbPolicy(RING_HASH_TYPE_URL, convertToLoadBalancingPolicy); + } +} diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts index 3560d8481..99059dcac 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-manager.ts @@ -204,35 +204,7 @@ class XdsClusterManager implements LoadBalancer { } else { connectivityState = ConnectivityState.TRANSIENT_FAILURE; } - /* For each of the states CONNECTING, IDLE, and TRANSIENT_FAILURE, there is - * exactly one corresponding picker, so if the state is one of those and - * that does not change, no new information is provided by passing the - * new state upward. */ - if (connectivityState === this.currentState && connectivityState !== ConnectivityState.READY) { - return; - } - let picker: Picker; - - switch (connectivityState) { - case ConnectivityState.READY: - picker = new XdsClusterManagerPicker(pickerMap); - break; - case ConnectivityState.CONNECTING: - case ConnectivityState.IDLE: - picker = new QueuePicker(this); - break; - default: - picker = new UnavailablePicker({ - code: Status.UNAVAILABLE, - details: 'xds_cluster_manager: all children report state TRANSIENT_FAILURE', - metadata: new Metadata() - }); - } - trace( - 'Transitioning to ' + - ConnectivityState[connectivityState] - ); - this.channelControlHelper.updateState(connectivityState, picker); + this.channelControlHelper.updateState(connectivityState, new XdsClusterManagerPicker(pickerMap)); } updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void { diff --git a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts index c4bf984f7..29c0b6f31 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-cluster-resolver.ts @@ -115,10 +115,15 @@ class XdsClusterResolverLoadBalancingConfig implements TypedLoadBalancingConfig } } +interface WeightedEndpoint { + endpoint: Endpoint; + weight: number; +} + interface LocalityEntry { locality: Locality__Output; weight: number; - endpoints: Endpoint[]; + endpoints: WeightedEndpoint[]; } interface PriorityEntry { @@ -166,16 +171,19 @@ function getEdsPriorities(edsUpdate: ClusterLoadAssignment__Output): PriorityEnt if (!endpoint.load_balancing_weight) { continue; } - const endpoints: Endpoint[] = endpoint.lb_endpoints.filter(lbEndpoint => lbEndpoint.health_status === 'UNKNOWN' || lbEndpoint.health_status === 'HEALTHY').map( + const endpoints: WeightedEndpoint[] = endpoint.lb_endpoints.filter(lbEndpoint => lbEndpoint.health_status === 'UNKNOWN' || lbEndpoint.health_status === 'HEALTHY').map( (lbEndpoint) => { /* The validator in the XdsClient class ensures that each endpoint has * a socket_address with an IP address and a port_value. */ const socketAddress = lbEndpoint.endpoint!.address!.socket_address!; return { - addresses: [{ - host: socketAddress.address!, - port: socketAddress.port_value!, - }] + endpoint: { + addresses: [{ + host: socketAddress.address!, + port: socketAddress.port_value!, + }] + }, + weight: lbEndpoint.load_balancing_weight?.value ?? 1 }; } ); @@ -211,7 +219,7 @@ function getDnsPriorities(endpoints: Endpoint[]): PriorityEntry[] { sub_zone: '' }, weight: 1, - endpoints: endpoints + endpoints: endpoints.map(endpoint => ({endpoint: endpoint, weight: 1})) }], dropCategories: [] }]; @@ -295,15 +303,16 @@ export class XdsClusterResolver implements LoadBalancer { newPriorityNames[priority] = newPriorityName; for (const localityObj of priorityEntry.localities) { - for (const endpoint of localityObj.endpoints) { + for (const weightedEndpoint of localityObj.endpoints) { endpointList.push({ localityPath: [ newPriorityName, localityToName(localityObj.locality), ], locality: localityObj.locality, - weight: localityObj.weight, - ...endpoint + localityWeight: localityObj.weight, + endpointWeight: localityObj.weight * weightedEndpoint.weight, + ...weightedEndpoint.endpoint }); } newLocalityPriorities.set(localityToName(localityObj.locality), priority); diff --git a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts index f3fbcd513..3fb57d6e2 100644 --- a/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts +++ b/packages/grpc-js-xds/src/load-balancer-xds-wrr-locality.ts @@ -90,7 +90,7 @@ class XdsWrrLocalityLoadBalancer implements LoadBalancer { if (!(localityName in targets)) { targets[localityName] = { child_policy: lbConfig.getChildPolicy(), - weight: address.weight + weight: address.localityWeight }; } } diff --git a/packages/grpc-js-xds/src/matcher.ts b/packages/grpc-js-xds/src/matcher.ts index 148df7f85..b657d32c9 100644 --- a/packages/grpc-js-xds/src/matcher.ts +++ b/packages/grpc-js-xds/src/matcher.ts @@ -71,7 +71,7 @@ export class SafeRegexValueMatcher implements ValueMatcher { const numberRegex = new RE2(/^-?\d+$/u); export class RangeValueMatcher implements ValueMatcher { - constructor(private start: BigInt, private end: BigInt) {} + constructor(private start: bigint, private end: bigint) {} apply(value: string) { if (!numberRegex.test(value)) { @@ -264,4 +264,4 @@ export class FullMatcher implements Matcher { headers: ${this.headerMatchers.map(matcher => matcher.toString()).join('\n\t')} fraction: ${this.fraction ? fractionToString(this.fraction): 'none'}`; } -} \ No newline at end of file +} diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index cd830d521..3acdd2329 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -34,18 +34,19 @@ import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderM import ConfigSelector = experimental.ConfigSelector; import { ContainsValueMatcher, ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; import { envoyFractionToFraction, Fraction } from "./fraction"; -import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; +import { HashPolicy, RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resources'; import Duration = experimental.Duration; import { Duration__Output } from './generated/google/protobuf/Duration'; import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter'; -import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_FEDERATION, EXPERIMENTAL_RETRY } from './environment'; +import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_FEDERATION, EXPERIMENTAL_RETRY, EXPERIMENTAL_RING_HASH } from './environment'; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; import { BootstrapInfo, loadBootstrapInfo, validateBootstrapConfig } from './xds-bootstrap'; import { ListenerResourceType } from './xds-resource-type/listener-resource-type'; import { RouteConfigurationResourceType } from './xds-resource-type/route-config-resource-type'; import { protoDurationToDuration } from './duration'; +import { loadXxhashApi } from './xxhash'; const TRACER_NAME = 'xds_resolver'; @@ -381,7 +382,11 @@ class XdsResolver implements Resolver { } } - private handleRouteConfig(routeConfig: RouteConfiguration__Output) { + private async handleRouteConfig(routeConfig: RouteConfiguration__Output) { + /* We need to load the xxhash API before this function finishes, because + * it is invoked in the config selector, which can be called immediately + * after this function returns. */ + await loadXxhashApi(); this.latestRouteConfig = routeConfig; /* Select the virtual host using the default authority override if it * exists, and the channel target otherwise. */ @@ -456,6 +461,26 @@ class XdsResolver implements Resolver { } } } + const hashPolicies: HashPolicy[] = []; + if (EXPERIMENTAL_RING_HASH) { + for (const routeHashPolicy of route.route!.hash_policy) { + if (routeHashPolicy.policy_specifier === 'header') { + const headerPolicy = routeHashPolicy.header!; + hashPolicies.push({ + type: 'HEADER', + terminal: routeHashPolicy.terminal, + headerName: headerPolicy.header_name, + regex: headerPolicy.regex_rewrite?.pattern ? new RE2(headerPolicy.regex_rewrite.pattern.regex, 'ug') : undefined, + regexSubstitution: headerPolicy.regex_rewrite?.substitution + }); + } else if (routeHashPolicy.policy_specifier === 'filter_state' && routeHashPolicy.filter_state!.key === 'io.grpc.channel_id') { + hashPolicies.push({ + type: 'CHANNEL_ID', + terminal: routeHashPolicy.terminal + }); + } + } + } switch (route.route!.cluster_specifier) { case 'cluster_header': continue; @@ -483,7 +508,7 @@ class XdsResolver implements Resolver { } } } - routeAction = new SingleClusterRouteAction(cluster, {name: [], timeout: timeout, retryPolicy: retryPolicy}, extraFilterFactories); + routeAction = new SingleClusterRouteAction(cluster, {name: [], timeout: timeout, retryPolicy: retryPolicy}, extraFilterFactories, hashPolicies); break; } case 'weighted_clusters': { @@ -525,7 +550,7 @@ class XdsResolver implements Resolver { } weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, dynamicFilterFactories: extraFilterFactories}); } - routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, {name: [], timeout: timeout, retryPolicy: retryPolicy}); + routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, {name: [], timeout: timeout, retryPolicy: retryPolicy}, hashPolicies); break; } default: @@ -554,7 +579,7 @@ class XdsResolver implements Resolver { this.clusterRefcounts.set(name, {inLastConfig: true, refCount: 0}); } } - const configSelector: ConfigSelector = (methodName, metadata) => { + const configSelector: ConfigSelector = (methodName, metadata, channelId) => { for (const {matcher, action} of matchList) { if (matcher.apply(methodName, metadata)) { const clusterResult = action.getCluster(); @@ -562,10 +587,11 @@ class XdsResolver implements Resolver { const onCommitted = () => { this.unrefCluster(clusterResult.name); } + const hash = action.getHash(metadata, channelId); return { methodConfig: clusterResult.methodConfig, onCommitted: onCommitted, - pickInformation: {cluster: clusterResult.name}, + pickInformation: {cluster: clusterResult.name, hash: `${hash}`}, status: status.OK, dynamicFilterFactories: clusterResult.dynamicFilterFactories }; @@ -573,8 +599,8 @@ class XdsResolver implements Resolver { } return { methodConfig: {name: []}, - // cluster won't be used here, but it's set because of some TypeScript weirdness - pickInformation: {cluster: ''}, + // These fields won't be used here, but they're set because of some TypeScript weirdness + pickInformation: {cluster: '', hash: ''}, status: status.UNAVAILABLE, dynamicFilterFactories: [] }; diff --git a/packages/grpc-js-xds/src/route-action.ts b/packages/grpc-js-xds/src/route-action.ts index 83530f1b4..2f87fee96 100644 --- a/packages/grpc-js-xds/src/route-action.ts +++ b/packages/grpc-js-xds/src/route-action.ts @@ -14,10 +14,12 @@ * limitations under the License. */ -import { MethodConfig, experimental } from '@grpc/grpc-js'; +import { Metadata, MethodConfig, experimental } from '@grpc/grpc-js'; import Duration = experimental.Duration; import Filter = experimental.Filter; import FilterFactory = experimental.FilterFactory; +import { RE2 } from 're2-wasm'; +import { xxhashApi } from './xxhash'; export interface ClusterResult { name: string; @@ -28,6 +30,7 @@ export interface ClusterResult { export interface RouteAction { toString(): string; getCluster(): ClusterResult; + getHash(metadata: Metadata, channelId: number): bigint; } function durationToLogString(duration: Duration) { @@ -39,8 +42,83 @@ function durationToLogString(duration: Duration) { } } +export interface HashPolicy { + type: 'HEADER' | 'CHANNEL_ID'; + terminal: boolean; + headerName?: string; + regex?: RE2; + regexSubstitution?: string; +} + +/** + * Must be called only after xxhash.loadXxhashApi() resolves. + * @param hashPolicies + * @param metadata + * @param channelId + */ +function getHash(hashPolicies: HashPolicy[], metadata: Metadata, channelId: number): bigint { + let hash: bigint | null = null; + for (const policy of hashPolicies) { + let newHash: bigint | null = null; + switch (policy.type) { + case 'CHANNEL_ID': + newHash = xxhashApi!.h64(`${channelId}`, 0n); + break; + case 'HEADER': { + if (!policy.headerName) { + break; + } + if (policy.headerName.endsWith('-bin')) { + break; + } + let headerString: string; + if (policy.headerName === 'content-type') { + headerString = 'application/grpc'; + } else { + const headerValues = metadata.get(policy.headerName); + if (headerValues.length === 0) { + break; + } + headerString = headerValues.join(','); + } + let rewrittenHeaderString = headerString; + if (policy.regex && policy.regexSubstitution) { + /* The JS string replace method uses $-prefixed patterns to produce + * other strings. See + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement + * RE2-based regex substitutions use \n where n is a number to refer + * to capture group n, and they otherwise have no special replacement + * patterns. See + * https://github.com/envoyproxy/envoy/blob/2443032526cf6e50d63d35770df9473dd0460fc0/api/envoy/type/matcher/v3/regex.proto#L79-L87 + * We convert an RE2 regex substitution into a string substitution by + * first replacing each "$" with "$$" (which produces "$" in the + * output), and then replace each "\n" for any whole number n with + * "$n". */ + const regexSubstitution = policy.regexSubstitution.replace(/\$/g, '$$$$').replace(/\\(\d+)/g, '$$$1'); + rewrittenHeaderString = headerString.replace(policy.regex, regexSubstitution); + } + newHash = xxhashApi!.h64(rewrittenHeaderString, 0n); + break; + } + } + if (hash === null) { + hash = newHash; + } else if (newHash !== null) { + hash = ((hash << 1n) | (hash >> 63n)) ^ newHash; + } + if (policy.terminal && hash !== null) { + break; + } + } + if (hash === null) { + return xxhashApi!.h64(`${Math.random()}`, 0n); + } else { + return hash; + } +} + export class SingleClusterRouteAction implements RouteAction { - constructor(private cluster: string, private methodConfig: MethodConfig, private extraFilterFactories: FilterFactory[]) {} + constructor(private cluster: string, private methodConfig: MethodConfig, private extraFilterFactories: FilterFactory[], private hashPolicies: HashPolicy[]) {} getCluster() { return { @@ -50,6 +128,10 @@ export class SingleClusterRouteAction implements RouteAction { }; } + getHash(metadata: Metadata, channelId: number): bigint { + return getHash(this.hashPolicies, metadata, channelId); + } + toString() { return 'SingleCluster(' + this.cluster + ', ' + JSON.stringify(this.methodConfig) + ')'; } @@ -72,7 +154,7 @@ export class WeightedClusterRouteAction implements RouteAction { * The weighted cluster choices represented as a CDF */ private clusterChoices: ClusterChoice[]; - constructor(private clusters: WeightedCluster[], private totalWeight: number, private methodConfig: MethodConfig) { + constructor(private clusters: WeightedCluster[], private totalWeight: number, private methodConfig: MethodConfig, private hashPolicies: HashPolicy[]) { this.clusterChoices = []; let lastNumerator = 0; for (const clusterWeight of clusters) { @@ -96,6 +178,10 @@ export class WeightedClusterRouteAction implements RouteAction { return {name: '', methodConfig: this.methodConfig, dynamicFilterFactories: []}; } + getHash(metadata: Metadata, channelId: number): bigint { + return getHash(this.hashPolicies, metadata, channelId); + } + toString() { const clusterListString = this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ') return 'WeightedCluster(' + clusterListString + ', ' + JSON.stringify(this.methodConfig) + ')'; diff --git a/packages/grpc-js-xds/src/xds-bootstrap.ts b/packages/grpc-js-xds/src/xds-bootstrap.ts index fccd3edcf..536439dd0 100644 --- a/packages/grpc-js-xds/src/xds-bootstrap.ts +++ b/packages/grpc-js-xds/src/xds-bootstrap.ts @@ -357,14 +357,14 @@ export function loadBootstrapInfo(): BootstrapInfo { try { rawBootstrap = fs.readFileSync(bootstrapPath, { encoding: 'utf8'}); } catch (e) { - throw new Error(`Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${e.message}`); + throw new Error(`Failed to read xDS bootstrap file from path ${bootstrapPath} with error ${(e as Error).message}`); } try { const parsedFile = JSON.parse(rawBootstrap); loadedBootstrapInfo = validateBootstrapConfig(parsedFile); return loadedBootstrapInfo; } catch (e) { - throw new Error(`Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${e.message}`) + throw new Error(`Failed to parse xDS bootstrap file at path ${bootstrapPath} with error ${(e as Error).message}`) } } @@ -383,7 +383,7 @@ export function loadBootstrapInfo(): BootstrapInfo { loadedBootstrapInfo = validateBootstrapConfig(parsedConfig); } catch (e) { throw new Error( - `Failed to parse xDS bootstrap config from environment variable GRPC_XDS_BOOTSTRAP_CONFIG with error ${e.message}` + `Failed to parse xDS bootstrap config from environment variable GRPC_XDS_BOOTSTRAP_CONFIG with error ${(e as Error).message}` ); } diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index 464e26596..baabf2e76 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -208,14 +208,14 @@ class AdsResponseParser { try { decodeResult = this.result.type.decode(decodeContext, resource); } catch (e) { - this.result.errors.push(`${errorPrefix} ${e.message}`); + this.result.errors.push(`${errorPrefix} ${(e as Error).message}`); return; } let parsedName: XdsResourceName; try { parsedName = parseXdsResourceName(decodeResult.name, this.result.type!.getTypeUrl()); } catch (e) { - this.result.errors.push(`${errorPrefix} ${e.message}`); + this.result.errors.push(`${errorPrefix} ${(e as Error).message}`); return; } this.adsCallState.typeStates.get(this.result.type!)?.subscribedResources.get(parsedName.authority)?.get(parsedName.key)?.markSeen(); diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index 1e8ea41f8..c13a91d7e 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -21,7 +21,7 @@ import { LoadBalancingConfig, experimental, logVerbosity } from "@grpc/grpc-js"; import { XdsServerConfig } from "../xds-bootstrap"; import { Duration__Output } from "../generated/google/protobuf/Duration"; import { OutlierDetection__Output } from "../generated/envoy/config/cluster/v3/OutlierDetection"; -import { EXPERIMENTAL_CUSTOM_LB_CONFIG, EXPERIMENTAL_OUTLIER_DETECTION } from "../environment"; +import { EXPERIMENTAL_CUSTOM_LB_CONFIG, EXPERIMENTAL_OUTLIER_DETECTION, EXPERIMENTAL_RING_HASH } from "../environment"; import { Cluster__Output } from "../generated/envoy/config/cluster/v3/Cluster"; import { UInt32Value__Output } from "../generated/google/protobuf/UInt32Value"; import { Any__Output } from "../generated/google/protobuf/Any"; @@ -150,6 +150,27 @@ export class ClusterResourceType extends XdsResourceType { child_policy: [{round_robin: {}}] } }; + } else if(EXPERIMENTAL_RING_HASH && message.lb_policy === 'RING_HASH') { + if (!message.ring_hash_lb_config) { + return null; + } + if (message.ring_hash_lb_config.hash_function !== 'XX_HASH') { + return null; + } + const minRingSize = message.ring_hash_lb_config.minimum_ring_size ? Number(message.ring_hash_lb_config.minimum_ring_size.value) : 1024; + if (minRingSize > 8_388_608) { + return null; + } + const maxRingSize = message.ring_hash_lb_config.maximum_ring_size ? Number(message.ring_hash_lb_config.maximum_ring_size.value) : 8_388_608; + if (maxRingSize > 8_388_608) { + return null; + } + lbPolicyConfig = { + ring_hash: { + min_ring_size: minRingSize, + max_ring_size: maxRingSize + } + }; } else { return null; } diff --git a/packages/grpc-js-xds/src/xxhash.ts b/packages/grpc-js-xds/src/xxhash.ts new file mode 100644 index 000000000..63f68af2b --- /dev/null +++ b/packages/grpc-js-xds/src/xxhash.ts @@ -0,0 +1,31 @@ +/* + * 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. + * + */ + +/* The simpler `import xxhash from 'xxhash-wasm';` doesn't compile correctly + * to CommonJS require calls for some reason, so we use this import to get + * the type, and then an explicit require call to get the actual value. */ +import xxhashImport from 'xxhash-wasm'; +const xxhash: typeof xxhashImport = require('xxhash-wasm'); + +export let xxhashApi: Awaited> | null = null; + +export async function loadXxhashApi() { + if (!xxhashApi) { + xxhashApi = await xxhash(); + } + return xxhashApi; +} diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index f89a4680f..d38437a10 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -70,7 +70,7 @@ export interface FakeCluster { } export class FakeEdsCluster implements FakeCluster { - constructor(private clusterName: string, private endpointName: string, private endpoints: Endpoint[], private loadBalancingPolicyOverride?: Any) {} + constructor(private clusterName: string, private endpointName: string, private endpoints: Endpoint[], private loadBalancingPolicyOverride?: Any | 'RING_HASH') {} getEndpointConfig(): ClusterLoadAssignment { return { @@ -94,7 +94,12 @@ export class FakeEdsCluster implements FakeCluster { ] } }; - if (this.loadBalancingPolicyOverride) { + if (this.loadBalancingPolicyOverride === 'RING_HASH') { + result.lb_policy = 'RING_HASH'; + result.ring_hash_lb_config = { + hash_function: 'XX_HASH' + }; + } else if (this.loadBalancingPolicyOverride) { result.load_balancing_policy = { policies: [ { @@ -257,8 +262,14 @@ function createRouteConfig(route: FakeRoute): Route { prefix: '' }, route: { - cluster: route.cluster.getName() - } + cluster: route.cluster.getName(), + // Default to consistent hash + hash_policy: [{ + filter_state: { + key: 'io.grpc.channel_id' + } + }] + }, }; } else { return { @@ -271,7 +282,13 @@ function createRouteConfig(route: FakeRoute): Route { name: clusterWeight.cluster.getName(), weight: {value: clusterWeight.weight} })) - } + }, + // Default to consistent hash + hash_policy: [{ + filter_state: { + key: 'io.grpc.channel_id' + } + }] } } } diff --git a/packages/grpc-js-xds/test/test-confg-parsing.ts b/packages/grpc-js-xds/test/test-confg-parsing.ts index 740934ba1..f2065805f 100644 --- a/packages/grpc-js-xds/test/test-confg-parsing.ts +++ b/packages/grpc-js-xds/test/test-confg-parsing.ts @@ -311,6 +311,37 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { } } } + ], + ring_hash: [ + { + name: 'empty config', + input: {}, + output: { + min_ring_size: 1024, + max_ring_size: 4096 + } + }, + { + name: 'populated config', + input: { + min_ring_size: 2048, + max_ring_size: 8192 + } + }, + { + name: 'min_ring_size too large', + input: { + min_ring_size: 8_388_609 + }, + error: /min_ring_size/ + }, + { + name: 'max_ring_size too large', + input: { + max_ring_size: 8_388_609 + }, + error: /max_ring_size/ + } ] } diff --git a/packages/grpc-js-xds/test/test-ring-hash.ts b/packages/grpc-js-xds/test/test-ring-hash.ts new file mode 100644 index 000000000..0a9e893ef --- /dev/null +++ b/packages/grpc-js-xds/test/test-ring-hash.ts @@ -0,0 +1,108 @@ +/* + * 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. + * + */ + +import { Backend } from "./backend"; +import { XdsTestClient } from "./client"; +import { FakeEdsCluster, FakeRouteGroup } from "./framework"; +import { XdsServer } from "./xds-server"; + +import { register } from "../src"; +import assert = require("assert"); +import { Any } from "../src/generated/google/protobuf/Any"; +import { AnyExtension } from "@grpc/proto-loader"; +import { RingHash } from "../src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash"; + +register(); + +describe('Ring hash LB policy', () => { + let xdsServer: XdsServer; + let client: XdsTestClient; + beforeEach(done => { + xdsServer = new XdsServer(); + xdsServer.startServer(error => { + done(error); + }); + }); + afterEach(() => { + client?.close(); + xdsServer?.shutdownServer(); + }); + it('Should route requests to the single backend with the old lbPolicy field', done => { + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}], 'RING_HASH'); + 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)); + }); + it('Should route requests to the single backend with the new load_balancing_policy field', done => { + const lbPolicy: AnyExtension & RingHash = { + '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.ring_hash.v3.RingHash', + hash_function: 'XX_HASH' + }; + 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)); + }); + it('Should route all identical requests to the same backend', done => { + const backend1 = new Backend(); + const backend2 = new Backend() + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend1, backend2], locality:{region: 'region1'}}], 'RING_HASH'); + 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.sendNCalls(10, error => { + assert.ifError(error); + assert((backend1.getCallCount() === 0) !== (backend2.getCallCount() === 0)); + 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 6f021c176..2aa62bdd8 100644 --- a/packages/grpc-js-xds/test/xds-server.ts +++ b/packages/grpc-js-xds/test/xds-server.ts @@ -44,6 +44,7 @@ const loadedProtos = loadPackageDefinition(loadSync( 'envoy/extensions/clusters/aggregate/v3/cluster.proto', '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', 'xds/type/v3/typed_struct.proto' ], { diff --git a/packages/grpc-js-xds/tsconfig.json b/packages/grpc-js-xds/tsconfig.json index c121a5f6d..24212dfc2 100644 --- a/packages/grpc-js-xds/tsconfig.json +++ b/packages/grpc-js-xds/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "rootDir": ".", "outDir": "build", - "target": "es2017", - "lib": ["es2017"], + "target": "es2020", + "lib": ["es2020"], "module": "commonjs", "incremental": true }, diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index f2bb8bcf7..aa1e6c83e 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -61,6 +61,7 @@ export interface ChannelOptions { * Set the enableTrace option in TLS clients and servers */ 'grpc-node.tls_enable_trace'?: number; + 'grpc.lb.ring_hash.ring_size_cap'?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } @@ -96,6 +97,7 @@ export const recognizedOptions = { 'grpc.service_config_disable_resolution': true, 'grpc.client_idle_timeout_ms': true, 'grpc-node.tls_enable_trace': true, + 'grpc.lb.ring_hash.ring_size_cap': true, }; export function channelOptionsEqual( diff --git a/packages/grpc-js/src/experimental.ts b/packages/grpc-js/src/experimental.ts index 870fbe285..1e7a1e143 100644 --- a/packages/grpc-js/src/experimental.ts +++ b/packages/grpc-js/src/experimental.ts @@ -26,6 +26,7 @@ export { Endpoint, endpointToString, endpointHasAddress, + EndpointMap, } from './subchannel-address'; export { ChildLoadBalancerHandler } from './load-balancer-child-handler'; export { diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 2817201e2..10d865cef 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -193,6 +193,15 @@ export class InternalChannel { private readonly callTracker = new ChannelzCallTracker(); private readonly childrenTracker = new ChannelzChildrenTracker(); + /** + * Randomly generated ID to be passed to the config selector, for use by + * ring_hash in xDS. An integer distributed approximately uniformly between + * 0 and MAX_SAFE_INTEGER. + */ + private readonly randomChannelId = Math.floor( + Math.random() * Number.MAX_SAFE_INTEGER + ); + constructor( target: string, private readonly credentials: ChannelCredentials, @@ -528,7 +537,7 @@ export class InternalChannel { if (this.configSelector) { return { type: 'SUCCESS', - config: this.configSelector(method, metadata), + config: this.configSelector(method, metadata, this.randomChannelId), }; } else { if (this.currentResolutionError) { diff --git a/packages/grpc-js/src/load-balancer-outlier-detection.ts b/packages/grpc-js/src/load-balancer-outlier-detection.ts index 8e0513611..8f2097f46 100644 --- a/packages/grpc-js/src/load-balancer-outlier-detection.ts +++ b/packages/grpc-js/src/load-balancer-outlier-detection.ts @@ -33,10 +33,9 @@ import { ChildLoadBalancerHandler } from './load-balancer-child-handler'; import { PickArgs, Picker, PickResult, PickResultType } from './picker'; import { Endpoint, + EndpointMap, SubchannelAddress, - endpointHasAddress, endpointToString, - subchannelAddressEqual, } from './subchannel-address'; import { BaseSubchannelWrapper, @@ -461,126 +460,9 @@ interface MapEntry { subchannelWrappers: OutlierDetectionSubchannelWrapper[]; } -interface EndpointMapEntry { - key: Endpoint; - value: MapEntry; -} - -function endpointEqualUnordered( - endpoint1: Endpoint, - endpoint2: Endpoint -): boolean { - if (endpoint1.addresses.length !== endpoint2.addresses.length) { - return false; - } - for (const address1 of endpoint1.addresses) { - let matchFound = false; - for (const address2 of endpoint2.addresses) { - if (subchannelAddressEqual(address1, address2)) { - matchFound = true; - break; - } - } - if (!matchFound) { - return false; - } - } - return true; -} - -class EndpointMap { - private map: Set = new Set(); - - get size() { - return this.map.size; - } - - getForSubchannelAddress(address: SubchannelAddress): MapEntry | undefined { - for (const entry of this.map) { - if (endpointHasAddress(entry.key, address)) { - return entry.value; - } - } - return undefined; - } - - /** - * Delete any entries in this map with keys that are not in endpoints - * @param endpoints - */ - deleteMissing(endpoints: Endpoint[]) { - for (const entry of this.map) { - let foundEntry = false; - for (const endpoint of endpoints) { - if (endpointEqualUnordered(endpoint, entry.key)) { - foundEntry = true; - } - } - if (!foundEntry) { - this.map.delete(entry); - } - } - } - - get(endpoint: Endpoint): MapEntry | undefined { - for (const entry of this.map) { - if (endpointEqualUnordered(endpoint, entry.key)) { - return entry.value; - } - } - return undefined; - } - - set(endpoint: Endpoint, mapEntry: MapEntry) { - for (const entry of this.map) { - if (endpointEqualUnordered(endpoint, entry.key)) { - entry.value = mapEntry; - return; - } - } - this.map.add({ key: endpoint, value: mapEntry }); - } - - delete(endpoint: Endpoint) { - for (const entry of this.map) { - if (endpointEqualUnordered(endpoint, entry.key)) { - this.map.delete(entry); - return; - } - } - } - - has(endpoint: Endpoint): boolean { - for (const entry of this.map) { - if (endpointEqualUnordered(endpoint, entry.key)) { - return true; - } - } - return false; - } - - *keys(): IterableIterator { - for (const entry of this.map) { - yield entry.key; - } - } - - *values(): IterableIterator { - for (const entry of this.map) { - yield entry.value; - } - } - - *entries(): IterableIterator<[Endpoint, MapEntry]> { - for (const entry of this.map) { - yield [entry.key, entry.value]; - } - } -} - export class OutlierDetectionLoadBalancer implements LoadBalancer { private childBalancer: ChildLoadBalancerHandler; - private entryMap = new EndpointMap(); + private entryMap = new EndpointMap(); private latestConfig: OutlierDetectionLoadBalancingConfig | null = null; private ejectionTimer: NodeJS.Timeout; private timerStartTime: Date | null = null; diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 9af662662..5cbd11cf3 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -541,6 +541,19 @@ export class LeafLoadBalancer { this.pickFirstBalancer.updateAddressList([this.endpoint], LEAF_CONFIG); } + /** + * Update the endpoint associated with this LeafLoadBalancer to a new + * endpoint. Does not trigger connection establishment if a connection + * attempt is not already in progress. + * @param newEndpoint + */ + updateEndpoint(newEndpoint: Endpoint) { + this.endpoint = newEndpoint; + if (this.latestState !== ConnectivityState.IDLE) { + this.startConnecting(); + } + } + getConnectivityState() { return this.latestState; } diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index 6474269f7..e0526f7a3 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -17,9 +17,10 @@ import { StatusObject } from './call-interface'; import { Metadata } from './metadata'; -import { Status } from './constants'; +import { LogVerbosity, Status } from './constants'; import { LoadBalancer } from './load-balancer'; import { SubchannelInterface } from './subchannel-interface'; +import { trace } from './logging'; export enum PickResultType { COMPLETE, @@ -122,25 +123,40 @@ export class UnavailablePicker implements Picker { * indicating that the pick should be tried again with the next `Picker`. Also * reports back to the load balancer that a connection should be established * once any pick is attempted. + * If the childPicker is provided, delegate to it instead of returning the + * hardcoded QUEUE pick result, but still calls exitIdle. */ export class QueuePicker { private calledExitIdle = false; // Constructed with a load balancer. Calls exitIdle on it the first time pick is called - constructor(private loadBalancer: LoadBalancer) {} + constructor( + private loadBalancer: LoadBalancer, + private childPicker?: Picker + ) {} - pick(pickArgs: PickArgs): QueuePickResult { + pick(pickArgs: PickArgs): PickResult { + trace( + LogVerbosity.DEBUG, + 'picker', + 'Queue picker called for load balancer of type ' + + this.loadBalancer.constructor.name + ); if (!this.calledExitIdle) { process.nextTick(() => { this.loadBalancer.exitIdle(); }); this.calledExitIdle = true; } - return { - pickResultType: PickResultType.QUEUE, - subchannel: null, - status: null, - onCallStarted: null, - onCallEnded: null, - }; + if (this.childPicker) { + return this.childPicker.pick(pickArgs); + } else { + return { + pickResultType: PickResultType.QUEUE, + subchannel: null, + status: null, + onCallStarted: null, + onCallEnded: null, + }; + } } } diff --git a/packages/grpc-js/src/resolver.ts b/packages/grpc-js/src/resolver.ts index 4dfa8d133..1c84c0490 100644 --- a/packages/grpc-js/src/resolver.ts +++ b/packages/grpc-js/src/resolver.ts @@ -37,7 +37,7 @@ export interface CallConfig { * https://github.com/grpc/proposal/blob/master/A31-xds-timeout-support-and-config-selector.md#new-functionality-in-grpc */ export interface ConfigSelector { - (methodName: string, metadata: Metadata): CallConfig; + (methodName: string, metadata: Metadata, channelId: number): CallConfig; } /** diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index df480400a..e600047e8 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -279,7 +279,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { ); // Ensure that this.exitIdle() is called by the picker if (connectivityState === ConnectivityState.IDLE) { - picker = new QueuePicker(this); + picker = new QueuePicker(this, picker); } this.currentState = connectivityState; this.channelControlHelper.updateState(connectivityState, picker); diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts index 36fd99ea6..70a7962f7 100644 --- a/packages/grpc-js/src/subchannel-address.ts +++ b/packages/grpc-js/src/subchannel-address.ts @@ -122,3 +122,127 @@ export function endpointHasAddress( } return false; } + +interface EndpointMapEntry { + key: Endpoint; + value: ValueType; +} + +function endpointEqualUnordered( + endpoint1: Endpoint, + endpoint2: Endpoint +): boolean { + if (endpoint1.addresses.length !== endpoint2.addresses.length) { + return false; + } + for (const address1 of endpoint1.addresses) { + let matchFound = false; + for (const address2 of endpoint2.addresses) { + if (subchannelAddressEqual(address1, address2)) { + matchFound = true; + break; + } + } + if (!matchFound) { + return false; + } + } + return true; +} + +export class EndpointMap { + private map: Set> = new Set(); + + get size() { + return this.map.size; + } + + getForSubchannelAddress(address: SubchannelAddress): ValueType | undefined { + for (const entry of this.map) { + if (endpointHasAddress(entry.key, address)) { + return entry.value; + } + } + return undefined; + } + + /** + * Delete any entries in this map with keys that are not in endpoints + * @param endpoints + */ + deleteMissing(endpoints: Endpoint[]): ValueType[] { + const removedValues: ValueType[] = []; + for (const entry of this.map) { + let foundEntry = false; + for (const endpoint of endpoints) { + if (endpointEqualUnordered(endpoint, entry.key)) { + foundEntry = true; + } + } + if (!foundEntry) { + removedValues.push(entry.value); + this.map.delete(entry); + } + } + return removedValues; + } + + get(endpoint: Endpoint): ValueType | undefined { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + return entry.value; + } + } + return undefined; + } + + set(endpoint: Endpoint, mapEntry: ValueType) { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + entry.value = mapEntry; + return; + } + } + this.map.add({ key: endpoint, value: mapEntry }); + } + + delete(endpoint: Endpoint) { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + this.map.delete(entry); + return; + } + } + } + + has(endpoint: Endpoint): boolean { + for (const entry of this.map) { + if (endpointEqualUnordered(endpoint, entry.key)) { + return true; + } + } + return false; + } + + clear() { + this.map.clear(); + } + + *keys(): IterableIterator { + for (const entry of this.map) { + yield entry.key; + } + } + + *values(): IterableIterator { + for (const entry of this.map) { + yield entry.value; + } + } + + *entries(): IterableIterator<[Endpoint, ValueType]> { + for (const entry of this.map) { + yield [entry.key, entry.value]; + } + } +} From 036e0e1b7f9e16f3b1ccde949e963f66911df4ec Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Sep 2023 17:15:20 -0700 Subject: [PATCH 589/694] grpc-js-xds: Enable xDS affinity test --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index a1e24ff1f..e4a0cf214 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -166,6 +166,7 @@ main() { cd "${TEST_DRIVER_FULL_DIR}" local failed_tests=0 test_suites=( + "affinity_test" "api_listener_test" "baseline_test" "change_backend_service_test" From 4bff372df7c005a27618f180f561cb5256fe3191 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Sep 2023 17:24:17 -0700 Subject: [PATCH 590/694] grpc-js: Remove logging in QueuePicker --- packages/grpc-js/src/picker.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index e0526f7a3..ac79c9fee 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -17,10 +17,9 @@ import { StatusObject } from './call-interface'; import { Metadata } from './metadata'; -import { LogVerbosity, Status } from './constants'; +import { Status } from './constants'; import { LoadBalancer } from './load-balancer'; import { SubchannelInterface } from './subchannel-interface'; -import { trace } from './logging'; export enum PickResultType { COMPLETE, @@ -135,12 +134,6 @@ export class QueuePicker { ) {} pick(pickArgs: PickArgs): PickResult { - trace( - LogVerbosity.DEBUG, - 'picker', - 'Queue picker called for load balancer of type ' + - this.loadBalancer.constructor.name - ); if (!this.calledExitIdle) { process.nextTick(() => { this.loadBalancer.exitIdle(); From 9974f7704dac5073a97bb108d5ad96c827fc4677 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Sep 2023 17:59:35 -0700 Subject: [PATCH 591/694] grpc-js-xds: Drop support for Node versions below 16 --- packages/grpc-js-xds/package.json | 2 +- run-tests.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index c240bfee5..8910b856f 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -53,7 +53,7 @@ "@grpc/grpc-js": "~1.8.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=16.0.0" }, "files": [ "src/**/*.ts", diff --git a/run-tests.sh b/run-tests.sh index 0adcc0f17..16dff5cbf 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -28,7 +28,7 @@ cd $ROOT git submodule update --init --recursive if [ ! -n "$node_versions" ] ; then - node_versions="14 16" + node_versions="16" fi set +ex From 9e487e44ab74ef85dafd8e7b89aacf7dc17758c1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Sep 2023 18:07:20 -0700 Subject: [PATCH 592/694] grpc-js-xds: Update gts dependency for compatibility with TypeScript update --- packages/grpc-js-xds/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 8910b856f..9d254854c 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -38,7 +38,7 @@ "@types/mocha": "^5.2.6", "@types/node": "^13.11.1", "@types/yargs": "^15.0.5", - "gts": "^2.0.2", + "gts": "^5.0.1", "typescript": "^4.9.5", "yargs": "^15.4.1" }, From 0b2281b02804b339eea2f703b112bbc5e0a734f2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 8 Sep 2023 10:12:14 -0700 Subject: [PATCH 593/694] Revert version support change, run ring_hash tests conditionallly --- packages/grpc-js-xds/gulpfile.ts | 4 +++- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/resolver-xds.ts | 9 +++++++-- .../grpc-js-xds/test/test-confg-parsing.ts | 19 ++++++++++++++----- packages/grpc-js-xds/test/test-ring-hash.ts | 18 ++++++++++++++---- run-tests.sh | 2 +- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/packages/grpc-js-xds/gulpfile.ts b/packages/grpc-js-xds/gulpfile.ts index 2bb43a7f1..93f93d8fc 100644 --- a/packages/grpc-js-xds/gulpfile.ts +++ b/packages/grpc-js-xds/gulpfile.ts @@ -63,7 +63,9 @@ 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_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'true'; + if (Number(process.versions.node.split('.')[0]) > 14) { + process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'true'; + } return gulp.src(`${outDir}/test/**/*.js`) .pipe(mocha({reporter: 'mocha-jenkins-reporter', require: ['ts-node/register']})); diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 9d254854c..6ec4548d5 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -53,7 +53,7 @@ "@grpc/grpc-js": "~1.8.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=10.10.0" }, "files": [ "src/**/*.ts", diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 3acdd2329..5182e1005 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -587,11 +587,16 @@ class XdsResolver implements Resolver { const onCommitted = () => { this.unrefCluster(clusterResult.name); } - const hash = action.getHash(metadata, channelId); + let hash: string; + if (EXPERIMENTAL_RING_HASH) { + hash = `${action.getHash(metadata, channelId)}`; + } else { + hash = ''; + } return { methodConfig: clusterResult.methodConfig, onCommitted: onCommitted, - pickInformation: {cluster: clusterResult.name, hash: `${hash}`}, + pickInformation: {cluster: clusterResult.name, hash: hash}, status: status.OK, dynamicFilterFactories: clusterResult.dynamicFilterFactories }; diff --git a/packages/grpc-js-xds/test/test-confg-parsing.ts b/packages/grpc-js-xds/test/test-confg-parsing.ts index f2065805f..c185c8527 100644 --- a/packages/grpc-js-xds/test/test-confg-parsing.ts +++ b/packages/grpc-js-xds/test/test-confg-parsing.ts @@ -19,6 +19,7 @@ import { experimental, LoadBalancingConfig } from "@grpc/grpc-js"; import { register } from "../src"; import assert = require("assert"); import parseLoadbalancingConfig = experimental.parseLoadBalancingConfig; +import { EXPERIMENTAL_RING_HASH } from "../src/environment"; register(); @@ -34,6 +35,7 @@ interface TestCase { input: object, output?: object; error?: RegExp; + skipIf?: boolean; } /* The main purpose of these tests is to verify that configs that are expected @@ -319,28 +321,32 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { output: { min_ring_size: 1024, max_ring_size: 4096 - } + }, + skipIf: !EXPERIMENTAL_RING_HASH }, { name: 'populated config', input: { min_ring_size: 2048, max_ring_size: 8192 - } + }, + skipIf: !EXPERIMENTAL_RING_HASH }, { name: 'min_ring_size too large', input: { min_ring_size: 8_388_609 }, - error: /min_ring_size/ + error: /min_ring_size/, + skipIf: !EXPERIMENTAL_RING_HASH }, { name: 'max_ring_size too large', input: { max_ring_size: 8_388_609 }, - error: /max_ring_size/ + error: /max_ring_size/, + skipIf: !EXPERIMENTAL_RING_HASH } ] } @@ -349,7 +355,10 @@ describe('Load balancing policy config parsing', () => { for (const [lbPolicyName, testCases] of Object.entries(allTestCases)) { describe(lbPolicyName, () => { for (const testCase of testCases) { - it(testCase.name, () => { + it(testCase.name, function() { + if (testCase.skipIf) { + this.skip(); + } const lbConfigInput = {[lbPolicyName]: testCase.input}; if (testCase.error) { assert.throws(() => { diff --git a/packages/grpc-js-xds/test/test-ring-hash.ts b/packages/grpc-js-xds/test/test-ring-hash.ts index 0a9e893ef..af795bc97 100644 --- a/packages/grpc-js-xds/test/test-ring-hash.ts +++ b/packages/grpc-js-xds/test/test-ring-hash.ts @@ -25,6 +25,7 @@ import assert = require("assert"); import { Any } from "../src/generated/google/protobuf/Any"; import { AnyExtension } from "@grpc/proto-loader"; import { RingHash } from "../src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash"; +import { EXPERIMENTAL_RING_HASH } from "../src/environment"; register(); @@ -41,7 +42,10 @@ describe('Ring hash LB policy', () => { client?.close(); xdsServer?.shutdownServer(); }); - it('Should route requests to the single backend with the old lbPolicy field', done => { + it('Should route requests to the single backend with the old lbPolicy field', function(done) { + if (!EXPERIMENTAL_RING_HASH) { + this.skip(); + } const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}], 'RING_HASH'); const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]); routeGroup.startAllBackends().then(() => { @@ -59,7 +63,10 @@ describe('Ring hash LB policy', () => { client.sendOneCall(done); }, reason => done(reason)); }); - it('Should route requests to the single backend with the new load_balancing_policy field', done => { + it('Should route requests to the single backend with the new load_balancing_policy field', function(done) { + if (!EXPERIMENTAL_RING_HASH) { + this.skip(); + } const lbPolicy: AnyExtension & RingHash = { '@type': 'type.googleapis.com/envoy.extensions.load_balancing_policies.ring_hash.v3.RingHash', hash_function: 'XX_HASH' @@ -81,7 +88,10 @@ describe('Ring hash LB policy', () => { client.sendOneCall(done); }, reason => done(reason)); }); - it('Should route all identical requests to the same backend', done => { + it('Should route all identical requests to the same backend', function(done) { + if (!EXPERIMENTAL_RING_HASH) { + this.skip(); + } const backend1 = new Backend(); const backend2 = new Backend() const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend1, backend2], locality:{region: 'region1'}}], 'RING_HASH'); @@ -105,4 +115,4 @@ describe('Ring hash LB policy', () => { }) }, reason => done(reason)); }); -}) +}); diff --git a/run-tests.sh b/run-tests.sh index 16dff5cbf..0adcc0f17 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -28,7 +28,7 @@ cd $ROOT git submodule update --init --recursive if [ ! -n "$node_versions" ] ; then - node_versions="16" + node_versions="14 16" fi set +ex From c41c3dae7b2be1a1846984544f4d48de647a6ece Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 8 Sep 2023 14:51:58 -0700 Subject: [PATCH 594/694] Test ring_hash fallback on dropped connection --- packages/grpc-js-xds/test/test-ring-hash.ts | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/packages/grpc-js-xds/test/test-ring-hash.ts b/packages/grpc-js-xds/test/test-ring-hash.ts index af795bc97..20d9eeed1 100644 --- a/packages/grpc-js-xds/test/test-ring-hash.ts +++ b/packages/grpc-js-xds/test/test-ring-hash.ts @@ -115,4 +115,59 @@ describe('Ring hash LB policy', () => { }) }, reason => done(reason)); }); + it('Should fallback to a second backend if the first one goes down', function(done) { + if (!EXPERIMENTAL_RING_HASH) { + this.skip(); + } + const backends = [new Backend(), new Backend(), new Backend()]; + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: backends, locality:{region: 'region1'}}], 'RING_HASH'); + 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.sendNCalls(100, error => { + assert.ifError(error); + let backendWithTraffic: number | null = null; + for (let i = 0; i < backends.length; i++) { + if (backendWithTraffic === null) { + if (backends[i].getCallCount() > 0) { + backendWithTraffic = i; + } + } else { + assert.strictEqual(backends[i].getCallCount(), 0, `Backends ${backendWithTraffic} and ${i} both got traffic`); + } + } + assert.notStrictEqual(backendWithTraffic, null, 'No backend got traffic'); + backends[backendWithTraffic!].shutdown(error => { + assert.ifError(error); + backends[backendWithTraffic!].resetCallCount(); + client.sendNCalls(100, error => { + assert.ifError(error); + let backendWithTraffic2: number | null = null; + for (let i = 0; i < backends.length; i++) { + if (backendWithTraffic2 === null) { + if (backends[i].getCallCount() > 0) { + backendWithTraffic2 = i; + } + } else { + assert.strictEqual(backends[i].getCallCount(), 0, `Backends ${backendWithTraffic2} and ${i} both got traffic`); + } + } + assert.notStrictEqual(backendWithTraffic2, null, 'No backend got traffic'); + assert.notStrictEqual(backendWithTraffic2, backendWithTraffic, `Traffic went to the same backend ${backendWithTraffic} after shutdown`); + done(); + }); + }); + }); + }, reason => done(reason)); + }) }); From f1f8d1ba619a4756133699854426b6aea386a744 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Sep 2023 13:51:32 -0700 Subject: [PATCH 595/694] grpc-js: Make a few improvements to DNS resolving timing --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/resolver-dns.ts | 3 ++- packages/grpc-js/src/resolving-load-balancer.ts | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 974f196c4..7b8801212 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.2", + "version": "1.9.3", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index c40cb8ec5..6bb31a3a1 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -175,6 +175,7 @@ class DnsResolver implements Resolver { }); this.backoff.stop(); this.backoff.reset(); + this.stopNextResolutionTimer(); return; } if (this.dnsHostname === null) { @@ -339,9 +340,9 @@ class DnsResolver implements Resolver { private startResolutionWithBackoff() { if (this.pendingLookupPromise === null) { this.continueResolving = false; - this.startResolution(); this.backoff.runOnce(); this.startNextResolutionTimer(); + this.startResolution(); } } diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 9b5d4c2dc..425d1fe2e 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -247,6 +247,8 @@ export class ResolvingLoadBalancer implements LoadBalancer { configSelector: ConfigSelector | null, attributes: { [key: string]: unknown } ) => { + this.backoffTimeout.stop(); + this.backoffTimeout.reset(); 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 From 5c8b11b0be738666ec00f02fb679c9ff8fef947d Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Sep 2023 15:39:19 -0700 Subject: [PATCH 596/694] Trace parsed unvalidated resources --- .../src/xds-resource-type/cluster-resource-type.ts | 1 + .../src/xds-resource-type/endpoint-resource-type.ts | 1 + .../src/xds-resource-type/listener-resource-type.ts | 1 + .../src/xds-resource-type/route-config-resource-type.ts | 7 +++++++ 4 files changed, 10 insertions(+) diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index c13a91d7e..0038fe912 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -285,6 +285,7 @@ export class ClusterResourceType extends XdsResourceType { ); } const message = decodeSingleResource(CDS_TYPE_URL, resource.value); + trace('Decoded raw resource of type ' + CDS_TYPE_URL + ': ' + JSON.stringify(message)); const validatedMessage = this.validateResource(context, message); if (validatedMessage) { return { diff --git a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts index 6ffd7788d..10d8cc3c7 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts @@ -101,6 +101,7 @@ export class EndpointResourceType extends XdsResourceType { ); } const message = decodeSingleResource(EDS_TYPE_URL, resource.value); + trace('Decoded raw resource of type ' + EDS_TYPE_URL + ': ' + JSON.stringify(message)); const validatedMessage = this.validateResource(message); if (validatedMessage) { return { diff --git a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts index 20e243b86..73782c380 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts @@ -106,6 +106,7 @@ export class ListenerResourceType extends XdsResourceType { ); } const message = decodeSingleResource(LDS_TYPE_URL, resource.value); + trace('Decoded raw resource of type ' + LDS_TYPE_URL + ': ' + JSON.stringify(message)); const validatedMessage = this.validateResource(message); if (validatedMessage) { return { diff --git a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts index cdc2e3196..6c0bc93e9 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts @@ -15,6 +15,7 @@ * */ +import { experimental, logVerbosity } from "@grpc/grpc-js"; import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from "../environment"; import { RetryPolicy__Output } from "../generated/envoy/config/route/v3/RetryPolicy"; import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration"; @@ -24,6 +25,11 @@ import { validateOverrideFilter } from "../http-filter"; import { RDS_TYPE_URL, decodeSingleResource } from "../resources"; import { Watcher, XdsClient } from "../xds-client"; import { XdsDecodeContext, XdsDecodeResult, XdsResourceType } from "./xds-resource-type"; +const TRACER_NAME = 'xds_client'; + +function trace(text: string): void { + experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text); +} const SUPPORTED_PATH_SPECIFIERS = ['prefix', 'path', 'safe_regex']; const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ @@ -169,6 +175,7 @@ export class RouteConfigurationResourceType extends XdsResourceType { ); } const message = decodeSingleResource(RDS_TYPE_URL, resource.value); + trace('Decoded raw resource of type ' + RDS_TYPE_URL + ': ' + JSON.stringify(message)); const validatedMessage = this.validateResource(message); if (validatedMessage) { return { From e570a99d6df993fbf2174c08811819310909dac8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Sep 2023 17:29:01 -0700 Subject: [PATCH 597/694] Improve unvalidated resource log formatting --- .../grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts | 2 +- .../grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts | 2 +- .../grpc-js-xds/src/xds-resource-type/listener-resource-type.ts | 2 +- .../src/xds-resource-type/route-config-resource-type.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index 0038fe912..9934c1760 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -285,7 +285,7 @@ export class ClusterResourceType extends XdsResourceType { ); } const message = decodeSingleResource(CDS_TYPE_URL, resource.value); - trace('Decoded raw resource of type ' + CDS_TYPE_URL + ': ' + JSON.stringify(message)); + trace('Decoded raw resource of type ' + CDS_TYPE_URL + ': ' + JSON.stringify(message, undefined, 2)); const validatedMessage = this.validateResource(context, message); if (validatedMessage) { return { diff --git a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts index 10d8cc3c7..093ca52e5 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/endpoint-resource-type.ts @@ -101,7 +101,7 @@ export class EndpointResourceType extends XdsResourceType { ); } const message = decodeSingleResource(EDS_TYPE_URL, resource.value); - trace('Decoded raw resource of type ' + EDS_TYPE_URL + ': ' + JSON.stringify(message)); + trace('Decoded raw resource of type ' + EDS_TYPE_URL + ': ' + JSON.stringify(message, undefined, 2)); const validatedMessage = this.validateResource(message); if (validatedMessage) { return { diff --git a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts index 73782c380..a557e1988 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts @@ -106,7 +106,7 @@ export class ListenerResourceType extends XdsResourceType { ); } const message = decodeSingleResource(LDS_TYPE_URL, resource.value); - trace('Decoded raw resource of type ' + LDS_TYPE_URL + ': ' + JSON.stringify(message)); + trace('Decoded raw resource of type ' + LDS_TYPE_URL + ': ' + JSON.stringify(message, undefined, 2)); const validatedMessage = this.validateResource(message); if (validatedMessage) { return { diff --git a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts index 6c0bc93e9..766a84388 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/route-config-resource-type.ts @@ -175,7 +175,7 @@ export class RouteConfigurationResourceType extends XdsResourceType { ); } const message = decodeSingleResource(RDS_TYPE_URL, resource.value); - trace('Decoded raw resource of type ' + RDS_TYPE_URL + ': ' + JSON.stringify(message)); + trace('Decoded raw resource of type ' + RDS_TYPE_URL + ': ' + JSON.stringify(message, undefined, 2)); const validatedMessage = this.validateResource(message); if (validatedMessage) { return { From 57c1bd2ede8445c4f76390e2715ea20a3a20f06e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 11 Sep 2023 17:32:41 -0700 Subject: [PATCH 598/694] grpc-js-xds: interop client: reduce periodic logging --- packages/grpc-js-xds/interop/xds-interop-client.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index f26278abb..1fdaa3a69 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -467,9 +467,11 @@ function sendConstantQps(client: TestServiceClient, qps: number, failOnFailedRpc makeSingleRequest(client, callType, failOnFailedRpcs, callStatsTracker, callStartTimestampsTrackers[callType]); } }, 1000/qps); - setInterval(() => { - console.log(`Accumulated stats: ${JSON.stringify(accumulatedStats, undefined, 2)}`); - }, 1000); + if (VERBOSITY >= 2) { + setInterval(() => { + console.log(`Accumulated stats: ${JSON.stringify(accumulatedStats, undefined, 2)}`); + }, 1000); + } } const callTypeEnumMap = { From 8df1bd712f13ae9639a76f1fb60410edebdbf31c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 12 Sep 2023 10:08:25 -0700 Subject: [PATCH 599/694] Treat ring_hash_lb_config field as optional --- .../src/xds-resource-type/cluster-resource-type.ts | 9 +++------ packages/grpc-js-xds/test/framework.ts | 3 --- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts index 9934c1760..c4081baa8 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/cluster-resource-type.ts @@ -151,17 +151,14 @@ export class ClusterResourceType extends XdsResourceType { } }; } else if(EXPERIMENTAL_RING_HASH && message.lb_policy === 'RING_HASH') { - if (!message.ring_hash_lb_config) { + if (message.ring_hash_lb_config && message.ring_hash_lb_config.hash_function !== 'XX_HASH') { return null; } - if (message.ring_hash_lb_config.hash_function !== 'XX_HASH') { - return null; - } - const minRingSize = message.ring_hash_lb_config.minimum_ring_size ? Number(message.ring_hash_lb_config.minimum_ring_size.value) : 1024; + const minRingSize = message.ring_hash_lb_config?.minimum_ring_size ? Number(message.ring_hash_lb_config.minimum_ring_size.value) : 1024; if (minRingSize > 8_388_608) { return null; } - const maxRingSize = message.ring_hash_lb_config.maximum_ring_size ? Number(message.ring_hash_lb_config.maximum_ring_size.value) : 8_388_608; + const maxRingSize = message.ring_hash_lb_config?.maximum_ring_size ? Number(message.ring_hash_lb_config.maximum_ring_size.value) : 8_388_608; if (maxRingSize > 8_388_608) { return null; } diff --git a/packages/grpc-js-xds/test/framework.ts b/packages/grpc-js-xds/test/framework.ts index d38437a10..bd6f270d6 100644 --- a/packages/grpc-js-xds/test/framework.ts +++ b/packages/grpc-js-xds/test/framework.ts @@ -96,9 +96,6 @@ export class FakeEdsCluster implements FakeCluster { }; if (this.loadBalancingPolicyOverride === 'RING_HASH') { result.lb_policy = 'RING_HASH'; - result.ring_hash_lb_config = { - hash_function: 'XX_HASH' - }; } else if (this.loadBalancingPolicyOverride) { result.load_balancing_policy = { policies: [ From 506748b8a44adef2e10d8cb2af382efe4a4166d1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 12 Sep 2023 12:41:35 -0700 Subject: [PATCH 600/694] Enable ring_hash tracing in interop tests --- packages/grpc-js-xds/interop/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/interop/Dockerfile b/packages/grpc-js-xds/interop/Dockerfile index 63a321061..6239b5f22 100644 --- a/packages/grpc-js-xds/interop/Dockerfile +++ b/packages/grpc-js-xds/interop/Dockerfile @@ -33,6 +33,6 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/ COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/ ENV GRPC_VERBOSITY="DEBUG" -ENV GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_cluster_resolver,xds_cluster_impl,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection,server,server_call +ENV GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager,cds_balancer,xds_cluster_resolver,xds_cluster_impl,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection,server,server_call,ring_hash ENTRYPOINT [ "/nodejs/bin/node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ] From a02622572aec2b236257b86842d26eb32e5805f0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 12 Sep 2023 13:00:15 -0700 Subject: [PATCH 601/694] Improve Listener resource log formatting --- packages/grpc-js-xds/src/xds-client.ts | 2 +- .../grpc-js-xds/src/xds-resource-type/listener-resource-type.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/src/xds-client.ts b/packages/grpc-js-xds/src/xds-client.ts index baabf2e76..d1aed9265 100644 --- a/packages/grpc-js-xds/src/xds-client.ts +++ b/packages/grpc-js-xds/src/xds-client.ts @@ -250,7 +250,7 @@ class AdsResponseParser { if (!decodeResult.value) { return; } - this.adsCallState.client.trace('Parsed resource of type ' + this.result.type.getTypeUrl() + ': ' + JSON.stringify(decodeResult.value, undefined, 2)); + this.adsCallState.client.trace('Parsed resource of type ' + this.result.type.getTypeUrl() + ': ' + JSON.stringify(decodeResult.value, (key, value) => (value && value.type === 'Buffer' && Array.isArray(value.data)) ? (value.data as Number[]).map(n => n.toString(16)).join('') : value, 2)); this.result.haveValidResources = true; if (this.result.type.resourcesEqual(resourceState.cachedResource, decodeResult.value)) { return; diff --git a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts index a557e1988..cf5d4d591 100644 --- a/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts +++ b/packages/grpc-js-xds/src/xds-resource-type/listener-resource-type.ts @@ -106,7 +106,7 @@ export class ListenerResourceType extends XdsResourceType { ); } const message = decodeSingleResource(LDS_TYPE_URL, resource.value); - trace('Decoded raw resource of type ' + LDS_TYPE_URL + ': ' + JSON.stringify(message, undefined, 2)); + trace('Decoded raw resource of type ' + LDS_TYPE_URL + ': ' + JSON.stringify(message, (key, value) => (value && value.type === 'Buffer' && Array.isArray(value.data)) ? (value.data as Number[]).map(n => n.toString(16)).join('') : value, 2)); const validatedMessage = this.validateResource(message); if (validatedMessage) { return { From 10c4bbdbe391bb806d55fe4070af811c79174834 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Sep 2023 10:18:30 -0700 Subject: [PATCH 602/694] Add logging for DNS update delays due to rate limit or backoff --- packages/grpc-js/src/backoff-timeout.ts | 15 +++++++++++++++ packages/grpc-js/src/resolver-dns.ts | 5 +++++ packages/grpc-js/src/resolving-load-balancer.ts | 1 + 3 files changed, 21 insertions(+) diff --git a/packages/grpc-js/src/backoff-timeout.ts b/packages/grpc-js/src/backoff-timeout.ts index 3ffd26064..78318d1e8 100644 --- a/packages/grpc-js/src/backoff-timeout.ts +++ b/packages/grpc-js/src/backoff-timeout.ts @@ -78,6 +78,11 @@ export class BackoffTimeout { * running is true. */ private startTime: Date = new Date(); + /** + * The approximate time that the currently running timer will end. Only valid + * if running is true. + */ + private endTime: Date = new Date(); constructor(private callback: () => void, options?: BackoffOptions) { if (options) { @@ -100,6 +105,8 @@ export class BackoffTimeout { } private runTimer(delay: number) { + this.endTime = this.startTime; + this.endTime.setMilliseconds(this.endTime.getMilliseconds() + this.nextDelay); clearTimeout(this.timerId); this.timerId = setTimeout(() => { this.callback(); @@ -178,4 +185,12 @@ export class BackoffTimeout { this.hasRef = false; this.timerId.unref?.(); } + + /** + * Get the approximate timestamp of when the timer will fire. Only valid if + * this.isRunning() is true. + */ + getEndTime() { + return this.endTime; + } } diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 6bb31a3a1..149530bbd 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -353,6 +353,11 @@ class DnsResolver implements Resolver { * fires. Otherwise, start resolving immediately. */ if (this.pendingLookupPromise === null) { if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) { + if (this.isNextResolutionTimerRunning) { + trace('resolution update delayed by "min time between resolutions" rate limit'); + } else { + trace('resolution update delayed by backoff timer until ' + this.backoff.getEndTime().toISOString()); + } this.continueResolving = true; } else { this.startResolutionWithBackoff(); diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 425d1fe2e..ceec4b5d1 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -222,6 +222,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { * In that case, the backoff timer callback will call * updateResolution */ if (this.backoffTimeout.isRunning()) { + trace('requestReresolution delayed by backoff timer until ' + this.backoffTimeout.getEndTime().toISOString()); this.continueResolving = true; } else { this.updateResolution(); From 6567f8d7cd908bf259d7e93bfc2ee06cdfe015fe Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Sep 2023 14:07:22 -0700 Subject: [PATCH 603/694] 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 604/694] 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' ], { From ab02dc0be4096d027d86141ba68a96e54c23761c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 13 Sep 2023 16:45:46 -0700 Subject: [PATCH 605/694] proto-loader: Allow the grpcLib option to be omitted in the type generator --- packages/proto-loader/README.md | 3 +- .../bin/proto-loader-gen-types.ts | 45 ++++++++++++------- packages/proto-loader/package.json | 2 +- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/packages/proto-loader/README.md b/packages/proto-loader/README.md index 2a7af61e3..5abf4d1dd 100644 --- a/packages/proto-loader/README.md +++ b/packages/proto-loader/README.md @@ -87,7 +87,8 @@ Options: -I, --includeDirs Directories to search for included files [array] -O, --outDir Directory in which to output files [string] [required] --grpcLib The gRPC implementation library that these types will - be used with [string] [required] + be used with. If not provided, some types will not be + generated [string] --inputTemplate Template for mapping input or "permissive" type names [string] [default: "%s"] --outputTemplate Template for mapping output or "restricted" type names diff --git a/packages/proto-loader/bin/proto-loader-gen-types.ts b/packages/proto-loader/bin/proto-loader-gen-types.ts index 944790ad5..6db109904 100644 --- a/packages/proto-loader/bin/proto-loader-gen-types.ts +++ b/packages/proto-loader/bin/proto-loader-gen-types.ts @@ -39,7 +39,7 @@ const useNameFmter = ({outputTemplate, inputTemplate}: GeneratorOptions) => { type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & { includeDirs?: string[]; - grpcLib: string; + grpcLib?: string; outDir: string; verbose?: boolean; includeComments?: boolean; @@ -522,12 +522,12 @@ function generateEnumInterface(formatter: TextFormatter, enumType: Protobuf.Enum * We always generate two service client methods per service method: one camel * cased, and one with the original casing. So we will still generate one * service client method for any conflicting name. - * + * * Technically, at runtime conflicting name in the service client method * actually shadows the original method, but TypeScript does not have a good * way to represent that. So this change is not 100% accurate, but it gets the * generated code to compile. - * + * * This is just a list of the methods in the Client class definitions in * grpc@1.24.11 and @grpc/grpc-js@1.4.0. */ @@ -640,7 +640,11 @@ function generateServiceHandlerInterface(formatter: TextFormatter, serviceType: function generateServiceDefinitionInterface(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { const {inputName, outputName} = useNameFmter(options); - formatter.writeLine(`export interface ${serviceType.name}Definition extends grpc.ServiceDefinition {`); + if (options.grpcLib) { + formatter.writeLine(`export interface ${serviceType.name}Definition extends grpc.ServiceDefinition {`); + } else { + formatter.writeLine(`export interface ${serviceType.name}Definition {`); + } formatter.indent(); for (const methodName of Object.keys(serviceType.methods).sort()) { const method = serviceType.methods[methodName]; @@ -655,8 +659,10 @@ function generateServiceDefinitionInterface(formatter: TextFormatter, serviceTyp function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protobuf.Service, options: GeneratorOptions) { formatter.writeLine(`// Original file: ${(serviceType.filename ?? 'null')?.replace(/\\/g, '/')}`); formatter.writeLine(''); - const grpcImportPath = options.grpcLib.startsWith('.') ? getPathToRoot(serviceType) + options.grpcLib : options.grpcLib; - formatter.writeLine(`import type * as grpc from '${grpcImportPath}'`); + if (options.grpcLib) { + const grpcImportPath = options.grpcLib.startsWith('.') ? getPathToRoot(serviceType) + options.grpcLib : options.grpcLib; + formatter.writeLine(`import type * as grpc from '${grpcImportPath}'`); + } formatter.writeLine(`import type { MethodDefinition } from '@grpc/proto-loader'`) const dependencies: Set = new Set(); for (const method of serviceType.methodsArray) { @@ -668,11 +674,13 @@ function generateServiceInterfaces(formatter: TextFormatter, serviceType: Protob } formatter.writeLine(''); - generateServiceClientInterface(formatter, serviceType, options); - formatter.writeLine(''); + if (options.grpcLib) { + generateServiceClientInterface(formatter, serviceType, options); + formatter.writeLine(''); - generateServiceHandlerInterface(formatter, serviceType, options); - formatter.writeLine(''); + generateServiceHandlerInterface(formatter, serviceType, options); + formatter.writeLine(''); + } generateServiceDefinitionInterface(formatter, serviceType, options); } @@ -742,6 +750,9 @@ function generateLoadedDefinitionTypes(formatter: TextFormatter, namespace: Prot } function generateRootFile(formatter: TextFormatter, root: Protobuf.Root, options: GeneratorOptions) { + if (!options.grpcLib) { + return; + } formatter.writeLine(`import type * as grpc from '${options.grpcLib}';`); generateDefinitionImports(formatter, root, options); formatter.writeLine(''); @@ -802,11 +813,13 @@ function writeFilesForRoot(root: Protobuf.Root, masterFileName: string, options: const filePromises: Promise[] = []; const masterFileFormatter = new TextFormatter(); - generateRootFile(masterFileFormatter, root, options); - if (options.verbose) { - console.log(`Writing ${options.outDir}/${masterFileName}`); + if (options.grpcLib) { + generateRootFile(masterFileFormatter, root, options); + if (options.verbose) { + console.log(`Writing ${options.outDir}/${masterFileName}`); + } + filePromises.push(writeFile(`${options.outDir}/${masterFileName}`, masterFileFormatter.getFullText())); } - filePromises.push(writeFile(`${options.outDir}/${masterFileName}`, masterFileFormatter.getFullText())); filePromises.push(...generateFilesForNamespace(root, options)); @@ -898,12 +911,12 @@ async function runScript() { includeComments: 'Generate doc comments from comments in the original files', includeDirs: 'Directories to search for included files', outDir: 'Directory in which to output files', - grpcLib: 'The gRPC implementation library that these types will be used with', + grpcLib: 'The gRPC implementation library that these types will be used with. If not provided, some types will not be generated', inputTemplate: 'Template for mapping input or "permissive" type names', outputTemplate: 'Template for mapping output or "restricted" type names', inputBranded: 'Output property for branded type for "permissive" types with fullName of the Message as its value', outputBranded: 'Output property for branded type for "restricted" types with fullName of the Message as its value', - }).demandOption(['outDir', 'grpcLib']) + }).demandOption(['outDir']) .demand(1) .usage('$0 [options] filenames...') .epilogue('WARNING: This tool is in alpha. The CLI and generated code are subject to change') diff --git a/packages/proto-loader/package.json b/packages/proto-loader/package.json index c53976be6..a7f4d159f 100644 --- a/packages/proto-loader/package.json +++ b/packages/proto-loader/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/proto-loader", - "version": "0.7.9", + "version": "0.7.10", "author": "Google Inc.", "contributors": [ { From afbdbdeec38f92ebcde0e5147910172b524605a1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Sep 2023 13:50:42 -0700 Subject: [PATCH 606/694] grpc-health-check: Add generated code for version 2.0 --- .../src/generated/grpc/health/v1/Health.ts | 10 ++ .../grpc/health/v1/HealthCheckRequest.ts | 10 ++ .../grpc/health/v1/HealthCheckResponse.ts | 37 +++++ .../test/generated/grpc/health/v1/Health.ts | 129 ++++++++++++++++++ .../grpc/health/v1/HealthCheckRequest.ts | 10 ++ .../grpc/health/v1/HealthCheckResponse.ts | 37 +++++ .../test/generated/health.ts | 26 ++++ 7 files changed, 259 insertions(+) create mode 100644 packages/grpc-health-check/src/generated/grpc/health/v1/Health.ts create mode 100644 packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckRequest.ts create mode 100644 packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckResponse.ts create mode 100644 packages/grpc-health-check/test/generated/grpc/health/v1/Health.ts create mode 100644 packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckRequest.ts create mode 100644 packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckResponse.ts create mode 100644 packages/grpc-health-check/test/generated/health.ts diff --git a/packages/grpc-health-check/src/generated/grpc/health/v1/Health.ts b/packages/grpc-health-check/src/generated/grpc/health/v1/Health.ts new file mode 100644 index 000000000..a308498f4 --- /dev/null +++ b/packages/grpc-health-check/src/generated/grpc/health/v1/Health.ts @@ -0,0 +1,10 @@ +// Original file: proto/health/v1/health.proto + +import type { MethodDefinition } from '@grpc/proto-loader' +import type { HealthCheckRequest as _grpc_health_v1_HealthCheckRequest, HealthCheckRequest__Output as _grpc_health_v1_HealthCheckRequest__Output } from '../../../grpc/health/v1/HealthCheckRequest'; +import type { HealthCheckResponse as _grpc_health_v1_HealthCheckResponse, HealthCheckResponse__Output as _grpc_health_v1_HealthCheckResponse__Output } from '../../../grpc/health/v1/HealthCheckResponse'; + +export interface HealthDefinition { + Check: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output> + Watch: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output> +} diff --git a/packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckRequest.ts b/packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckRequest.ts new file mode 100644 index 000000000..71ae9df4e --- /dev/null +++ b/packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckRequest.ts @@ -0,0 +1,10 @@ +// Original file: proto/health/v1/health.proto + + +export interface HealthCheckRequest { + 'service'?: (string); +} + +export interface HealthCheckRequest__Output { + 'service': (string); +} diff --git a/packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckResponse.ts b/packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckResponse.ts new file mode 100644 index 000000000..ee4f375ae --- /dev/null +++ b/packages/grpc-health-check/src/generated/grpc/health/v1/HealthCheckResponse.ts @@ -0,0 +1,37 @@ +// Original file: proto/health/v1/health.proto + + +// Original file: proto/health/v1/health.proto + +export const _grpc_health_v1_HealthCheckResponse_ServingStatus = { + UNKNOWN: 'UNKNOWN', + SERVING: 'SERVING', + NOT_SERVING: 'NOT_SERVING', + /** + * Used only by the Watch method. + */ + SERVICE_UNKNOWN: 'SERVICE_UNKNOWN', +} as const; + +export type _grpc_health_v1_HealthCheckResponse_ServingStatus = + | 'UNKNOWN' + | 0 + | 'SERVING' + | 1 + | 'NOT_SERVING' + | 2 + /** + * Used only by the Watch method. + */ + | 'SERVICE_UNKNOWN' + | 3 + +export type _grpc_health_v1_HealthCheckResponse_ServingStatus__Output = typeof _grpc_health_v1_HealthCheckResponse_ServingStatus[keyof typeof _grpc_health_v1_HealthCheckResponse_ServingStatus] + +export interface HealthCheckResponse { + 'status'?: (_grpc_health_v1_HealthCheckResponse_ServingStatus); +} + +export interface HealthCheckResponse__Output { + 'status': (_grpc_health_v1_HealthCheckResponse_ServingStatus__Output); +} diff --git a/packages/grpc-health-check/test/generated/grpc/health/v1/Health.ts b/packages/grpc-health-check/test/generated/grpc/health/v1/Health.ts new file mode 100644 index 000000000..320958e3c --- /dev/null +++ b/packages/grpc-health-check/test/generated/grpc/health/v1/Health.ts @@ -0,0 +1,129 @@ +// Original file: proto/health/v1/health.proto + +import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' +import type { HealthCheckRequest as _grpc_health_v1_HealthCheckRequest, HealthCheckRequest__Output as _grpc_health_v1_HealthCheckRequest__Output } from '../../../grpc/health/v1/HealthCheckRequest'; +import type { HealthCheckResponse as _grpc_health_v1_HealthCheckResponse, HealthCheckResponse__Output as _grpc_health_v1_HealthCheckResponse__Output } from '../../../grpc/health/v1/HealthCheckResponse'; + +/** + * Health is gRPC's mechanism for checking whether a server is able to handle + * RPCs. Its semantics are documented in + * https://github.com/grpc/grpc/blob/master/doc/health-checking.md. + */ +export interface HealthClient extends grpc.Client { + /** + * Check gets the health of the specified service. If the requested service + * is unknown, the call will fail with status NOT_FOUND. If the caller does + * not specify a service name, the server should respond with its overall + * health status. + * + * Clients should set a deadline when calling Check, and can declare the + * server unhealthy if they do not receive a timely response. + * + * Check implementations should be idempotent and side effect free. + */ + Check(argument: _grpc_health_v1_HealthCheckRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + Check(argument: _grpc_health_v1_HealthCheckRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + Check(argument: _grpc_health_v1_HealthCheckRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + Check(argument: _grpc_health_v1_HealthCheckRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + /** + * Check gets the health of the specified service. If the requested service + * is unknown, the call will fail with status NOT_FOUND. If the caller does + * not specify a service name, the server should respond with its overall + * health status. + * + * Clients should set a deadline when calling Check, and can declare the + * server unhealthy if they do not receive a timely response. + * + * Check implementations should be idempotent and side effect free. + */ + check(argument: _grpc_health_v1_HealthCheckRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + check(argument: _grpc_health_v1_HealthCheckRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + check(argument: _grpc_health_v1_HealthCheckRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + check(argument: _grpc_health_v1_HealthCheckRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall; + + /** + * Performs a watch for the serving status of the requested service. + * The server will immediately send back a message indicating the current + * serving status. It will then subsequently send a new message whenever + * the service's serving status changes. + * + * If the requested service is unknown when the call is received, the + * server will send a message setting the serving status to + * SERVICE_UNKNOWN but will *not* terminate the call. If at some + * future point, the serving status of the service becomes known, the + * server will send a new message with the service's serving status. + * + * If the call terminates with status UNIMPLEMENTED, then clients + * should assume this method is not supported and should not retry the + * call. If the call terminates with any other status (including OK), + * clients should retry the call with appropriate exponential backoff. + */ + Watch(argument: _grpc_health_v1_HealthCheckRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_health_v1_HealthCheckResponse__Output>; + Watch(argument: _grpc_health_v1_HealthCheckRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_health_v1_HealthCheckResponse__Output>; + /** + * Performs a watch for the serving status of the requested service. + * The server will immediately send back a message indicating the current + * serving status. It will then subsequently send a new message whenever + * the service's serving status changes. + * + * If the requested service is unknown when the call is received, the + * server will send a message setting the serving status to + * SERVICE_UNKNOWN but will *not* terminate the call. If at some + * future point, the serving status of the service becomes known, the + * server will send a new message with the service's serving status. + * + * If the call terminates with status UNIMPLEMENTED, then clients + * should assume this method is not supported and should not retry the + * call. If the call terminates with any other status (including OK), + * clients should retry the call with appropriate exponential backoff. + */ + watch(argument: _grpc_health_v1_HealthCheckRequest, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_health_v1_HealthCheckResponse__Output>; + watch(argument: _grpc_health_v1_HealthCheckRequest, options?: grpc.CallOptions): grpc.ClientReadableStream<_grpc_health_v1_HealthCheckResponse__Output>; + +} + +/** + * Health is gRPC's mechanism for checking whether a server is able to handle + * RPCs. Its semantics are documented in + * https://github.com/grpc/grpc/blob/master/doc/health-checking.md. + */ +export interface HealthHandlers extends grpc.UntypedServiceImplementation { + /** + * Check gets the health of the specified service. If the requested service + * is unknown, the call will fail with status NOT_FOUND. If the caller does + * not specify a service name, the server should respond with its overall + * health status. + * + * Clients should set a deadline when calling Check, and can declare the + * server unhealthy if they do not receive a timely response. + * + * Check implementations should be idempotent and side effect free. + */ + Check: grpc.handleUnaryCall<_grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse>; + + /** + * Performs a watch for the serving status of the requested service. + * The server will immediately send back a message indicating the current + * serving status. It will then subsequently send a new message whenever + * the service's serving status changes. + * + * If the requested service is unknown when the call is received, the + * server will send a message setting the serving status to + * SERVICE_UNKNOWN but will *not* terminate the call. If at some + * future point, the serving status of the service becomes known, the + * server will send a new message with the service's serving status. + * + * If the call terminates with status UNIMPLEMENTED, then clients + * should assume this method is not supported and should not retry the + * call. If the call terminates with any other status (including OK), + * clients should retry the call with appropriate exponential backoff. + */ + Watch: grpc.handleServerStreamingCall<_grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse>; + +} + +export interface HealthDefinition extends grpc.ServiceDefinition { + Check: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output> + Watch: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output> +} diff --git a/packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckRequest.ts b/packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckRequest.ts new file mode 100644 index 000000000..71ae9df4e --- /dev/null +++ b/packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckRequest.ts @@ -0,0 +1,10 @@ +// Original file: proto/health/v1/health.proto + + +export interface HealthCheckRequest { + 'service'?: (string); +} + +export interface HealthCheckRequest__Output { + 'service': (string); +} diff --git a/packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckResponse.ts b/packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckResponse.ts new file mode 100644 index 000000000..ee4f375ae --- /dev/null +++ b/packages/grpc-health-check/test/generated/grpc/health/v1/HealthCheckResponse.ts @@ -0,0 +1,37 @@ +// Original file: proto/health/v1/health.proto + + +// Original file: proto/health/v1/health.proto + +export const _grpc_health_v1_HealthCheckResponse_ServingStatus = { + UNKNOWN: 'UNKNOWN', + SERVING: 'SERVING', + NOT_SERVING: 'NOT_SERVING', + /** + * Used only by the Watch method. + */ + SERVICE_UNKNOWN: 'SERVICE_UNKNOWN', +} as const; + +export type _grpc_health_v1_HealthCheckResponse_ServingStatus = + | 'UNKNOWN' + | 0 + | 'SERVING' + | 1 + | 'NOT_SERVING' + | 2 + /** + * Used only by the Watch method. + */ + | 'SERVICE_UNKNOWN' + | 3 + +export type _grpc_health_v1_HealthCheckResponse_ServingStatus__Output = typeof _grpc_health_v1_HealthCheckResponse_ServingStatus[keyof typeof _grpc_health_v1_HealthCheckResponse_ServingStatus] + +export interface HealthCheckResponse { + 'status'?: (_grpc_health_v1_HealthCheckResponse_ServingStatus); +} + +export interface HealthCheckResponse__Output { + 'status': (_grpc_health_v1_HealthCheckResponse_ServingStatus__Output); +} diff --git a/packages/grpc-health-check/test/generated/health.ts b/packages/grpc-health-check/test/generated/health.ts new file mode 100644 index 000000000..afb2ced5f --- /dev/null +++ b/packages/grpc-health-check/test/generated/health.ts @@ -0,0 +1,26 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { MessageTypeDefinition } from '@grpc/proto-loader'; + +import type { HealthClient as _grpc_health_v1_HealthClient, HealthDefinition as _grpc_health_v1_HealthDefinition } from './grpc/health/v1/Health'; + +type SubtypeConstructor any, Subtype> = { + new(...args: ConstructorParameters): Subtype; +}; + +export interface ProtoGrpcType { + grpc: { + health: { + v1: { + /** + * Health is gRPC's mechanism for checking whether a server is able to handle + * RPCs. Its semantics are documented in + * https://github.com/grpc/grpc/blob/master/doc/health-checking.md. + */ + Health: SubtypeConstructor & { service: _grpc_health_v1_HealthDefinition } + HealthCheckRequest: MessageTypeDefinition + HealthCheckResponse: MessageTypeDefinition + } + } + } +} + From 524bb7d34142d8e8bfeb120b40e9a06ae6c9ad35 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Sep 2023 14:55:16 -0700 Subject: [PATCH 607/694] grpc-health-check: Implement version 2.0 update --- packages/grpc-health-check/README.md | 34 ++-- packages/grpc-health-check/gulpfile.ts | 28 ++-- packages/grpc-health-check/health.js | 55 ------- packages/grpc-health-check/package.json | 27 ++-- .../proto/health/v1/health.proto | 73 +++++++++ packages/grpc-health-check/src/health.ts | 112 +++++++++++++ .../grpc-health-check/src/object-stream.ts | 75 +++++++++ packages/grpc-health-check/src/server-type.ts | 103 ++++++++++++ .../grpc-health-check/test/health_test.js | 103 ------------ .../grpc-health-check/test/test-health.ts | 152 ++++++++++++++++++ packages/grpc-health-check/tsconfig.json | 29 ++++ 11 files changed, 599 insertions(+), 192 deletions(-) delete mode 100644 packages/grpc-health-check/health.js create mode 100644 packages/grpc-health-check/proto/health/v1/health.proto create mode 100644 packages/grpc-health-check/src/health.ts create mode 100644 packages/grpc-health-check/src/object-stream.ts create mode 100644 packages/grpc-health-check/src/server-type.ts delete mode 100644 packages/grpc-health-check/test/health_test.js create mode 100644 packages/grpc-health-check/test/test-health.ts create mode 100644 packages/grpc-health-check/tsconfig.json diff --git a/packages/grpc-health-check/README.md b/packages/grpc-health-check/README.md index 62a88347f..659dab140 100644 --- a/packages/grpc-health-check/README.md +++ b/packages/grpc-health-check/README.md @@ -4,11 +4,7 @@ Health check client and service for use with gRPC-node. ## Background -This package exports both a client and server that adhere to the [gRPC Health Checking Protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). - -By using this package, clients and servers can rely on common proto and service definitions. This means: -- Clients can use the generated stubs to health check _any_ server that adheres to the protocol. -- Servers do not reimplement common logic for publishing health statuses. +This package provides an implementation of the [gRPC Health Checking Protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) service, as described in [gRFC L106](https://github.com/grpc/proposal/blob/master/L106-node-heath-check-library.md). ## Installation @@ -22,33 +18,39 @@ npm install grpc-health-check ### Server -Any gRPC-node server can use `grpc-health-check` to adhere to the gRPC Health Checking Protocol. +Any gRPC-node server can use `grpc-health-check` to adhere to the gRPC Health Checking Protocol. The following shows how this package can be added to a pre-existing gRPC server. -```javascript 1.8 +```typescript // Import package -let health = require('grpc-health-check'); +import { HealthImplementation, ServingStatusMap } from 'grpc-health-check'; // Define service status map. Key is the service name, value is the corresponding status. -// By convention, the empty string "" key represents that status of the entire server. +// By convention, the empty string '' key represents that status of the entire server. const statusMap = { - "ServiceFoo": proto.grpc.health.v1.HealthCheckResponse.ServingStatus.SERVING, - "ServiceBar": proto.grpc.health.v1.HealthCheckResponse.ServingStatus.NOT_SERVING, - "": proto.grpc.health.v1.HealthCheckResponse.ServingStatus.NOT_SERVING, + 'ServiceFoo': 'SERVING', + 'ServiceBar': 'NOT_SERVING', + '': 'NOT_SERVING', }; // Construct the service implementation -let healthImpl = new health.Implementation(statusMap); +const healthImpl = new HealthImplementation(statusMap); + +healthImpl.addToServer(server); -// Add the service and implementation to your pre-existing gRPC-node server -server.addService(health.service, healthImpl); +// When ServiceBar comes up +healthImpl.setStatus('serviceBar', 'SERVING'); ``` Congrats! Your server now allows any client to run a health check against it. ### Client -Any gRPC-node client can use `grpc-health-check` to run health checks against other servers that follow the protocol. +Any gRPC-node client can use the `service` object exported by `grpc-health-check` to generate clients that can make health check requests. + +### Command Line Usage + +The absolute path to `health.proto` can be obtained on the command line with `node -p 'require("grpc-health-check").protoPath'`. ## Contributing diff --git a/packages/grpc-health-check/gulpfile.ts b/packages/grpc-health-check/gulpfile.ts index 0ddaa257e..f47087b14 100644 --- a/packages/grpc-health-check/gulpfile.ts +++ b/packages/grpc-health-check/gulpfile.ts @@ -19,22 +19,32 @@ import * as gulp from 'gulp'; import * as mocha from 'gulp-mocha'; import * as execa from 'execa'; import * as path from 'path'; -import * as del from 'del'; -import {linkSync} from '../../util'; const healthCheckDir = __dirname; -const baseDir = path.resolve(healthCheckDir, '..', '..'); -const testDir = path.resolve(healthCheckDir, 'test'); +const outDir = path.resolve(healthCheckDir, 'build'); -const runInstall = () => execa('npm', ['install', '--unsafe-perm'], {cwd: healthCheckDir, stdio: 'inherit'}); +const execNpmVerb = (verb: string, ...args: string[]) => + execa('npm', [verb, ...args], {cwd: healthCheckDir, stdio: 'inherit'}); +const execNpmCommand = execNpmVerb.bind(null, 'run'); -const runRebuild = () => execa('npm', ['rebuild', '--unsafe-perm'], {cwd: healthCheckDir, stdio: 'inherit'}); +const install = () => execNpmVerb('install', '--unsafe-perm'); -const install = gulp.series(runInstall, runRebuild); +/** + * Transpiles TypeScript files in src/ to JavaScript according to the settings + * found in tsconfig.json. + */ +const compile = () => execNpmCommand('compile'); + +const runTests = () => { + return gulp.src(`${outDir}/test/**/*.js`) + .pipe(mocha({reporter: 'mocha-jenkins-reporter', + require: ['ts-node/register']})); +}; -const test = () => gulp.src(`${testDir}/*.js`).pipe(mocha({reporter: 'mocha-jenkins-reporter'})); +const test = gulp.series(install, runTests); export { install, + compile, test -} \ No newline at end of file +} diff --git a/packages/grpc-health-check/health.js b/packages/grpc-health-check/health.js deleted file mode 100644 index cfa9c8348..000000000 --- a/packages/grpc-health-check/health.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * - * Copyright 2015 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. - * - */ - -'use strict'; - -var grpc = require('grpc'); - -var _get = require('lodash.get'); -var _clone = require('lodash.clone') - -var health_messages = require('./v1/health_pb'); -var health_service = require('./v1/health_grpc_pb'); - -function HealthImplementation(statusMap) { - this.statusMap = _clone(statusMap); -} - -HealthImplementation.prototype.setStatus = function(service, status) { - this.statusMap[service] = status; -}; - -HealthImplementation.prototype.check = function(call, callback){ - var service = call.request.getService(); - var status = _get(this.statusMap, service, null); - if (status === null) { - // TODO(murgatroid99): Do this without an explicit reference to grpc. - callback({code:grpc.status.NOT_FOUND}); - } else { - var response = new health_messages.HealthCheckResponse(); - response.setStatus(status); - callback(null, response); - } -}; - -module.exports = { - Client: health_service.HealthClient, - messages: health_messages, - service: health_service.HealthService, - Implementation: HealthImplementation -}; diff --git a/packages/grpc-health-check/package.json b/packages/grpc-health-check/package.json index e9b836346..a7fe1c3fd 100644 --- a/packages/grpc-health-check/package.json +++ b/packages/grpc-health-check/package.json @@ -1,6 +1,6 @@ { "name": "grpc-health-check", - "version": "1.8.0", + "version": "2.0.0", "author": "Google Inc.", "description": "Health check client and service for use with gRPC-node", "repository": { @@ -14,18 +14,27 @@ "email": "mlumish@google.com" } ], + "scripts": { + "compile": "tsc -p .", + "prepare": "npm run generate-types && npm run compile", + "generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated health/v1/health.proto", + "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O test/generated --grpcLib=@grpc/grpc-js health/v1/health.proto" + }, "dependencies": { - "google-protobuf": "^3.4.0", - "grpc": "^1.6.0", - "lodash.clone": "^4.5.0", - "lodash.get": "^4.4.2" + "@grpc/proto-loader": "^0.7.10", + "typescript": "^5.2.2" }, "files": [ "LICENSE", "README.md", - "health.js", - "v1" + "src", + "build", + "proto" ], - "main": "health.js", - "license": "Apache-2.0" + "main": "build/src/health.js", + "types": "build/src/health.d.ts", + "license": "Apache-2.0", + "devDependencies": { + "@grpc/grpc-js": "file:../grpc-js" + } } diff --git a/packages/grpc-health-check/proto/health/v1/health.proto b/packages/grpc-health-check/proto/health/v1/health.proto new file mode 100644 index 000000000..13b03f567 --- /dev/null +++ b/packages/grpc-health-check/proto/health/v1/health.proto @@ -0,0 +1,73 @@ +// Copyright 2015 The 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. + +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + +syntax = "proto3"; + +package grpc.health.v1; + +option csharp_namespace = "Grpc.Health.V1"; +option go_package = "google.golang.org/grpc/health/grpc_health_v1"; +option java_multiple_files = true; +option java_outer_classname = "HealthProto"; +option java_package = "io.grpc.health.v1"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + SERVICE_UNKNOWN = 3; // Used only by the Watch method. + } + ServingStatus status = 1; +} + +// Health is gRPC's mechanism for checking whether a server is able to handle +// RPCs. Its semantics are documented in +// https://github.com/grpc/grpc/blob/master/doc/health-checking.md. +service Health { + // Check gets the health of the specified service. If the requested service + // is unknown, the call will fail with status NOT_FOUND. If the caller does + // not specify a service name, the server should respond with its overall + // health status. + // + // Clients should set a deadline when calling Check, and can declare the + // server unhealthy if they do not receive a timely response. + // + // Check implementations should be idempotent and side effect free. + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); + + // Performs a watch for the serving status of the requested service. + // The server will immediately send back a message indicating the current + // serving status. It will then subsequently send a new message whenever + // the service's serving status changes. + // + // If the requested service is unknown when the call is received, the + // server will send a message setting the serving status to + // SERVICE_UNKNOWN but will *not* terminate the call. If at some + // future point, the serving status of the service becomes known, the + // server will send a new message with the service's serving status. + // + // If the call terminates with status UNIMPLEMENTED, then clients + // should assume this method is not supported and should not retry the + // call. If the call terminates with any other status (including OK), + // clients should retry the call with appropriate exponential backoff. + rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); +} diff --git a/packages/grpc-health-check/src/health.ts b/packages/grpc-health-check/src/health.ts new file mode 100644 index 000000000..86ca1af0d --- /dev/null +++ b/packages/grpc-health-check/src/health.ts @@ -0,0 +1,112 @@ +/* + * + * 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. + * + */ + +import * as path from 'path'; +import { loadSync, ServiceDefinition } from '@grpc/proto-loader'; +import { HealthCheckRequest__Output } from './generated/grpc/health/v1/HealthCheckRequest'; +import { HealthCheckResponse } from './generated/grpc/health/v1/HealthCheckResponse'; +import { sendUnaryData, Server, ServerUnaryCall, ServerWritableStream } from './server-type'; + +const loadedProto = loadSync('health/v1/health.proto', { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + includeDirs: [`${__dirname}/../../proto`], +}); + +export const service = loadedProto['grpc.health.v1.Health'] as ServiceDefinition; + +const GRPC_STATUS_NOT_FOUND = 5; + +export type ServingStatus = 'UNKNOWN' | 'SERVING' | 'NOT_SERVING'; + +export interface ServingStatusMap { + [serviceName: string]: ServingStatus; +} + +interface StatusWatcher { + (status: ServingStatus): void; +} + +export class HealthImplementation { + private statusMap: Map = new Map(); + private watchers: Map> = new Map(); + constructor(initialStatusMap?: ServingStatusMap) { + if (initialStatusMap) { + for (const [serviceName, status] of Object.entries(initialStatusMap)) { + this.statusMap.set(serviceName, status); + } + } + } + + setStatus(service: string, status: ServingStatus) { + this.statusMap.set(service, status); + for (const watcher of this.watchers.get(service) ?? []) { + watcher(status); + } + } + + private addWatcher(service: string, watcher: StatusWatcher) { + const existingWatcherSet = this.watchers.get(service); + if (existingWatcherSet) { + existingWatcherSet.add(watcher); + } else { + const newWatcherSet = new Set(); + newWatcherSet.add(watcher); + this.watchers.set(service, newWatcherSet); + } + } + + private removeWatcher(service: string, watcher: StatusWatcher) { + this.watchers.get(service)?.delete(watcher); + } + + addToServer(server: Server) { + server.addService(service, { + check: (call: ServerUnaryCall, callback: sendUnaryData) => { + const serviceName = call.request.service; + const status = this.statusMap.get(serviceName); + if (status) { + callback(null, {status: status}); + } else { + callback({code: GRPC_STATUS_NOT_FOUND, details: `Health status unknown for service ${serviceName}`}); + } + }, + watch: (call: ServerWritableStream) => { + const serviceName = call.request.service; + const statusWatcher = (status: ServingStatus) => { + call.write({status: status}); + }; + this.addWatcher(serviceName, statusWatcher); + call.on('cancelled', () => { + this.removeWatcher(serviceName, statusWatcher); + }); + const currentStatus = this.statusMap.get(serviceName); + if (currentStatus) { + call.write({status: currentStatus}); + } else { + call.write({status: 'SERVICE_UNKNOWN'}); + } + } + }); + } +} + +export const protoPath = path.resolve(__dirname, '../../proto/health/v1/health.proto'); diff --git a/packages/grpc-health-check/src/object-stream.ts b/packages/grpc-health-check/src/object-stream.ts new file mode 100644 index 000000000..2f70cfa7e --- /dev/null +++ b/packages/grpc-health-check/src/object-stream.ts @@ -0,0 +1,75 @@ +/* + * 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. + * + */ + +import { Readable, Writable } from 'stream'; + +interface EmitterAugmentation1 { + addListener(event: Name, listener: (arg1: Arg) => void): this; + emit(event: Name, arg1: Arg): boolean; + on(event: Name, listener: (arg1: Arg) => void): this; + once(event: Name, listener: (arg1: Arg) => void): this; + prependListener(event: Name, listener: (arg1: Arg) => void): this; + prependOnceListener(event: Name, listener: (arg1: Arg) => void): this; + removeListener(event: Name, listener: (arg1: Arg) => void): this; +} + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type WriteCallback = (error: Error | null | undefined) => void; + +export interface IntermediateObjectReadable extends Readable { + read(size?: number): any & T; +} + +export type ObjectReadable = { + read(size?: number): T; +} & EmitterAugmentation1<'data', T> & + IntermediateObjectReadable; + +export interface IntermediateObjectWritable extends Writable { + _write(chunk: any & T, encoding: string, callback: Function): void; + write(chunk: any & T, cb?: WriteCallback): boolean; + write(chunk: any & T, encoding?: any, cb?: WriteCallback): boolean; + setDefaultEncoding(encoding: string): this; + end(): ReturnType extends Writable ? this : void; + end( + chunk: any & T, + cb?: Function + ): ReturnType extends Writable ? this : void; + end( + chunk: any & T, + encoding?: any, + cb?: Function + ): ReturnType extends Writable ? this : void; +} + +export interface ObjectWritable extends IntermediateObjectWritable { + _write(chunk: T, encoding: string, callback: Function): void; + write(chunk: T, cb?: Function): boolean; + write(chunk: T, encoding?: any, cb?: Function): boolean; + setDefaultEncoding(encoding: string): this; + end(): ReturnType extends Writable ? this : void; + end( + chunk: T, + cb?: Function + ): ReturnType extends Writable ? this : void; + end( + chunk: T, + encoding?: any, + cb?: Function + ): ReturnType extends Writable ? this : void; +} diff --git a/packages/grpc-health-check/src/server-type.ts b/packages/grpc-health-check/src/server-type.ts new file mode 100644 index 000000000..f07704e87 --- /dev/null +++ b/packages/grpc-health-check/src/server-type.ts @@ -0,0 +1,103 @@ +/* + * 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. + * + */ + +import { ServiceDefinition } from '@grpc/proto-loader'; +import { ObjectReadable, ObjectWritable } from './object-stream'; +import { EventEmitter } from 'events'; + +type Metadata = any; + +interface StatusObject { + code: number; + details: string; + metadata: Metadata; +} + +type Deadline = Date | number; + +type ServerStatusResponse = Partial; + +type ServerErrorResponse = ServerStatusResponse & Error; + +type ServerSurfaceCall = { + cancelled: boolean; + readonly metadata: Metadata; + getPeer(): string; + sendMetadata(responseMetadata: Metadata): void; + getDeadline(): Deadline; + getPath(): string; +} & EventEmitter; + +export type ServerUnaryCall = ServerSurfaceCall & { + request: RequestType; +}; +type ServerReadableStream = + ServerSurfaceCall & ObjectReadable; +export type ServerWritableStream = + ServerSurfaceCall & + ObjectWritable & { + request: RequestType; + end: (metadata?: Metadata) => void; + }; +type ServerDuplexStream = ServerSurfaceCall & + ObjectReadable & + ObjectWritable & { end: (metadata?: Metadata) => void }; + +// Unary response callback signature. +export type sendUnaryData = ( + error: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number +) => void; + +// User provided handler for unary calls. +type handleUnaryCall = ( + call: ServerUnaryCall, + callback: sendUnaryData +) => void; + +// User provided handler for client streaming calls. +type handleClientStreamingCall = ( + call: ServerReadableStream, + callback: sendUnaryData +) => void; + +// User provided handler for server streaming calls. +type handleServerStreamingCall = ( + call: ServerWritableStream +) => void; + +// User provided handler for bidirectional streaming calls. +type handleBidiStreamingCall = ( + call: ServerDuplexStream +) => void; + +export type HandleCall = + | handleUnaryCall + | handleClientStreamingCall + | handleServerStreamingCall + | handleBidiStreamingCall; + +export type UntypedHandleCall = HandleCall; +export interface UntypedServiceImplementation { + [name: string]: UntypedHandleCall; +} + +export interface Server { + addService(service: ServiceDefinition, implementation: UntypedServiceImplementation): void; +} diff --git a/packages/grpc-health-check/test/health_test.js b/packages/grpc-health-check/test/health_test.js deleted file mode 100644 index a31d3b371..000000000 --- a/packages/grpc-health-check/test/health_test.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * - * Copyright 2015 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. - * - */ - -'use strict'; - -var assert = require('assert'); - -var health = require('../health'); - -var health_messages = require('../v1/health_pb'); - -var ServingStatus = health_messages.HealthCheckResponse.ServingStatus; - -var grpc = require('grpc'); - -describe('Health Checking', function() { - var statusMap = { - '': ServingStatus.SERVING, - 'grpc.test.TestServiceNotServing': ServingStatus.NOT_SERVING, - 'grpc.test.TestServiceServing': ServingStatus.SERVING - }; - var healthServer; - var healthImpl; - var healthClient; - before(function() { - healthServer = new grpc.Server(); - healthImpl = new health.Implementation(statusMap); - healthServer.addService(health.service, healthImpl); - var port_num = healthServer.bind('0.0.0.0:0', - grpc.ServerCredentials.createInsecure()); - healthServer.start(); - healthClient = new health.Client('localhost:' + port_num, - grpc.credentials.createInsecure()); - }); - after(function() { - healthServer.forceShutdown(); - }); - it('should say an enabled service is SERVING', function(done) { - var request = new health_messages.HealthCheckRequest(); - request.setService(''); - healthClient.check(request, function(err, response) { - assert.ifError(err); - assert.strictEqual(response.getStatus(), ServingStatus.SERVING); - done(); - }); - }); - it('should say that a disabled service is NOT_SERVING', function(done) { - var request = new health_messages.HealthCheckRequest(); - request.setService('grpc.test.TestServiceNotServing'); - healthClient.check(request, function(err, response) { - assert.ifError(err); - assert.strictEqual(response.getStatus(), ServingStatus.NOT_SERVING); - done(); - }); - }); - it('should say that an enabled service is SERVING', function(done) { - var request = new health_messages.HealthCheckRequest(); - request.setService('grpc.test.TestServiceServing'); - healthClient.check(request, function(err, response) { - assert.ifError(err); - assert.strictEqual(response.getStatus(), ServingStatus.SERVING); - done(); - }); - }); - it('should get NOT_FOUND if the service is not registered', function(done) { - var request = new health_messages.HealthCheckRequest(); - request.setService('not_registered'); - healthClient.check(request, function(err, response) { - assert(err); - assert.strictEqual(err.code, grpc.status.NOT_FOUND); - done(); - }); - }); - it('should get a different response if the status changes', function(done) { - var request = new health_messages.HealthCheckRequest(); - request.setService('transient'); - healthClient.check(request, function(err, response) { - assert(err); - assert.strictEqual(err.code, grpc.status.NOT_FOUND); - healthImpl.setStatus('transient', ServingStatus.SERVING); - healthClient.check(request, function(err, response) { - assert.ifError(err); - assert.strictEqual(response.getStatus(), ServingStatus.SERVING); - done(); - }); - }); - }); -}); diff --git a/packages/grpc-health-check/test/test-health.ts b/packages/grpc-health-check/test/test-health.ts new file mode 100644 index 000000000..80d60a234 --- /dev/null +++ b/packages/grpc-health-check/test/test-health.ts @@ -0,0 +1,152 @@ +/* + * + * 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. + * + */ + +import * as assert from 'assert'; +import * as grpc from '@grpc/grpc-js'; +import { HealthImplementation, ServingStatusMap, service as healthServiceDefinition } from '../src/health'; +import { HealthClient } from './generated/grpc/health/v1/Health'; +import { HealthCheckResponse__Output, _grpc_health_v1_HealthCheckResponse_ServingStatus__Output } from './generated/grpc/health/v1/HealthCheckResponse'; + +describe('Health checking', () => { + const statusMap: ServingStatusMap = { + '': 'SERVING', + 'grpc.test.TestServiceNotServing': 'NOT_SERVING', + 'grpc.test.TestServiceServing': 'SERVING' + }; + let healthServer: grpc.Server; + let healthClient: HealthClient; + let healthImpl: HealthImplementation; + beforeEach(done => { + healthServer = new grpc.Server(); + healthImpl = new HealthImplementation(statusMap); + healthImpl.addToServer(healthServer); + healthServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + if (error) { + done(error); + return; + } + const HealthClientConstructor = grpc.makeClientConstructor(healthServiceDefinition, 'grpc.health.v1.HealthService'); + healthClient = new HealthClientConstructor(`localhost:${port}`, grpc.credentials.createInsecure()) as unknown as HealthClient; + healthServer.start(); + done(); + }); + }); + afterEach((done) => { + healthClient.close(); + healthServer.tryShutdown(done); + }); + describe('check', () => { + it('Should say that an enabled service is SERVING', done => { + healthClient.check({service: ''}, (error, value) => { + assert.ifError(error); + assert.strictEqual(value?.status, 'SERVING'); + done(); + }); + }); + it('Should say that a disabled service is NOT_SERVING', done => { + healthClient.check({service: 'grpc.test.TestServiceNotServing'}, (error, value) => { + assert.ifError(error); + assert.strictEqual(value?.status, 'NOT_SERVING'); + done(); + }); + }); + it('Should get NOT_FOUND if the service is not registered', done => { + healthClient.check({service: 'not_registered'}, (error, value) => { + assert(error); + assert.strictEqual(error.code, grpc.status.NOT_FOUND); + done(); + }); + }); + it('Should get a different response if the health status changes', done => { + healthClient.check({service: 'transient'}, (error, value) => { + assert(error); + assert.strictEqual(error.code, grpc.status.NOT_FOUND); + healthImpl.setStatus('transient', 'SERVING'); + healthClient.check({service: 'transient'}, (error, value) => { + assert.ifError(error); + assert.strictEqual(value?.status, 'SERVING'); + done(); + }); + }); + }); + }); + describe('watch', () => { + it('Should respond with the health status for an existing service', done => { + const call = healthClient.watch({service: ''}); + call.on('data', (response: HealthCheckResponse__Output) => { + assert.strictEqual(response.status, 'SERVING'); + call.cancel(); + }); + call.on('error', () => {}); + call.on('status', status => { + assert.strictEqual(status.code, grpc.status.CANCELLED); + done(); + }); + }); + it('Should send a new update when the status changes', done => { + const receivedStatusList: _grpc_health_v1_HealthCheckResponse_ServingStatus__Output[] = []; + const call = healthClient.watch({service: 'grpc.test.TestServiceServing'}); + call.on('data', (response: HealthCheckResponse__Output) => { + switch (receivedStatusList.length) { + case 0: + assert.strictEqual(response.status, 'SERVING'); + healthImpl.setStatus('grpc.test.TestServiceServing', 'NOT_SERVING'); + break; + case 1: + assert.strictEqual(response.status, 'NOT_SERVING'); + call.cancel(); + break; + default: + assert.fail(`Unexpected third status update ${response.status}`); + } + receivedStatusList.push(response.status); + }); + call.on('error', () => {}); + call.on('status', status => { + assert.deepStrictEqual(receivedStatusList, ['SERVING', 'NOT_SERVING']); + assert.strictEqual(status.code, grpc.status.CANCELLED); + done(); + }); + }); + it('Should update when a service that did not exist is added', done => { + const receivedStatusList: _grpc_health_v1_HealthCheckResponse_ServingStatus__Output[] = []; + const call = healthClient.watch({service: 'transient'}); + call.on('data', (response: HealthCheckResponse__Output) => { + switch (receivedStatusList.length) { + case 0: + assert.strictEqual(response.status, 'SERVICE_UNKNOWN'); + healthImpl.setStatus('transient', 'SERVING'); + break; + case 1: + assert.strictEqual(response.status, 'SERVING'); + call.cancel(); + break; + default: + assert.fail(`Unexpected third status update ${response.status}`); + } + receivedStatusList.push(response.status); + }); + call.on('error', () => {}); + call.on('status', status => { + assert.deepStrictEqual(receivedStatusList, ['SERVICE_UNKNOWN', 'SERVING']); + assert.strictEqual(status.code, grpc.status.CANCELLED); + done(); + }); + }) + }); +}); diff --git a/packages/grpc-health-check/tsconfig.json b/packages/grpc-health-check/tsconfig.json new file mode 100644 index 000000000..763ceda98 --- /dev/null +++ b/packages/grpc-health-check/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "declaration": true, + "forceConsistentCasingInFileNames": true, + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "pretty": true, + "sourceMap": true, + "strict": true, + "lib": ["es2017"], + "outDir": "build", + "target": "es2017", + "module": "commonjs", + "resolveJsonModule": true, + "incremental": true, + "types": ["mocha"], + "noUnusedLocals": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} From 5be024f060a7cc7377167aabde95e3aefd54fd87 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 18 Sep 2023 17:32:29 -0700 Subject: [PATCH 608/694] grpc-js: Delegate to child picker in ResolvingLoadBalancer#updateResolution --- packages/grpc-js/src/resolving-load-balancer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index e600047e8..3f52093a1 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -264,7 +264,11 @@ export class ResolvingLoadBalancer implements LoadBalancer { private updateResolution() { this.innerResolver.updateResolution(); if (this.currentState === ConnectivityState.IDLE) { - this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); + /* this.latestChildPicker is initialized as new QueuePicker(this), which + * is an appropriate value here if the child LB policy is unset. + * Otherwise, we want to delegate to the child here, in case that + * triggers something. */ + this.updateState(ConnectivityState.CONNECTING, this.latestChildPicker); } this.backoffTimeout.runOnce(); } From c8b9a45bc952a3586535d909088609552f54ab8e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 21 Sep 2023 10:01:38 -0700 Subject: [PATCH 609/694] grpc-js-xds: Fix behavior when channel goes IDLE --- packages/grpc-js-xds/package.json | 2 +- packages/grpc-js-xds/src/resolver-xds.ts | 14 ++++++----- packages/grpc-js-xds/test/client.ts | 14 +++++++---- packages/grpc-js-xds/test/test-core.ts | 31 ++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index a55c631cf..606bd9faf 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.9.0", + "version": "1.9.1", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index a934e7285..17d3c6305 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -213,7 +213,7 @@ function getPredicateForMatcher(routeMatch: RouteMatch__Output): Matcher { * the ServiceConfig definition. The difference is that the protobuf message * defines seconds as a long, which is represented as a string in JavaScript, * and the one used in the service config defines it as a number. - * @param duration + * @param duration */ function protoDurationToDuration(duration: Duration__Output): Duration { return { @@ -235,7 +235,7 @@ function getDefaultRetryMaxInterval(baseInterval: string): string { /** * Encode a text string as a valid path of a URI, as specified in RFC-3986 section 3.3 * @param uriPath A value representing an unencoded URI path - * @returns + * @returns */ function encodeURIPath(uriPath: string): string { return uriPath.replace(/[^A-Za-z0-9._~!$&^()*+,;=/-]/g, substring => encodeURIComponent(substring)); @@ -447,7 +447,7 @@ class XdsResolver implements Resolver { } } } - let retryPolicy: RetryPolicy | undefined = undefined; + let retryPolicy: RetryPolicy | undefined = undefined; if (EXPERIMENTAL_RETRY) { const retryConfig = route.route!.retry_policy ?? virtualHost.retry_policy; if (retryConfig) { @@ -458,10 +458,10 @@ class XdsResolver implements Resolver { } } if (retryableStatusCodes.length > 0) { - const baseInterval = retryConfig.retry_back_off?.base_interval ? - protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) : + const baseInterval = retryConfig.retry_back_off?.base_interval ? + protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) : DEFAULT_RETRY_BASE_INTERVAL; - const maxInterval = retryConfig.retry_back_off?.max_interval ? + const maxInterval = retryConfig.retry_back_off?.max_interval ? protoDurationToSecondsString(retryConfig.retry_back_off.max_interval) : getDefaultRetryMaxInterval(baseInterval); retryPolicy = { @@ -664,9 +664,11 @@ class XdsResolver implements Resolver { destroy() { if (this.listenerResourceName) { ListenerResourceType.cancelWatch(this.xdsClient, this.listenerResourceName, this.ldsWatcher); + this.isLdsWatcherActive = false; } if (this.latestRouteConfigName) { RouteConfigurationResourceType.cancelWatch(this.xdsClient, this.latestRouteConfigName, this.rdsWatcher); + this.latestRouteConfigName = null; } } diff --git a/packages/grpc-js-xds/test/client.ts b/packages/grpc-js-xds/test/client.ts index 6d346f918..0779702bb 100644 --- a/packages/grpc-js-xds/test/client.ts +++ b/packages/grpc-js-xds/test/client.ts @@ -15,7 +15,7 @@ * */ -import { credentials, loadPackageDefinition, ServiceError } from "@grpc/grpc-js"; +import { ChannelOptions, credentials, loadPackageDefinition, ServiceError } from "@grpc/grpc-js"; import { loadSync } from "@grpc/proto-loader"; import { ProtoGrpcType } from "./generated/echo"; import { EchoTestServiceClient } from "./generated/grpc/testing/EchoTestService"; @@ -44,14 +44,14 @@ export class XdsTestClient { private client: EchoTestServiceClient; private callInterval: NodeJS.Timer; - constructor(target: string, bootstrapInfo: string) { - this.client = new loadedProtos.grpc.testing.EchoTestService(target, credentials.createInsecure(), {[BOOTSTRAP_CONFIG_KEY]: bootstrapInfo}); + constructor(target: string, bootstrapInfo: string, options?: ChannelOptions) { + this.client = new loadedProtos.grpc.testing.EchoTestService(target, credentials.createInsecure(), {...options, [BOOTSTRAP_CONFIG_KEY]: bootstrapInfo}); this.callInterval = setInterval(() => {}, 0); clearInterval(this.callInterval); } - static createFromServer(targetName: string, xdsServer: XdsServer) { - return new XdsTestClient(`xds:///${targetName}`, xdsServer.getBootstrapInfoString()); + static createFromServer(targetName: string, xdsServer: XdsServer, options?: ChannelOptions) { + return new XdsTestClient(`xds:///${targetName}`, xdsServer.getBootstrapInfoString(), options); } startCalls(interval: number) { @@ -98,4 +98,8 @@ export class XdsTestClient { } sendInner(count, callback); } + + getConnectivityState() { + return this.client.getChannel().getConnectivityState(false); + } } diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts index cb145eb81..f48ab6c11 100644 --- a/packages/grpc-js-xds/test/test-core.ts +++ b/packages/grpc-js-xds/test/test-core.ts @@ -22,6 +22,7 @@ import { XdsServer } from "./xds-server"; import { register } from "../src"; import assert = require("assert"); +import { connectivityState } from "@grpc/grpc-js"; register(); @@ -60,4 +61,34 @@ describe('core xDS functionality', () => { }, reason => done(reason)); }, reason => done(reason)); }); + it('should be able to enter and exit idle', function(done) { + this.timeout(5000); + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}]); + 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, { + 'grpc.client_idle_timeout_ms': 1000, + }); + client.sendOneCall(error => { + assert.ifError(error); + assert.strictEqual(client.getConnectivityState(), connectivityState.READY); + setTimeout(() => { + assert.strictEqual(client.getConnectivityState(), connectivityState.IDLE); + client.sendOneCall(error => { + done(error); + }) + }, 1100); + }); + }, reason => done(reason)); + }); }); From e1415fe7bc86a625d4b95f82728dccb0ca809b65 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 25 Sep 2023 10:24:28 -0700 Subject: [PATCH 610/694] grpc-js-xds: Force submodule update and code generation in prepare script --- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js-xds/src/generated/cluster.ts | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 606bd9faf..ccf5c1d0c 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.9.1", + "version": "1.9.2", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -9,7 +9,7 @@ "clean": "gts clean", "compile": "tsc", "fix": "gts fix", - "prepare": "npm run compile", + "prepare": "git submodule update --init --recursive && npm run generate-types && 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", diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index 681bc5a2d..78ac3bbd3 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -97,15 +97,6 @@ export interface ProtoGrpcType { } } } - extensions: { - clusters: { - aggregate: { - v3: { - ClusterConfig: MessageTypeDefinition - } - } - } - } type: { matcher: { v3: { From 86debcd83b28432c6be17759832621fc274a8ff8 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 25 Sep 2023 17:20:36 -0700 Subject: [PATCH 611/694] Add cancellation example --- examples/cancellation/README.md | 18 +++++++++ examples/cancellation/client.js | 64 ++++++++++++++++++++++++++++++++ examples/cancellation/server.js | 66 +++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 examples/cancellation/README.md create mode 100644 examples/cancellation/client.js create mode 100644 examples/cancellation/server.js diff --git a/examples/cancellation/README.md b/examples/cancellation/README.md new file mode 100644 index 000000000..87d773475 --- /dev/null +++ b/examples/cancellation/README.md @@ -0,0 +1,18 @@ +# Cancellation + +This example shows how clients can cancel in-flight RPCs by cancelling the +call object returned by the method invocation. The client will receive a status +with code `CANCELLED` and the server handler's call object will emit a +`'cancelled'` event. + +## Start the server + +``` +node server.js +``` + +## Run the client + +``` +node client.js +``` diff --git a/examples/cancellation/client.js b/examples/cancellation/client.js new file mode 100644 index 000000000..c76487dfe --- /dev/null +++ b/examples/cancellation/client.js @@ -0,0 +1,64 @@ +/* + * + * 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. + * + */ + +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const parseArgs = require('minimist'); + +const PROTO_PATH = __dirname + '/../protos/echo.proto'; + +const packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo; + +function main() { + let argv = parseArgs(process.argv.slice(2), { + string: 'target', + default: {target: 'localhost:50052'} + }); + const client = new echoProto.Echo(argv.target, grpc.credentials.createInsecure()); + const call = client.bidirectionalStreamingEcho(); + const EXPECTED_MESSAGES = 2; + let receivedMessages = 0; + call.on('data', value => { + console.log(`received message "${value.message}"`) + receivedMessages += 1; + if (receivedMessages >= EXPECTED_MESSAGES) { + console.log('cancelling call'); + call.cancel(); + } + }); + call.on('status', statusObject => { + console.log(`received call status with code ${grpc.status[statusObject.code]}`); + }); + call.on('error', error => { + console.log(`received error ${error}`); + }) + console.log('sending message "hello"'); + call.write({message: 'hello'}); + console.log('sending message "world"') + call.write({message: 'world'}); +} + +main(); diff --git a/examples/cancellation/server.js b/examples/cancellation/server.js new file mode 100644 index 000000000..b9ff13fcc --- /dev/null +++ b/examples/cancellation/server.js @@ -0,0 +1,66 @@ +/* + * + * 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. + * + */ + +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const parseArgs = require('minimist'); + +const PROTO_PATH = __dirname + '/../protos/echo.proto'; + +const packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo; + +function bidirectionalStreamingEcho(call) { + call.on('data', value => { + const message = value.message; + console.log(`echoing message "${message}"`); + call.write({message: message}); + }); + call.on('end', () => { + call.end(); + }); + call.on('cancelled', () => { + console.log('received cancelled event'); + }); +} + +const serviceImplementation = { + bidirectionalStreamingEcho +} + +function main() { + const argv = parseArgs(process.argv.slice(2), { + string: 'port', + default: {port: '50052'} + }); + const server = new grpc.Server(); + server.addService(echoProto.Echo.service, serviceImplementation); + server.bindAsync(`0.0.0.0:${argv.port}`, grpc.ServerCredentials.createInsecure(), () => { + server.start(); + }); + client = new echoProto.Echo(`localhost:${argv.port}`, grpc.credentials.createInsecure()); +} + +main(); From 0ebfe60bf24496ef24479adeb1cf4e4a6c18efec Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 26 Sep 2023 09:46:56 -0700 Subject: [PATCH 612/694] Add error handling example --- examples/error_handling/README.md | 23 ++++++++ examples/error_handling/client.js | 89 +++++++++++++++++++++++++++++++ examples/error_handling/server.js | 68 +++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 examples/error_handling/README.md create mode 100644 examples/error_handling/client.js create mode 100644 examples/error_handling/server.js diff --git a/examples/error_handling/README.md b/examples/error_handling/README.md new file mode 100644 index 000000000..c1ba71d68 --- /dev/null +++ b/examples/error_handling/README.md @@ -0,0 +1,23 @@ +# Error Handling + +This example demonstrates basic RPC error handling in gRPC for unary and +streaming response cardinalities. + +## Start the server + +Run the server, whcih returns an error if the RPC request's `name` field is +empty. + +``` +node server.js +``` + +## Run the client + +Then run the client in another terminal, which makes two requests for each of +unary and streaming responses: one with an empty Name field and one with it +populated with the current username provided by os/user. + +``` +node client.js +``` diff --git a/examples/error_handling/client.js b/examples/error_handling/client.js new file mode 100644 index 000000000..1a8eff8ea --- /dev/null +++ b/examples/error_handling/client.js @@ -0,0 +1,89 @@ +/* + * + * 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. + * + */ + +const grpc = require('@grpc/grpc-js'); +const protoLoader = require('@grpc/proto-loader'); +const parseArgs = require('minimist'); +const os = require('os'); + +const PROTO_PATH = __dirname + '/../protos/helloworld.proto'; + +const packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +const helloProto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +function unaryCall(client, requestId, name, expectedCode) { + console.log(`[${requestId}] Calling SayHello with name:"${name}"`); + return new Promise((resolve, reject) => { + client.sayHello({name: name}, (error, value) => { + if (error) { + if (error.code === expectedCode) { + console.log(`[${requestId}] Received error ${error.message}`); + } else { + console.log(`[${requestId}] Received unexpected error ${error.message}`); + } + } + if (value) { + console.log(`[${requestId}] Received response ${value.message}`); + } + resolve(); + }); + }); +} + +function streamingCall(client, requestId, name, expectedCode) { + console.log(`[${requestId}] Calling SayHelloStreamReply with name:"${name}"`); + return new Promise((resolve, reject) => { + const call = client.sayHelloStreamReply({name: name}); + call.on('data', value => { + console.log(`[${requestId}] Received response ${value.message}`); + }); + call.on('status', status => { + console.log(`[${requestId}] Received status with code=${grpc.status[status.code]} details=${status.details}`); + resolve(); + }); + call.on('error', error => { + if (error.code === expectedCode) { + console.log(`[${requestId}] Received expected error ${error.message}`); + } else { + console.log(`[${requestId}] Received unexpected error ${error.message}`); + } + }); + }); +} + +async function main() { + let argv = parseArgs(process.argv.slice(2), { + string: 'target', + default: {target: 'localhost:50052'} + }); + const client = new helloProto.Greeter(argv.target, grpc.credentials.createInsecure()); + const name = os.userInfo().username ?? 'unknown'; + await unaryCall(client, 1, '', grpc.status.INVALID_ARGUMENT); + await unaryCall(client, 2, name, grpc.status.OK); + await streamingCall(client, 3, '', grpc.status.INVALID_ARGUMENT); + await streamingCall(client, 4, name, grpc.status.OK); +} + +main(); diff --git a/examples/error_handling/server.js b/examples/error_handling/server.js new file mode 100644 index 000000000..e77701848 --- /dev/null +++ b/examples/error_handling/server.js @@ -0,0 +1,68 @@ +/* + * + * 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. + * + */ + +var PROTO_PATH = __dirname + '/../protos/helloworld.proto'; + +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var packageDefinition = protoLoader.loadSync( + PROTO_PATH, + {keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + }); +var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld; + +/** + * Implements the SayHello RPC method. + */ +function sayHello(call, callback) { + if (call.request.name === '') { + callback({code: grpc.status.INVALID_ARGUMENT, details: 'request missing required field: name'}); + } + callback(null, {message: 'Hello ' + call.request.name}); +} + +const REPLY_COUNT = 5; + +function sayHelloStreamReply(call) { + if (call.request.name === '') { + call.emit('error', {code: grpc.status.INVALID_ARGUMENT, details: 'request missing required field: name'}); + } else { + for (let i = 0; i < REPLY_COUNT; i++) { + call.write({message: 'Hello ' + call.request.name}); + } + call.end(); + } +} + +/** + * Starts an RPC server that receives requests for the Greeter service at the + * sample server port + */ +function main() { + var server = new grpc.Server(); + server.addService(hello_proto.Greeter.service, {sayHello: sayHello, sayHelloStreamReply: sayHelloStreamReply}); + server.bindAsync('0.0.0.0:50052', grpc.ServerCredentials.createInsecure(), () => { + server.start(); + }); +} + +main(); From e6099d71f2764b8abeaf9d1bbf166c73b55bf2fd Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 26 Sep 2023 15:17:55 -0700 Subject: [PATCH 613/694] grpc-js: Unref backoff timer in subchannel --- packages/grpc-js/src/subchannel.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 91455f7c1..0f8844720 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -120,6 +120,7 @@ export class Subchannel { this.backoffTimeout = new BackoffTimeout(() => { this.handleBackoffTimer(); }, backoffOptions); + this.backoffTimeout.unref(); this.subchannelAddressString = subchannelAddressToString(subchannelAddress); this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; From 4c6869091e33580fa868e5ad12406fd21fe62341 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 26 Sep 2023 16:06:03 -0700 Subject: [PATCH 614/694] grpc-js-xds: Don't call git commands in npm scripts --- packages/grpc-js-xds/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index ccf5c1d0c..a6f0fcf1d 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -9,7 +9,7 @@ "clean": "gts clean", "compile": "tsc", "fix": "gts fix", - "prepare": "git submodule update --init --recursive && npm run generate-types && npm run compile", + "prepare": "npm run generate-types && 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", From ea6ba89ead05b603d42a5075b47e05107e684c02 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 26 Sep 2023 16:35:13 -0700 Subject: [PATCH 615/694] grpc-js: Bump version to 1.9.4 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 7b8801212..5dbf72506 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.3", + "version": "1.9.4", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 974b235a04b6df4b070e693a6d2aefa8449f0c98 Mon Sep 17 00:00:00 2001 From: Rafael Santos Date: Fri, 29 Sep 2023 15:44:42 +0100 Subject: [PATCH 616/694] Update server-call.ts Fix TS2345 --- packages/grpc-js/src/server-call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 95f928350..107c2e3ef 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -558,7 +558,7 @@ export class Http2ServerCallStream< return metadata; } - receiveUnaryMessage(encoding: string): Promise { + receiveUnaryMessage(encoding: string): Promise { return new Promise((resolve, reject) => { const { stream } = this; From b33b8bc2bbbc58430ade1688a3b71a14f29887b7 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 29 Sep 2023 11:17:23 -0700 Subject: [PATCH 617/694] grpc-js: Handle race between bindAsync and (try|force)Shutdown --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/server.ts | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 5dbf72506..003663b6f 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.4", + "version": "1.9.5", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index c9308ca62..0d8e0825b 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -161,6 +161,7 @@ export class Server { >(); private sessions = new Map(); private started = false; + private shutdown = false; private options: ChannelOptions; private serverAddressString = 'null'; @@ -375,6 +376,10 @@ export class Server { throw new Error('server is already started'); } + if (this.shutdown) { + throw new Error('bindAsync called after shutdown'); + } + if (typeof port !== 'string') { throw new TypeError('port must be a string'); } @@ -485,6 +490,11 @@ export class Server { http2Server.once('error', onError); http2Server.listen(addr, () => { + if (this.shutdown) { + http2Server.close(); + resolve(new Error('bindAsync failed because server is shutdown')); + return; + } const boundAddress = http2Server.address()!; let boundSubchannelAddress: SubchannelAddress; if (typeof boundAddress === 'string') { @@ -583,6 +593,11 @@ export class Server { http2Server.once('error', onError); http2Server.listen(address, () => { + if (this.shutdown) { + http2Server.close(); + resolve({port: 0, count: 0}); + return; + } const boundAddress = http2Server.address() as AddressInfo; const boundSubchannelAddress: SubchannelAddress = { host: boundAddress.address, @@ -637,6 +652,12 @@ export class Server { ) => { // We only want one resolution result. Discard all future results resolverListener.onSuccessfulResolution = () => {}; + if (this.shutdown) { + deferredCallback( + new Error(`bindAsync failed because server is shutdown`), + 0 + ); + } if (addressList.length === 0) { deferredCallback( new Error(`No addresses resolved for port ${port}`), @@ -707,6 +728,7 @@ export class Server { } this.started = false; + this.shutdown = true; // Always destroy any available sessions. It's possible that one or more // tryShutdown() calls are in progress. Don't wait on them to finish. @@ -785,6 +807,7 @@ export class Server { // Close the server if necessary. this.started = false; + this.shutdown = true; for (const { server: http2Server, channelzRef: ref } of this .http2ServerList) { From 2003c8859c0740eac33fd4ab12fad5d087b9c121 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 2 Oct 2023 10:12:03 -0700 Subject: [PATCH 618/694] Cancellation example: corrected information --- examples/cancellation/README.md | 4 ++-- examples/cancellation/server.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/cancellation/README.md b/examples/cancellation/README.md index 87d773475..5dcd76c09 100644 --- a/examples/cancellation/README.md +++ b/examples/cancellation/README.md @@ -2,8 +2,8 @@ This example shows how clients can cancel in-flight RPCs by cancelling the call object returned by the method invocation. The client will receive a status -with code `CANCELLED` and the server handler's call object will emit a -`'cancelled'` event. +with code `CANCELLED` and the server handler's call object will emit either a +`'cancelled'` event or an `'end'` event. ## Start the server diff --git a/examples/cancellation/server.js b/examples/cancellation/server.js index b9ff13fcc..d68033d42 100644 --- a/examples/cancellation/server.js +++ b/examples/cancellation/server.js @@ -38,11 +38,13 @@ function bidirectionalStreamingEcho(call) { console.log(`echoing message "${message}"`); call.write({message: message}); }); + // Either 'end' or 'cancelled' will be emitted when the call is cancelled call.on('end', () => { + console.log('server received end event') call.end(); }); call.on('cancelled', () => { - console.log('received cancelled event'); + console.log('server received cancelled event'); }); } From abac01a9cf6c66afc1dace2832ef445c3d45a947 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 11 Oct 2023 17:43:14 +0900 Subject: [PATCH 619/694] chore(grpc-js): remove unused callcredentials parameter from insecure impl --- packages/grpc-js/src/channel-credentials.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/channel-credentials.ts b/packages/grpc-js/src/channel-credentials.ts index 72b19d65e..2ed18507f 100644 --- a/packages/grpc-js/src/channel-credentials.ts +++ b/packages/grpc-js/src/channel-credentials.ts @@ -163,8 +163,8 @@ export abstract class ChannelCredentials { } class InsecureChannelCredentialsImpl extends ChannelCredentials { - constructor(callCredentials?: CallCredentials) { - super(callCredentials); + constructor() { + super(); } compose(callCredentials: CallCredentials): never { From 976567395e91bf7064b86d8f609fa8791454abbc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 16 Oct 2023 15:16:58 -0700 Subject: [PATCH 620/694] grpc-js: Deprecate Server#start --- packages/grpc-js/src/server.ts | 36 +++++++++++++++------------- packages/grpc-js/test/test-server.ts | 21 ---------------- 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 4099f1350..f04cd9810 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -16,6 +16,7 @@ */ import * as http2 from 'http2'; +import * as util from 'util'; import { AddressInfo } from 'net'; import { ServiceError } from './call'; @@ -89,6 +90,17 @@ interface BindResult { function noop(): void {} +/** + * Decorator to wrap a class method with util.deprecate + * @param message The message to output if the deprecated method is called + * @returns + */ +function deprecate(message: string) { + return function (target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext Return>) { + return util.deprecate(target, message); + } +} + function getUnimplementedStatusResponse( methodName: string ): Partial { @@ -160,6 +172,10 @@ export class Server { UntypedHandler >(); private sessions = new Map(); + /** + * This field only exists to ensure that the start method throws an error if + * it is called twice, as it did previously. + */ private started = false; private options: ChannelOptions; private serverAddressString = 'null'; @@ -371,10 +387,6 @@ export class Server { creds: ServerCredentials, callback: (error: Error | null, port: number) => void ): void { - if (this.started === true) { - throw new Error('server is already started'); - } - if (typeof port !== 'string') { throw new TypeError('port must be a string'); } @@ -709,8 +721,6 @@ export class Server { } } - this.started = false; - // Always destroy any available sessions. It's possible that one or more // tryShutdown() calls are in progress. Don't wait on them to finish. this.sessions.forEach((channelzInfo, session) => { @@ -750,6 +760,10 @@ export class Server { return this.handlers.delete(name); } + /** + * @deprecated No longer needed as of version 1.10.x + */ + @deprecate('Calling start() is no longer necessary. It can be safely omitted.') start(): void { if ( this.http2ServerList.length === 0 || @@ -763,9 +777,6 @@ export class Server { if (this.started === true) { throw new Error('server is already started'); } - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Starting'); - } this.started = true; } @@ -786,9 +797,6 @@ export class Server { } } - // Close the server if necessary. - this.started = false; - for (const { server: http2Server, channelzRef: ref } of this .http2ServerList) { if (http2Server.listening) { @@ -1053,10 +1061,6 @@ export class Server { http2Server.on('stream', handler.bind(this)); http2Server.on('session', session => { - if (!this.started) { - session.destroy(); - return; - } const channelzRef = registerChannelzSocket( session.socket.remoteAddress ?? 'unknown', diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 2a7a48181..d1b485ec3 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -113,27 +113,6 @@ describe('Server', () => { }); }); - it('throws if bind is called after the server is started', done => { - const server = new Server(); - - server.bindAsync( - 'localhost:0', - ServerCredentials.createInsecure(), - (err, port) => { - assert.ifError(err); - server.start(); - assert.throws(() => { - server.bindAsync( - 'localhost:0', - ServerCredentials.createInsecure(), - noop - ); - }, /server is already started/); - server.tryShutdown(done); - } - ); - }); - it('throws on invalid inputs', () => { const server = new Server(); From 0f8ebbdd1786ad134ea17af2615e6070ee961bc1 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 16 Oct 2023 17:06:32 -0700 Subject: [PATCH 621/694] grpc-js: Include library version and PID in all trace logs --- doc/environment_variables.md | 3 +-- packages/grpc-js/src/index.ts | 7 ------- packages/grpc-js/src/logging.ts | 5 ++++- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/doc/environment_variables.md b/doc/environment_variables.md index 70b32e715..1b5ad26af 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -42,7 +42,6 @@ can be set. - `subchannel_internals` - Traces HTTP/2 session state. Includes per-call logs. - `channel_stacktrace` - Traces channel construction events with stack traces. - `keepalive` - Traces gRPC keepalive pings - - `index` - Traces module loading - `outlier_detection` - Traces outlier detection events The following tracers are added by the `@grpc/grpc-js-xds` library: @@ -62,4 +61,4 @@ can be set. - DEBUG - log all gRPC messages - INFO - log INFO and ERROR message - ERROR - log only errors (default) - - NONE - won't log any \ No newline at end of file + - NONE - won't log any diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index adacae08f..28fc776c7 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -273,14 +273,7 @@ import * as load_balancer_outlier_detection from './load-balancer-outlier-detect import * as channelz from './channelz'; import { Deadline } from './deadline'; -const clientVersion = require('../../package.json').version; - (() => { - logging.trace( - LogVerbosity.DEBUG, - 'index', - 'Loading @grpc/grpc-js version ' + clientVersion - ); resolver_dns.setup(); resolver_uds.setup(); resolver_ip.setup(); diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts index 83438ef73..e1b396fff 100644 --- a/packages/grpc-js/src/logging.ts +++ b/packages/grpc-js/src/logging.ts @@ -16,6 +16,9 @@ */ import { LogVerbosity } from './constants'; +import { pid } from 'process'; + +const clientVersion = require('../../package.json').version; const DEFAULT_LOGGER: Partial = { error: (message?: any, ...optionalParams: any[]) => { @@ -109,7 +112,7 @@ export function trace( text: string ): void { if (isTracerEnabled(tracer)) { - log(severity, new Date().toISOString() + ' | ' + tracer + ' | ' + text); + log(severity, new Date().toISOString() + ' | v' + clientVersion + ' ' + pid + ' | ' + tracer + ' | ' + text); } } From 3a9f4d2aa698ec595c2b923e6aac6d478d4b2a44 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 16 Oct 2023 16:52:41 -0700 Subject: [PATCH 622/694] grpc-js: Propagate connectivity error information to request errors --- packages/grpc-js/package.json | 2 +- .../grpc-js/src/load-balancer-pick-first.ts | 20 +++++++++++++++---- .../grpc-js/src/load-balancer-round-robin.ts | 12 ++++++++--- packages/grpc-js/src/picker.ts | 17 +++++++--------- packages/grpc-js/src/subchannel-interface.ts | 3 ++- packages/grpc-js/src/subchannel.ts | 8 +++++--- packages/grpc-js/src/transport.ts | 9 +++++++-- 7 files changed, 47 insertions(+), 24 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 003663b6f..9d66b4095 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.5", + "version": "1.9.6", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 0f2d7d654..1a22b3f76 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -153,9 +153,11 @@ export class PickFirstLoadBalancer implements LoadBalancer { private subchannelStateListener: ConnectivityStateListener = ( subchannel, previousState, - newState + newState, + keepaliveTime, + errorMessage ) => { - this.onSubchannelStateUpdate(subchannel, previousState, newState); + this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage); }; /** * Timer reference for the timer tracking when to start @@ -172,6 +174,12 @@ export class PickFirstLoadBalancer implements LoadBalancer { */ private stickyTransientFailureMode = false; + /** + * The most recent error reported by any subchannel as it transitioned to + * TRANSIENT_FAILURE. + */ + private lastError: string | null = null; + /** * Load balancer that attempts to connect to each backend in the address list * in order, and picks the first one that connects, using it for every @@ -200,7 +208,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { if (this.stickyTransientFailureMode) { this.updateState( ConnectivityState.TRANSIENT_FAILURE, - new UnavailablePicker() + new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`}) ); } else { this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); @@ -241,7 +249,8 @@ export class PickFirstLoadBalancer implements LoadBalancer { private onSubchannelStateUpdate( subchannel: SubchannelInterface, previousState: ConnectivityState, - newState: ConnectivityState + newState: ConnectivityState, + errorMessage?: string ) { if (this.currentPick?.realSubchannelEquals(subchannel)) { if (newState !== ConnectivityState.READY) { @@ -258,6 +267,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { } if (newState === ConnectivityState.TRANSIENT_FAILURE) { child.hasReportedTransientFailure = true; + if (errorMessage) { + this.lastError = errorMessage; + } this.maybeEnterStickyTransientFailureMode(); if (index === this.currentSubchannelIndex) { this.startNextSubchannelConnecting(index + 1); diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index f389fefc0..062aa9f0d 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -105,18 +105,24 @@ export class RoundRobinLoadBalancer implements LoadBalancer { private currentReadyPicker: RoundRobinPicker | null = null; + private lastError: string | null = null; + constructor(private readonly channelControlHelper: ChannelControlHelper) { this.subchannelStateListener = ( subchannel: SubchannelInterface, previousState: ConnectivityState, - newState: ConnectivityState + newState: ConnectivityState, + keepaliveTime: number, + errorMessage?: string ) => { this.calculateAndUpdateState(); - if ( newState === ConnectivityState.TRANSIENT_FAILURE || newState === ConnectivityState.IDLE ) { + if (errorMessage) { + this.lastError = errorMessage; + } this.channelControlHelper.requestReresolution(); subchannel.startConnecting(); } @@ -157,7 +163,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer { ) { this.updateState( ConnectivityState.TRANSIENT_FAILURE, - new UnavailablePicker() + new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`}) ); } else { this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); diff --git a/packages/grpc-js/src/picker.ts b/packages/grpc-js/src/picker.ts index d95eca21b..6474269f7 100644 --- a/packages/grpc-js/src/picker.ts +++ b/packages/grpc-js/src/picker.ts @@ -97,16 +97,13 @@ export interface Picker { */ export class UnavailablePicker implements Picker { private status: StatusObject; - constructor(status?: StatusObject) { - if (status !== undefined) { - this.status = status; - } else { - this.status = { - code: Status.UNAVAILABLE, - details: 'No connection established', - metadata: new Metadata(), - }; - } + constructor(status?: Partial) { + this.status = { + code: Status.UNAVAILABLE, + details: 'No connection established', + metadata: new Metadata(), + ...status, + }; } pick(pickArgs: PickArgs): TransientFailurePickResult { return { diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts index 9b947ad32..cc19c22c4 100644 --- a/packages/grpc-js/src/subchannel-interface.ts +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -23,7 +23,8 @@ export type ConnectivityStateListener = ( subchannel: SubchannelInterface, previousState: ConnectivityState, newState: ConnectivityState, - keepaliveTime: number + keepaliveTime: number, + errorMessage?: string ) => void; /** diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 0f8844720..03bbf035f 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -250,7 +250,8 @@ export class Subchannel { error => { this.transitionToState( [ConnectivityState.CONNECTING], - ConnectivityState.TRANSIENT_FAILURE + ConnectivityState.TRANSIENT_FAILURE, + `${error}` ); } ); @@ -265,7 +266,8 @@ export class Subchannel { */ private transitionToState( oldStates: ConnectivityState[], - newState: ConnectivityState + newState: ConnectivityState, + errorMessage?: string ): boolean { if (oldStates.indexOf(this.connectivityState) === -1) { return false; @@ -318,7 +320,7 @@ export class Subchannel { throw new Error(`Invalid state: unknown ConnectivityState ${newState}`); } for (const listener of this.stateListeners) { - listener(this, previousState, newState, this.keepaliveTime); + listener(this, previousState, newState, this.keepaliveTime, errorMessage); } return true; } diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 49ec01ddc..8dc4e2de6 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -741,6 +741,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { connectionOptions ); this.session = session; + let errorMessage = 'Failed to connect'; session.unref(); session.once('connect', () => { session.removeAllListeners(); @@ -749,10 +750,14 @@ export class Http2SubchannelConnector implements SubchannelConnector { }); session.once('close', () => { this.session = null; - reject(); + // Leave time for error event to happen before rejecting + setImmediate(() => { + reject(`${errorMessage} (${new Date().toISOString()})`); + }); }); session.once('error', error => { - this.trace('connection failed with error ' + (error as Error).message); + errorMessage = (error as Error).message; + this.trace('connection failed with error ' + errorMessage); }); }); } From 2f5ddc713774e863eef5c708e4f666b3a60d81ef Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 19 Oct 2023 13:57:57 -0700 Subject: [PATCH 623/694] grpc-js: pick_first: fix happy eyeballs and reresolution in sticky TF mode --- packages/grpc-js/package.json | 2 +- .../grpc-js/src/load-balancer-pick-first.ts | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 9d66b4095..29e4b5849 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.6", + "version": "1.9.7", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 1a22b3f76..5e8361ace 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -174,6 +174,12 @@ export class PickFirstLoadBalancer implements LoadBalancer { */ private stickyTransientFailureMode = false; + /** + * Indicates whether we called channelControlHelper.requestReresolution since + * the last call to updateAddressList + */ + private requestedResolutionSinceLastUpdate = false; + /** * The most recent error reported by any subchannel as it transitioned to * TRANSIENT_FAILURE. @@ -216,15 +222,28 @@ export class PickFirstLoadBalancer implements LoadBalancer { } } + private requestReresolution() { + this.requestedResolutionSinceLastUpdate = true; + this.channelControlHelper.requestReresolution(); + } + private maybeEnterStickyTransientFailureMode() { - if (this.stickyTransientFailureMode) { + if (!this.allChildrenHaveReportedTF()) { return; } - if (!this.allChildrenHaveReportedTF()) { + if (!this.requestedResolutionSinceLastUpdate) { + /* Each time we get an update we reset each subchannel's + * hasReportedTransientFailure flag, so the next time we get to this + * point after that, each subchannel has reported TRANSIENT_FAILURE + * at least once since then. That is the trigger for requesting + * reresolution, whether or not the LB policy is already in sticky TF + * mode. */ + this.requestReresolution(); + } + if (this.stickyTransientFailureMode) { return; } this.stickyTransientFailureMode = true; - this.channelControlHelper.requestReresolution(); for (const { subchannel } of this.children) { subchannel.startConnecting(); } @@ -256,7 +275,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { if (newState !== ConnectivityState.READY) { this.removeCurrentPick(); this.calculateAndReportNewState(); - this.channelControlHelper.requestReresolution(); + this.requestReresolution(); } return; } @@ -283,7 +302,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { private startNextSubchannelConnecting(startIndex: number) { clearTimeout(this.connectionDelayTimeout); - if (this.triedAllSubchannels || this.stickyTransientFailureMode) { + if (this.triedAllSubchannels) { return; } for (const [index, child] of this.children.entries()) { @@ -382,6 +401,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.currentSubchannelIndex = 0; this.children = []; this.triedAllSubchannels = false; + this.requestedResolutionSinceLastUpdate = false; } updateAddressList( From d465f839d46d708ca93a06956181a7e27b4e7ba0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 19 Oct 2023 16:20:04 -0700 Subject: [PATCH 624/694] Add pick_first requestReresolution tests --- packages/grpc-js/test/test-pick-first.ts | 90 +++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index e9e9e5601..a018e7a2d 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -41,7 +41,11 @@ function updateStateCallBackForExpectedStateSequence( ) { const actualStateSequence: ConnectivityState[] = []; let lastPicker: Picker | null = null; + let finished = false; return (connectivityState: ConnectivityState, picker: Picker) => { + if (finished) { + return; + } // Ignore duplicate state transitions if ( connectivityState === actualStateSequence[actualStateSequence.length - 1] @@ -60,6 +64,7 @@ function updateStateCallBackForExpectedStateSequence( if ( expectedStateSequence[actualStateSequence.length] !== connectivityState ) { + finished = true; done( new Error( `Unexpected state ${ @@ -69,10 +74,12 @@ function updateStateCallBackForExpectedStateSequence( )}]` ) ); + return; } actualStateSequence.push(connectivityState); lastPicker = picker; if (actualStateSequence.length === expectedStateSequence.length) { + finished = true; done(); } }; @@ -90,7 +97,7 @@ describe('Shuffler', () => { }); }); -describe('pick_first load balancing policy', () => { +describe.only('pick_first load balancing policy', () => { const config = new PickFirstLoadBalancingConfig(false); let subchannels: MockSubchannel[] = []; const baseChannelControlHelper: ChannelControlHelper = { @@ -462,6 +469,87 @@ describe('pick_first load balancing policy', () => { }); }); }); + it('Should request reresolution every time each child reports TF', done => { + let reresolutionRequestCount = 0; + const targetReresolutionRequestCount = 3; + const currentStartState = ConnectivityState.IDLE; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.CONNECTING, ConnectivityState.TRANSIENT_FAILURE], + err => setImmediate(() => { + assert.strictEqual(reresolutionRequestCount, targetReresolutionRequestCount); + done(err); + }) + ), + requestReresolution: () => { + reresolutionRequestCount += 1; + } + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + process.nextTick(() => { + pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + process.nextTick(() => { + subchannels[1].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + process.nextTick(() => { + pickFirst.updateAddressList([{ host: 'localhost', port: 3 }], config); + process.nextTick(() => { + subchannels[2].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + }); + }); + }); + }); + }); + }); + it('Should request reresolution if the new subchannels are already in TF', done => { + let reresolutionRequestCount = 0; + const targetReresolutionRequestCount = 3; + const currentStartState = ConnectivityState.TRANSIENT_FAILURE; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.TRANSIENT_FAILURE], + err => setImmediate(() => { + assert.strictEqual(reresolutionRequestCount, targetReresolutionRequestCount); + done(err); + }) + ), + requestReresolution: () => { + reresolutionRequestCount += 1; + } + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + process.nextTick(() => { + pickFirst.updateAddressList([{ host: 'localhost', port: 2 }], config); + }); + }); + }); describe('Address list randomization', () => { const shuffleConfig = new PickFirstLoadBalancingConfig(true); it('Should pick different subchannels after multiple updates', done => { From 446f139b37c6d446dd4d322043747018b760d80e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 27 Oct 2023 10:14:58 -0700 Subject: [PATCH 625/694] grpc-js: Cancel and don't start idle timer on shutdown --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/internal-channel.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 29e4b5849..6627b0103 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.7", + "version": "1.9.8", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 0ed189c03..f36849ef8 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -554,7 +554,7 @@ export class InternalChannel { } private maybeStartIdleTimer() { - if (this.callCount === 0) { + if (this.connectivityState !== ConnectivityState.SHUTDOWN && this.callCount === 0) { this.idleTimer = setTimeout(() => { this.trace( 'Idle timer triggered after ' + @@ -706,6 +706,9 @@ export class InternalChannel { this.resolvingLoadBalancer.destroy(); this.updateState(ConnectivityState.SHUTDOWN); clearInterval(this.callRefTimer); + if (this.idleTimer) { + clearTimeout(this.idleTimer); + } if (this.channelzEnabled) { unregisterChannelzRef(this.channelzRef); } From 9050ea9dae8b5f015f2d9ecd12fb4432ab953eef Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 30 Oct 2023 09:42:29 -0700 Subject: [PATCH 626/694] grpc-js: Don't repeat fixed resolver results --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/resolver-dns.ts | 25 ++++++++++-------- packages/grpc-js/src/resolver-ip.ts | 32 +++++++++++++----------- packages/grpc-js/test/test-pick-first.ts | 2 +- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 6627b0103..56fa0d3ff 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.8", + "version": "1.9.9", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 149530bbd..9431183d3 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -99,6 +99,7 @@ class DnsResolver implements Resolver { private nextResolutionTimer: NodeJS.Timeout; private isNextResolutionTimerRunning = false; private isServiceConfigEnabled = true; + private returnedIpResult = false; constructor( private target: GrpcUri, private listener: ResolverListener, @@ -163,16 +164,19 @@ class DnsResolver implements Resolver { */ private startResolution() { if (this.ipResult !== null) { - trace('Returning IP address for target ' + uriToString(this.target)); - setImmediate(() => { - this.listener.onSuccessfulResolution( - this.ipResult!, - null, - null, - null, - {} - ); - }); + if (!this.returnedIpResult) { + trace('Returning IP address for target ' + uriToString(this.target)); + setImmediate(() => { + this.listener.onSuccessfulResolution( + this.ipResult!, + null, + null, + null, + {} + ); + }); + this.returnedIpResult = true; + } this.backoff.stop(); this.backoff.reset(); this.stopNextResolutionTimer(); @@ -380,6 +384,7 @@ class DnsResolver implements Resolver { this.latestLookupResult = null; this.latestServiceConfig = null; this.latestServiceConfigError = null; + this.returnedIpResult = false; } /** diff --git a/packages/grpc-js/src/resolver-ip.ts b/packages/grpc-js/src/resolver-ip.ts index 0704131e1..7cf4f5645 100644 --- a/packages/grpc-js/src/resolver-ip.ts +++ b/packages/grpc-js/src/resolver-ip.ts @@ -41,6 +41,7 @@ const DEFAULT_PORT = 443; class IpResolver implements Resolver { private addresses: SubchannelAddress[] = []; private error: StatusObject | null = null; + private hasReturnedResult = false; constructor( target: GrpcUri, private listener: ResolverListener, @@ -87,22 +88,25 @@ class IpResolver implements Resolver { trace('Parsed ' + target.scheme + ' address list ' + this.addresses); } updateResolution(): void { - process.nextTick(() => { - if (this.error) { - this.listener.onError(this.error); - } else { - this.listener.onSuccessfulResolution( - this.addresses, - null, - null, - null, - {} - ); - } - }); + if (!this.hasReturnedResult) { + this.hasReturnedResult = true; + process.nextTick(() => { + if (this.error) { + this.listener.onError(this.error); + } else { + this.listener.onSuccessfulResolution( + this.addresses, + null, + null, + null, + {} + ); + } + }); + } } destroy(): void { - // This resolver owns no resources, so we do nothing here. + this.hasReturnedResult = false; } static getDefaultAuthority(target: GrpcUri): string { diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index a018e7a2d..075448882 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -97,7 +97,7 @@ describe('Shuffler', () => { }); }); -describe.only('pick_first load balancing policy', () => { +describe('pick_first load balancing policy', () => { const config = new PickFirstLoadBalancingConfig(false); let subchannels: MockSubchannel[] = []; const baseChannelControlHelper: ChannelControlHelper = { From 1f148e93496f4fb7ed245ed7c5c3b157552b16b4 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Fri, 27 Oct 2023 19:28:37 +0300 Subject: [PATCH 627/694] Fix missing port in proxy CONNECT when using the default HTTPS port --- packages/grpc-js/src/http_proxy.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index 3aed28c85..d1df22d2b 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -189,12 +189,16 @@ export function getProxiedConnection( if (parsedTarget === null) { return Promise.resolve({}); } + const targetHostPost = splitHostPort(parsedTarget.path); + if (targetHostPost === null) { + return Promise.resolve({}); + } const options: http.RequestOptions = { method: 'CONNECT', - path: parsedTarget.path, + path: targetHostPost.host + ':' + (targetHostPost.port != null ? targetHostPost.port : '443'), }; const headers: http.OutgoingHttpHeaders = { - Host: parsedTarget.path, + Host: targetHostPost.host + ':' + (targetHostPost.port != null ? targetHostPost.port : '443'), }; // Connect to the subchannel address as a proxy if (isTcpSubchannelAddress(address)) { From 0854192dbaf3e305fb450f29ee0bfda57356668a Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Tue, 31 Oct 2023 00:19:32 +0200 Subject: [PATCH 628/694] Review fixes --- packages/grpc-js/src/http_proxy.ts | 12 ++++++++---- packages/grpc-js/src/resolver-dns.ts | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index d1df22d2b..3e905c488 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -30,6 +30,7 @@ import { import { ChannelOptions } from './channel-options'; import { GrpcUri, parseUri, splitHostPort, uriToString } from './uri-parser'; import { URL } from 'url'; +import { DEFAULT_PORT } from './resolver-dns'; const TRACER_NAME = 'proxy'; @@ -189,16 +190,19 @@ export function getProxiedConnection( if (parsedTarget === null) { return Promise.resolve({}); } - const targetHostPost = splitHostPort(parsedTarget.path); - if (targetHostPost === null) { + const splitHostPost = splitHostPort(parsedTarget.path); + if (splitHostPost === null) { return Promise.resolve({}); } + const hostPort = `${splitHostPost.host}:${ + splitHostPost.port ?? DEFAULT_PORT + }`; const options: http.RequestOptions = { method: 'CONNECT', - path: targetHostPost.host + ':' + (targetHostPost.port != null ? targetHostPost.port : '443'), + path: hostPort, }; const headers: http.OutgoingHttpHeaders = { - Host: targetHostPost.host + ':' + (targetHostPost.port != null ? targetHostPost.port : '443'), + Host: hostPort, }; // Connect to the subchannel address as a proxy if (isTcpSubchannelAddress(address)) { diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 9431183d3..31e0d0bab 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -43,7 +43,7 @@ function trace(text: string): void { /** * The default TCP port to connect to if not explicitly specified in the target. */ -const DEFAULT_PORT = 443; +export const DEFAULT_PORT = 443; const DEFAULT_MIN_TIME_BETWEEN_RESOLUTIONS_MS = 30_000; From bf2009a72f7b071cb9b1605ca24646b01e9c2621 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 7 Nov 2023 11:09:59 -0800 Subject: [PATCH 629/694] grpc-js: Handle unset opaqueData in goaway event --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/transport.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 56fa0d3ff..be29ff870 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.9", + "version": "1.9.10", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 8dc4e2de6..39ca69383 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -194,17 +194,18 @@ class Http2Transport implements Transport { }); session.once( 'goaway', - (errorCode: number, lastStreamID: number, opaqueData: Buffer) => { + (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => { let tooManyPings = false; /* See the last paragraph of * https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md#basic-keepalive */ if ( errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM && + opaqueData && opaqueData.equals(tooManyPingsData) ) { tooManyPings = true; } - this.trace('connection closed by GOAWAY with code ' + errorCode); + this.trace('connection closed by GOAWAY with code ' + errorCode + ' and data ' + opaqueData?.toString()); this.reportDisconnectToOwner(tooManyPings); } ); From 54df17727f1e1fa40c2c8386c105196fe8731c81 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Thu, 2 Nov 2023 14:07:40 -0400 Subject: [PATCH 630/694] feat(grpc-reflection): created new grpc-reflection package ported from nestjs-grpc-reflection library --- README.md | 8 + gulpfile.ts | 5 +- packages/grpc-reflection/LICENSE | 201 ++++++++++++++ packages/grpc-reflection/README.md | 42 +++ packages/grpc-reflection/gulpfile.ts | 50 ++++ packages/grpc-reflection/images/example.gif | Bin 0 -> 1065106 bytes packages/grpc-reflection/package.json | 47 ++++ .../proto/grpc/reflection/v1/reflection.proto | 149 +++++++++++ .../grpc/reflection/v1alpha/reflection.proto | 139 ++++++++++ .../grpc-reflection/proto/sample/sample.proto | 39 +++ .../proto/sample/vendor/common.proto | 15 ++ .../sample/vendor/dependency/dependency.proto | 7 + .../proto/sample/vendor/vendor.proto | 10 + .../grpc/reflection/v1/ErrorResponse.ts | 24 ++ .../reflection/v1/ExtensionNumberResponse.ts | 28 ++ .../grpc/reflection/v1/ExtensionRequest.ts | 26 ++ .../reflection/v1/FileDescriptorResponse.ts | 30 +++ .../grpc/reflection/v1/ListServiceResponse.ts | 25 ++ .../grpc/reflection/v1/ServerReflection.ts | 9 + .../reflection/v1/ServerReflectionRequest.ts | 91 +++++++ .../reflection/v1/ServerReflectionResponse.ts | 75 ++++++ .../grpc/reflection/v1/ServiceResponse.ts | 26 ++ .../grpc/reflection/v1alpha/ErrorResponse.ts | 24 ++ .../v1alpha/ExtensionNumberResponse.ts | 28 ++ .../reflection/v1alpha/ExtensionRequest.ts | 26 ++ .../v1alpha/FileDescriptorResponse.ts | 30 +++ .../reflection/v1alpha/ListServiceResponse.ts | 25 ++ .../reflection/v1alpha/ServerReflection.ts | 9 + .../v1alpha/ServerReflectionRequest.ts | 91 +++++++ .../v1alpha/ServerReflectionResponse.ts | 75 ++++++ .../reflection/v1alpha/ServiceResponse.ts | 26 ++ .../grpc-reflection/src/protobuf-visitor.ts | 110 ++++++++ .../src/reflection-v1-implementation.ts | 252 ++++++++++++++++++ packages/grpc-reflection/src/utils.ts | 11 + .../test/test-reflection-v1-implementation.ts | 182 +++++++++++++ packages/grpc-reflection/test/test-utils.ts | 14 + packages/grpc-reflection/tsconfig.json | 29 ++ 37 files changed, 1976 insertions(+), 2 deletions(-) create mode 100644 packages/grpc-reflection/LICENSE create mode 100644 packages/grpc-reflection/README.md create mode 100644 packages/grpc-reflection/gulpfile.ts create mode 100644 packages/grpc-reflection/images/example.gif create mode 100644 packages/grpc-reflection/package.json create mode 100644 packages/grpc-reflection/proto/grpc/reflection/v1/reflection.proto create mode 100644 packages/grpc-reflection/proto/grpc/reflection/v1alpha/reflection.proto create mode 100644 packages/grpc-reflection/proto/sample/sample.proto create mode 100644 packages/grpc-reflection/proto/sample/vendor/common.proto create mode 100644 packages/grpc-reflection/proto/sample/vendor/dependency/dependency.proto create mode 100644 packages/grpc-reflection/proto/sample/vendor/vendor.proto create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ErrorResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionNumberResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionRequest.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/FileDescriptorResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ListServiceResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflection.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionRequest.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1/ServiceResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ErrorResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionNumberResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionRequest.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/FileDescriptorResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ListServiceResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflection.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionRequest.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionResponse.ts create mode 100644 packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServiceResponse.ts create mode 100644 packages/grpc-reflection/src/protobuf-visitor.ts create mode 100644 packages/grpc-reflection/src/reflection-v1-implementation.ts create mode 100644 packages/grpc-reflection/src/utils.ts create mode 100644 packages/grpc-reflection/test/test-reflection-v1-implementation.ts create mode 100644 packages/grpc-reflection/test/test-utils.ts create mode 100644 packages/grpc-reflection/tsconfig.json diff --git a/README.md b/README.md index f0f215ce3..de4cc752b 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,11 @@ Directory: [`packages/grpc-health-check`](https://github.com/grpc/grpc-node/tree npm package: [grpc-health-check](https://www.npmjs.com/package/grpc-health-check) Health check service for gRPC servers. + +### gRPC Reflection API Service + +Directory: [`packages/grpc-reflection`](https://github.com/grpc/grpc-node/tree/master/packages/grpc-reflection) + +npm package: [@grpc/reflection](https://www.npmjs.com/package/@grpc/reflection) + +Reflection API service for gRPC servers. diff --git a/gulpfile.ts b/gulpfile.ts index 7ac4e9a0b..3bdd6bb68 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -19,10 +19,11 @@ import * as gulp from 'gulp'; import * as healthCheck from './packages/grpc-health-check/gulpfile'; import * as jsCore from './packages/grpc-js/gulpfile'; import * as jsXds from './packages/grpc-js-xds/gulpfile'; +import * as reflection from './packages/grpc-reflection/gulpfile'; import * as protobuf from './packages/proto-loader/gulpfile'; import * as internalTest from './test/gulpfile'; -const installAll = gulp.series(jsCore.install, healthCheck.install, protobuf.install, internalTest.install, jsXds.install); +const installAll = gulp.series(jsCore.install, healthCheck.install, protobuf.install, internalTest.install, jsXds.install, reflection.install); const lint = gulp.parallel(jsCore.lint); @@ -36,7 +37,7 @@ const clean = gulp.series(jsCore.clean, protobuf.clean, jsXds.clean); const cleanAll = gulp.series(jsXds.cleanAll, jsCore.cleanAll, internalTest.cleanAll, protobuf.cleanAll); -const nativeTestOnly = gulp.parallel(healthCheck.test); +const nativeTestOnly = gulp.parallel(healthCheck.test, reflection.test); const nativeTest = gulp.series(build, nativeTestOnly); diff --git a/packages/grpc-reflection/LICENSE b/packages/grpc-reflection/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/packages/grpc-reflection/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/packages/grpc-reflection/README.md b/packages/grpc-reflection/README.md new file mode 100644 index 000000000..cb2501877 --- /dev/null +++ b/packages/grpc-reflection/README.md @@ -0,0 +1,42 @@ +# gRPC Reflection + +gRPC reflection API service for use with gRPC-node. + +## Background + +This package provides an implementation of the [gRPC Server Reflection Protocol](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md) service which can be added to an existing gRPC server. Adding this service to your server will allow clients [such as postman](https://blog.postman.com/postman-now-supports-grpc/) to dynamically load the API specification from your running application rather than needing to pass around and load proto files manually. + +![example of reflection working with postman](https://gitlab.com/jtimmons/nestjs-grpc-reflection-module/-/raw/master/images/example.gif) + +## Installation + +Use the package manager [npm](https://www.npmjs.com/get-npm) to install `@grpc/reflection`. + +```bash +npm install @grpc/reflection +``` + +## Usage + +Any gRPC-node server can use `@grpc/reflection` to expose reflection information about their gRPC API. + +```typescript +import { ReflectionServer } from '@grpc/reflection'; + +const pkg = protoLoader.load(...); // Load your gRPC package definition as normal + +// Create the reflection implementation based on your gRPC package and add it to your existing server +const reflection = new ReflectionServer(pkg); +reflection.addToServer(server); +``` + +Congrats! Your server now allows any client to request reflection information about its API. + +## Contributing + +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. The original proposal for this library can be found in [gRFC L108](https://github.com/grpc/proposal/blob/master/L108-node-grpc-reflection-library.md) + +Please make sure to update tests as appropriate. + +## License +[Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) diff --git a/packages/grpc-reflection/gulpfile.ts b/packages/grpc-reflection/gulpfile.ts new file mode 100644 index 000000000..f95b91bc0 --- /dev/null +++ b/packages/grpc-reflection/gulpfile.ts @@ -0,0 +1,50 @@ +/* + * 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 * as gulp from 'gulp'; +import * as mocha from 'gulp-mocha'; +import * as execa from 'execa'; +import * as path from 'path'; + +const reflectionDir = __dirname; +const outDir = path.resolve(reflectionDir, 'build'); + +const execNpmVerb = (verb: string, ...args: string[]) => + execa('npm', [verb, ...args], {cwd: reflectionDir, stdio: 'inherit'}); +const execNpmCommand = execNpmVerb.bind(null, 'run'); + +const install = () => execNpmVerb('install', '--unsafe-perm'); + +/** + * Transpiles TypeScript files in src/ to JavaScript according to the settings + * found in tsconfig.json. + */ +const compile = () => execNpmCommand('compile'); + +const runTests = () => { + return gulp.src(`${outDir}/test/**/*.js`) + .pipe(mocha({reporter: 'mocha-jenkins-reporter', + require: ['ts-node/register']})); +}; + +const test = gulp.series(install, runTests); + +export { + install, + compile, + test +} diff --git a/packages/grpc-reflection/images/example.gif b/packages/grpc-reflection/images/example.gif new file mode 100644 index 0000000000000000000000000000000000000000..65c92d78fa56659c55a20b3d53f741f1120d894a GIT binary patch literal 1065106 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

>H)?EyAcUXzy_cOVljs>sZc5t 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

Ix>gV)^of(i>Q~2l*0sKMu6Nz* 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ΜBq=c8sQ(?{Y{(z<#PcN3`5`rp1b>v;57(tU1_ub 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

=z%uR@0#u4(s@ouZSZ0Yj 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

T&+oh}4bzP8!T_3w$ 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�_iv2IE4+J|s+SD^yQxB;P0oKJ>=NBK6qM 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�v~QRzYM)G& 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 literal 0 HcmV?d00001 diff --git a/packages/grpc-reflection/package.json b/packages/grpc-reflection/package.json new file mode 100644 index 000000000..b413dddae --- /dev/null +++ b/packages/grpc-reflection/package.json @@ -0,0 +1,47 @@ +{ + "name": "@grpc/reflection", + "version": "1.0.0", + "author": "Justin Timmons", + "description": "Reflection API service for use with gRPC-node", + "repository": { + "type": "git", + "url": "https://github.com/grpc/grpc-node.git", + "directory": "packages/grpc-reflection" + }, + "bugs": "https://github.com/grpc/grpc-node/issues", + "contributors": [ + { + "name": "Justin Timmons", + "email": "justinmtimmons@gmail.com" + } + ], + "files": [ + "LICENSE", + "README.md", + "src", + "build", + "proto" + ], + "license": "Apache-2.0", + "scripts": { + "compile": "tsc -p .", + "postcompile": "copyfiles './proto/**/*.proto' build/", + "prepare": "npm run generate-types && npm run compile", + "test": "mocha --require ts-node/register test/**.ts", + "generate-types": "proto-loader-gen-types --longs String --enums String --bytes Array --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated grpc/reflection/v1/reflection.proto grpc/reflection/v1alpha/reflection.proto" + }, + "dependencies": { + "google-protobuf": "^3.21.2" + }, + "peerDependencies": { + "@grpc/grpc-js": ">=1.5.4", + "@grpc/proto-loader": ">=0.6.9" + }, + "devDependencies": { + "@grpc/grpc-js": "^1.8.21", + "@grpc/proto-loader": "^0.7.10", + "@types/google-protobuf": "^3.15.7", + "copyfiles": "^2.4.1", + "typescript": "^5.2.2" + } +} diff --git a/packages/grpc-reflection/proto/grpc/reflection/v1/reflection.proto b/packages/grpc-reflection/proto/grpc/reflection/v1/reflection.proto new file mode 100644 index 000000000..1c106af7f --- /dev/null +++ b/packages/grpc-reflection/proto/grpc/reflection/v1/reflection.proto @@ -0,0 +1,149 @@ +// Taken from spec https://raw.githubusercontent.com/grpc/grpc/master/src/proto/grpc/reflection/v1/reflection.proto +// Additional versions can be found here: https://github.com/grpc/grpc/tree/master/src/proto/grpc/reflection + +// Copyright 2016 The 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. + +// Service exported by server reflection. A more complete description of how +// server reflection works can be found at +// https://github.com/grpc/grpc/blob/master/doc/server-reflection.md +// +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/reflection/v1/reflection.proto + +syntax = "proto3"; + +package grpc.reflection.v1; + +option go_package = "google.golang.org/grpc/reflection/grpc_reflection_v1"; +option java_multiple_files = true; +option java_package = "io.grpc.reflection.v1"; +option java_outer_classname = "ServerReflectionProto"; + +service ServerReflection { + // The reflection service is structured as a bidirectional stream, ensuring + // all related requests go to a single server. + rpc ServerReflectionInfo(stream ServerReflectionRequest) + returns (stream ServerReflectionResponse); +} + +// The message sent by the client when calling ServerReflectionInfo method. +message ServerReflectionRequest { + string host = 1; + // To use reflection service, the client should set one of the following + // fields in message_request. The server distinguishes requests by their + // defined field and then handles them using corresponding methods. + oneof message_request { + // Find a proto file by the file name. + string file_by_filename = 3; + + // Find the proto file that declares the given fully-qualified symbol name. + // This field should be a fully-qualified symbol name + // (e.g. .[.] or .). + string file_containing_symbol = 4; + + // Find the proto file which defines an extension extending the given + // message type with the given field number. + ExtensionRequest file_containing_extension = 5; + + // Finds the tag numbers used by all known extensions of the given message + // type, and appends them to ExtensionNumberResponse in an undefined order. + // Its corresponding method is best-effort: it's not guaranteed that the + // reflection service will implement this method, and it's not guaranteed + // that this method will provide all extensions. Returns + // StatusCode::UNIMPLEMENTED if it's not implemented. + // This field should be a fully-qualified type name. The format is + // . + string all_extension_numbers_of_type = 6; + + // List the full names of registered services. The content will not be + // checked. + string list_services = 7; + } +} + +// The type name and extension number sent by the client when requesting +// file_containing_extension. +message ExtensionRequest { + // Fully-qualified type name. The format should be . + string containing_type = 1; + int32 extension_number = 2; +} + +// The message sent by the server to answer ServerReflectionInfo method. +message ServerReflectionResponse { + string valid_host = 1; + ServerReflectionRequest original_request = 2; + // The server sets one of the following fields according to the message_request + // in the request. + oneof message_response { + // This message is used to answer file_by_filename, file_containing_symbol, + // file_containing_extension requests with transitive dependencies. + // As the repeated label is not allowed in oneof fields, we use a + // FileDescriptorResponse message to encapsulate the repeated fields. + // The reflection service is allowed to avoid sending FileDescriptorProtos + // that were previously sent in response to earlier requests in the stream. + FileDescriptorResponse file_descriptor_response = 4; + + // This message is used to answer all_extension_numbers_of_type requests. + ExtensionNumberResponse all_extension_numbers_response = 5; + + // This message is used to answer list_services requests. + ListServiceResponse list_services_response = 6; + + // This message is used when an error occurs. + ErrorResponse error_response = 7; + } +} + +// Serialized FileDescriptorProto messages sent by the server answering +// a file_by_filename, file_containing_symbol, or file_containing_extension +// request. +message FileDescriptorResponse { + // Serialized FileDescriptorProto messages. We avoid taking a dependency on + // descriptor.proto, which uses proto2 only features, by making them opaque + // bytes instead. + repeated bytes file_descriptor_proto = 1; +} + +// A list of extension numbers sent by the server answering +// all_extension_numbers_of_type request. +message ExtensionNumberResponse { + // Full name of the base type, including the package name. The format + // is . + string base_type_name = 1; + repeated int32 extension_number = 2; +} + +// A list of ServiceResponse sent by the server answering list_services request. +message ListServiceResponse { + // The information of each service may be expanded in the future, so we use + // ServiceResponse message to encapsulate it. + repeated ServiceResponse service = 1; +} + +// The information of a single service used by ListServiceResponse to answer +// list_services request. +message ServiceResponse { + // Full name of a registered service, including its package name. The format + // is . + string name = 1; +} + +// The error code and error message sent by the server when an error occurs. +message ErrorResponse { + // This field uses the error codes defined in grpc::StatusCode. + int32 error_code = 1; + string error_message = 2; +} diff --git a/packages/grpc-reflection/proto/grpc/reflection/v1alpha/reflection.proto b/packages/grpc-reflection/proto/grpc/reflection/v1alpha/reflection.proto new file mode 100644 index 000000000..781659af2 --- /dev/null +++ b/packages/grpc-reflection/proto/grpc/reflection/v1alpha/reflection.proto @@ -0,0 +1,139 @@ +// Taken from spec https://raw.githubusercontent.com/grpc/grpc/master/src/proto/grpc/reflection/v1alpha/reflection.proto +// Additional versions can be found here: https://github.com/grpc/grpc/tree/master/src/proto/grpc/reflection + +// Copyright 2016 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. + +// Service exported by server reflection + +syntax = "proto3"; + +package grpc.reflection.v1alpha; + +service ServerReflection { + // The reflection service is structured as a bidirectional stream, ensuring + // all related requests go to a single server. + rpc ServerReflectionInfo(stream ServerReflectionRequest) + returns (stream ServerReflectionResponse); +} + +// The message sent by the client when calling ServerReflectionInfo method. +message ServerReflectionRequest { + string host = 1; + // To use reflection service, the client should set one of the following + // fields in message_request. The server distinguishes requests by their + // defined field and then handles them using corresponding methods. + oneof message_request { + // Find a proto file by the file name. + string file_by_filename = 3; + + // Find the proto file that declares the given fully-qualified symbol name. + // This field should be a fully-qualified symbol name + // (e.g. .[.] or .). + string file_containing_symbol = 4; + + // Find the proto file which defines an extension extending the given + // message type with the given field number. + ExtensionRequest file_containing_extension = 5; + + // Finds the tag numbers used by all known extensions of the given message + // type, and appends them to ExtensionNumberResponse in an undefined order. + // Its corresponding method is best-effort: it's not guaranteed that the + // reflection service will implement this method, and it's not guaranteed + // that this method will provide all extensions. Returns + // StatusCode::UNIMPLEMENTED if it's not implemented. + // This field should be a fully-qualified type name. The format is + // . + string all_extension_numbers_of_type = 6; + + // List the full names of registered services. The content will not be + // checked. + string list_services = 7; + } +} + +// The type name and extension number sent by the client when requesting +// file_containing_extension. +message ExtensionRequest { + // Fully-qualified type name. The format should be . + string containing_type = 1; + int32 extension_number = 2; +} + +// The message sent by the server to answer ServerReflectionInfo method. +message ServerReflectionResponse { + string valid_host = 1; + ServerReflectionRequest original_request = 2; + // The server set one of the following fields accroding to the message_request + // in the request. + oneof message_response { + // This message is used to answer file_by_filename, file_containing_symbol, + // file_containing_extension requests with transitive dependencies. As + // the repeated label is not allowed in oneof fields, we use a + // FileDescriptorResponse message to encapsulate the repeated fields. + // The reflection service is allowed to avoid sending FileDescriptorProtos + // that were previously sent in response to earlier requests in the stream. + FileDescriptorResponse file_descriptor_response = 4; + + // This message is used to answer all_extension_numbers_of_type requst. + ExtensionNumberResponse all_extension_numbers_response = 5; + + // This message is used to answer list_services request. + ListServiceResponse list_services_response = 6; + + // This message is used when an error occurs. + ErrorResponse error_response = 7; + } +} + +// Serialized FileDescriptorProto messages sent by the server answering +// a file_by_filename, file_containing_symbol, or file_containing_extension +// request. +message FileDescriptorResponse { + // Serialized FileDescriptorProto messages. We avoid taking a dependency on + // descriptor.proto, which uses proto2 only features, by making them opaque + // bytes instead. + repeated bytes file_descriptor_proto = 1; +} + +// A list of extension numbers sent by the server answering +// all_extension_numbers_of_type request. +message ExtensionNumberResponse { + // Full name of the base type, including the package name. The format + // is . + string base_type_name = 1; + repeated int32 extension_number = 2; +} + +// A list of ServiceResponse sent by the server answering list_services request. +message ListServiceResponse { + // The information of each service may be expanded in the future, so we use + // ServiceResponse message to encapsulate it. + repeated ServiceResponse service = 1; +} + +// The information of a single service used by ListServiceResponse to answer +// list_services request. +message ServiceResponse { + // Full name of a registered service, including its package name. The format + // is . + string name = 1; +} + +// The error code and error message sent by the server when an error occurs. +message ErrorResponse { + // This field uses the error codes defined in grpc::StatusCode. + int32 error_code = 1; + string error_message = 2; +} diff --git a/packages/grpc-reflection/proto/sample/sample.proto b/packages/grpc-reflection/proto/sample/sample.proto new file mode 100644 index 000000000..b5b532087 --- /dev/null +++ b/packages/grpc-reflection/proto/sample/sample.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package sample; + +import 'vendor.proto'; + +service SampleService { + rpc Hello (HelloRequest) returns (HelloResponse) {} + rpc Hello2 (HelloRequest) returns (CommonMessage) {} +} + +message HelloRequest { + string hello = 1; + HelloNested nested = 2; + ShadowedMessage nestedShadowedMessage = 3; + + message HelloNested { + string hello = 1; + CommonMessage field = 2; + } + + message ShadowedMessage { + int32 item = 1; + } +} + +enum HelloStatus { + HELLO = 1; + WORLD = 2; +} + +message HelloResponse { + string world = 1; + HelloStatus status = 2; +} + +message ShadowedMessage { + string hello = 1; +} diff --git a/packages/grpc-reflection/proto/sample/vendor/common.proto b/packages/grpc-reflection/proto/sample/vendor/common.proto new file mode 100644 index 000000000..c89246b5d --- /dev/null +++ b/packages/grpc-reflection/proto/sample/vendor/common.proto @@ -0,0 +1,15 @@ +syntax = "proto2"; + +// NOTE: intentionally using the same 'vendor' package here to document the +// file/package merging behavior of the reflection service. +// +// this file should be combined with vendor.proto to a single definition because +// it's under the same 'vendor' package +package vendor; + +message CommonMessage { + optional string common = 1; + optional DependentMessage dependency = 2; + + extensions 100 to 199; +} diff --git a/packages/grpc-reflection/proto/sample/vendor/dependency/dependency.proto b/packages/grpc-reflection/proto/sample/vendor/dependency/dependency.proto new file mode 100644 index 000000000..44cadfba1 --- /dev/null +++ b/packages/grpc-reflection/proto/sample/vendor/dependency/dependency.proto @@ -0,0 +1,7 @@ +syntax = "proto2"; + +package vendor.dependency; + +message DependentMessage { + optional string something = 1; +} diff --git a/packages/grpc-reflection/proto/sample/vendor/vendor.proto b/packages/grpc-reflection/proto/sample/vendor/vendor.proto new file mode 100644 index 000000000..f8e0c1e5a --- /dev/null +++ b/packages/grpc-reflection/proto/sample/vendor/vendor.proto @@ -0,0 +1,10 @@ +syntax = "proto2"; + +package vendor; + +import "./common.proto"; +import "./dependency/dependency.proto"; + +extend CommonMessage { + optional bool ext = 101; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ErrorResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ErrorResponse.ts new file mode 100644 index 000000000..e8168c36d --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ErrorResponse.ts @@ -0,0 +1,24 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + + +/** + * The error code and error message sent by the server when an error occurs. + */ +export interface ErrorResponse { + /** + * This field uses the error codes defined in grpc::StatusCode. + */ + 'errorCode'?: (number); + 'errorMessage'?: (string); +} + +/** + * The error code and error message sent by the server when an error occurs. + */ +export interface ErrorResponse__Output { + /** + * This field uses the error codes defined in grpc::StatusCode. + */ + 'errorCode': (number); + 'errorMessage': (string); +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionNumberResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionNumberResponse.ts new file mode 100644 index 000000000..fdb88119c --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionNumberResponse.ts @@ -0,0 +1,28 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + + +/** + * A list of extension numbers sent by the server answering + * all_extension_numbers_of_type request. + */ +export interface ExtensionNumberResponse { + /** + * Full name of the base type, including the package name. The format + * is . + */ + 'baseTypeName'?: (string); + 'extensionNumber'?: (number)[]; +} + +/** + * A list of extension numbers sent by the server answering + * all_extension_numbers_of_type request. + */ +export interface ExtensionNumberResponse__Output { + /** + * Full name of the base type, including the package name. The format + * is . + */ + 'baseTypeName': (string); + 'extensionNumber': (number)[]; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionRequest.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionRequest.ts new file mode 100644 index 000000000..34c6fefeb --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ExtensionRequest.ts @@ -0,0 +1,26 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + + +/** + * The type name and extension number sent by the client when requesting + * file_containing_extension. + */ +export interface ExtensionRequest { + /** + * Fully-qualified type name. The format should be . + */ + 'containingType'?: (string); + 'extensionNumber'?: (number); +} + +/** + * The type name and extension number sent by the client when requesting + * file_containing_extension. + */ +export interface ExtensionRequest__Output { + /** + * Fully-qualified type name. The format should be . + */ + 'containingType': (string); + 'extensionNumber': (number); +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/FileDescriptorResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/FileDescriptorResponse.ts new file mode 100644 index 000000000..253e650f9 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/FileDescriptorResponse.ts @@ -0,0 +1,30 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + + +/** + * Serialized FileDescriptorProto messages sent by the server answering + * a file_by_filename, file_containing_symbol, or file_containing_extension + * request. + */ +export interface FileDescriptorResponse { + /** + * Serialized FileDescriptorProto messages. We avoid taking a dependency on + * descriptor.proto, which uses proto2 only features, by making them opaque + * bytes instead. + */ + 'fileDescriptorProto'?: (Buffer | Uint8Array | string)[]; +} + +/** + * Serialized FileDescriptorProto messages sent by the server answering + * a file_by_filename, file_containing_symbol, or file_containing_extension + * request. + */ +export interface FileDescriptorResponse__Output { + /** + * Serialized FileDescriptorProto messages. We avoid taking a dependency on + * descriptor.proto, which uses proto2 only features, by making them opaque + * bytes instead. + */ + 'fileDescriptorProto': (Uint8Array)[]; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ListServiceResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ListServiceResponse.ts new file mode 100644 index 000000000..f1824d4cf --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ListServiceResponse.ts @@ -0,0 +1,25 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + +import type { ServiceResponse as _grpc_reflection_v1_ServiceResponse, ServiceResponse__Output as _grpc_reflection_v1_ServiceResponse__Output } from '../../../grpc/reflection/v1/ServiceResponse'; + +/** + * A list of ServiceResponse sent by the server answering list_services request. + */ +export interface ListServiceResponse { + /** + * The information of each service may be expanded in the future, so we use + * ServiceResponse message to encapsulate it. + */ + 'service'?: (_grpc_reflection_v1_ServiceResponse)[]; +} + +/** + * A list of ServiceResponse sent by the server answering list_services request. + */ +export interface ListServiceResponse__Output { + /** + * The information of each service may be expanded in the future, so we use + * ServiceResponse message to encapsulate it. + */ + 'service': (_grpc_reflection_v1_ServiceResponse__Output)[]; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflection.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflection.ts new file mode 100644 index 000000000..65d3b571b --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflection.ts @@ -0,0 +1,9 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + +import type { MethodDefinition } from '@grpc/proto-loader' +import type { ServerReflectionRequest as _grpc_reflection_v1_ServerReflectionRequest, ServerReflectionRequest__Output as _grpc_reflection_v1_ServerReflectionRequest__Output } from '../../../grpc/reflection/v1/ServerReflectionRequest'; +import type { ServerReflectionResponse as _grpc_reflection_v1_ServerReflectionResponse, ServerReflectionResponse__Output as _grpc_reflection_v1_ServerReflectionResponse__Output } from '../../../grpc/reflection/v1/ServerReflectionResponse'; + +export interface ServerReflectionDefinition { + ServerReflectionInfo: MethodDefinition<_grpc_reflection_v1_ServerReflectionRequest, _grpc_reflection_v1_ServerReflectionResponse, _grpc_reflection_v1_ServerReflectionRequest__Output, _grpc_reflection_v1_ServerReflectionResponse__Output> +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionRequest.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionRequest.ts new file mode 100644 index 000000000..301bd3953 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionRequest.ts @@ -0,0 +1,91 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + +import type { ExtensionRequest as _grpc_reflection_v1_ExtensionRequest, ExtensionRequest__Output as _grpc_reflection_v1_ExtensionRequest__Output } from '../../../grpc/reflection/v1/ExtensionRequest'; + +/** + * The message sent by the client when calling ServerReflectionInfo method. + */ +export interface ServerReflectionRequest { + 'host'?: (string); + /** + * Find a proto file by the file name. + */ + 'fileByFilename'?: (string); + /** + * Find the proto file that declares the given fully-qualified symbol name. + * This field should be a fully-qualified symbol name + * (e.g. .[.] or .). + */ + 'fileContainingSymbol'?: (string); + /** + * Find the proto file which defines an extension extending the given + * message type with the given field number. + */ + 'fileContainingExtension'?: (_grpc_reflection_v1_ExtensionRequest | null); + /** + * Finds the tag numbers used by all known extensions of the given message + * type, and appends them to ExtensionNumberResponse in an undefined order. + * Its corresponding method is best-effort: it's not guaranteed that the + * reflection service will implement this method, and it's not guaranteed + * that this method will provide all extensions. Returns + * StatusCode::UNIMPLEMENTED if it's not implemented. + * This field should be a fully-qualified type name. The format is + * . + */ + 'allExtensionNumbersOfType'?: (string); + /** + * List the full names of registered services. The content will not be + * checked. + */ + 'listServices'?: (string); + /** + * To use reflection service, the client should set one of the following + * fields in message_request. The server distinguishes requests by their + * defined field and then handles them using corresponding methods. + */ + 'messageRequest'?: "fileByFilename"|"fileContainingSymbol"|"fileContainingExtension"|"allExtensionNumbersOfType"|"listServices"; +} + +/** + * The message sent by the client when calling ServerReflectionInfo method. + */ +export interface ServerReflectionRequest__Output { + 'host': (string); + /** + * Find a proto file by the file name. + */ + 'fileByFilename'?: (string); + /** + * Find the proto file that declares the given fully-qualified symbol name. + * This field should be a fully-qualified symbol name + * (e.g. .[.] or .). + */ + 'fileContainingSymbol'?: (string); + /** + * Find the proto file which defines an extension extending the given + * message type with the given field number. + */ + 'fileContainingExtension'?: (_grpc_reflection_v1_ExtensionRequest__Output | null); + /** + * Finds the tag numbers used by all known extensions of the given message + * type, and appends them to ExtensionNumberResponse in an undefined order. + * Its corresponding method is best-effort: it's not guaranteed that the + * reflection service will implement this method, and it's not guaranteed + * that this method will provide all extensions. Returns + * StatusCode::UNIMPLEMENTED if it's not implemented. + * This field should be a fully-qualified type name. The format is + * . + */ + 'allExtensionNumbersOfType'?: (string); + /** + * List the full names of registered services. The content will not be + * checked. + */ + 'listServices'?: (string); + /** + * To use reflection service, the client should set one of the following + * fields in message_request. The server distinguishes requests by their + * defined field and then handles them using corresponding methods. + */ + 'messageRequest': "fileByFilename"|"fileContainingSymbol"|"fileContainingExtension"|"allExtensionNumbersOfType"|"listServices"; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionResponse.ts new file mode 100644 index 000000000..bc2790c15 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServerReflectionResponse.ts @@ -0,0 +1,75 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + +import type { ServerReflectionRequest as _grpc_reflection_v1_ServerReflectionRequest, ServerReflectionRequest__Output as _grpc_reflection_v1_ServerReflectionRequest__Output } from '../../../grpc/reflection/v1/ServerReflectionRequest'; +import type { FileDescriptorResponse as _grpc_reflection_v1_FileDescriptorResponse, FileDescriptorResponse__Output as _grpc_reflection_v1_FileDescriptorResponse__Output } from '../../../grpc/reflection/v1/FileDescriptorResponse'; +import type { ExtensionNumberResponse as _grpc_reflection_v1_ExtensionNumberResponse, ExtensionNumberResponse__Output as _grpc_reflection_v1_ExtensionNumberResponse__Output } from '../../../grpc/reflection/v1/ExtensionNumberResponse'; +import type { ListServiceResponse as _grpc_reflection_v1_ListServiceResponse, ListServiceResponse__Output as _grpc_reflection_v1_ListServiceResponse__Output } from '../../../grpc/reflection/v1/ListServiceResponse'; +import type { ErrorResponse as _grpc_reflection_v1_ErrorResponse, ErrorResponse__Output as _grpc_reflection_v1_ErrorResponse__Output } from '../../../grpc/reflection/v1/ErrorResponse'; + +/** + * The message sent by the server to answer ServerReflectionInfo method. + */ +export interface ServerReflectionResponse { + 'validHost'?: (string); + 'originalRequest'?: (_grpc_reflection_v1_ServerReflectionRequest | null); + /** + * This message is used to answer file_by_filename, file_containing_symbol, + * file_containing_extension requests with transitive dependencies. + * As the repeated label is not allowed in oneof fields, we use a + * FileDescriptorResponse message to encapsulate the repeated fields. + * The reflection service is allowed to avoid sending FileDescriptorProtos + * that were previously sent in response to earlier requests in the stream. + */ + 'fileDescriptorResponse'?: (_grpc_reflection_v1_FileDescriptorResponse | null); + /** + * This message is used to answer all_extension_numbers_of_type requests. + */ + 'allExtensionNumbersResponse'?: (_grpc_reflection_v1_ExtensionNumberResponse | null); + /** + * This message is used to answer list_services requests. + */ + 'listServicesResponse'?: (_grpc_reflection_v1_ListServiceResponse | null); + /** + * This message is used when an error occurs. + */ + 'errorResponse'?: (_grpc_reflection_v1_ErrorResponse | null); + /** + * The server sets one of the following fields according to the message_request + * in the request. + */ + 'messageResponse'?: "fileDescriptorResponse"|"allExtensionNumbersResponse"|"listServicesResponse"|"errorResponse"; +} + +/** + * The message sent by the server to answer ServerReflectionInfo method. + */ +export interface ServerReflectionResponse__Output { + 'validHost': (string); + 'originalRequest': (_grpc_reflection_v1_ServerReflectionRequest__Output | null); + /** + * This message is used to answer file_by_filename, file_containing_symbol, + * file_containing_extension requests with transitive dependencies. + * As the repeated label is not allowed in oneof fields, we use a + * FileDescriptorResponse message to encapsulate the repeated fields. + * The reflection service is allowed to avoid sending FileDescriptorProtos + * that were previously sent in response to earlier requests in the stream. + */ + 'fileDescriptorResponse'?: (_grpc_reflection_v1_FileDescriptorResponse__Output | null); + /** + * This message is used to answer all_extension_numbers_of_type requests. + */ + 'allExtensionNumbersResponse'?: (_grpc_reflection_v1_ExtensionNumberResponse__Output | null); + /** + * This message is used to answer list_services requests. + */ + 'listServicesResponse'?: (_grpc_reflection_v1_ListServiceResponse__Output | null); + /** + * This message is used when an error occurs. + */ + 'errorResponse'?: (_grpc_reflection_v1_ErrorResponse__Output | null); + /** + * The server sets one of the following fields according to the message_request + * in the request. + */ + 'messageResponse': "fileDescriptorResponse"|"allExtensionNumbersResponse"|"listServicesResponse"|"errorResponse"; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServiceResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServiceResponse.ts new file mode 100644 index 000000000..d529538e2 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1/ServiceResponse.ts @@ -0,0 +1,26 @@ +// Original file: proto/grpc/reflection/v1/reflection.proto + + +/** + * The information of a single service used by ListServiceResponse to answer + * list_services request. + */ +export interface ServiceResponse { + /** + * Full name of a registered service, including its package name. The format + * is . + */ + 'name'?: (string); +} + +/** + * The information of a single service used by ListServiceResponse to answer + * list_services request. + */ +export interface ServiceResponse__Output { + /** + * Full name of a registered service, including its package name. The format + * is . + */ + 'name': (string); +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ErrorResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ErrorResponse.ts new file mode 100644 index 000000000..dc6c3a2e2 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ErrorResponse.ts @@ -0,0 +1,24 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + + +/** + * The error code and error message sent by the server when an error occurs. + */ +export interface ErrorResponse { + /** + * This field uses the error codes defined in grpc::StatusCode. + */ + 'errorCode'?: (number); + 'errorMessage'?: (string); +} + +/** + * The error code and error message sent by the server when an error occurs. + */ +export interface ErrorResponse__Output { + /** + * This field uses the error codes defined in grpc::StatusCode. + */ + 'errorCode': (number); + 'errorMessage': (string); +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionNumberResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionNumberResponse.ts new file mode 100644 index 000000000..b6c322c4e --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionNumberResponse.ts @@ -0,0 +1,28 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + + +/** + * A list of extension numbers sent by the server answering + * all_extension_numbers_of_type request. + */ +export interface ExtensionNumberResponse { + /** + * Full name of the base type, including the package name. The format + * is . + */ + 'baseTypeName'?: (string); + 'extensionNumber'?: (number)[]; +} + +/** + * A list of extension numbers sent by the server answering + * all_extension_numbers_of_type request. + */ +export interface ExtensionNumberResponse__Output { + /** + * Full name of the base type, including the package name. The format + * is . + */ + 'baseTypeName': (string); + 'extensionNumber': (number)[]; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionRequest.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionRequest.ts new file mode 100644 index 000000000..4a378b35a --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ExtensionRequest.ts @@ -0,0 +1,26 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + + +/** + * The type name and extension number sent by the client when requesting + * file_containing_extension. + */ +export interface ExtensionRequest { + /** + * Fully-qualified type name. The format should be . + */ + 'containingType'?: (string); + 'extensionNumber'?: (number); +} + +/** + * The type name and extension number sent by the client when requesting + * file_containing_extension. + */ +export interface ExtensionRequest__Output { + /** + * Fully-qualified type name. The format should be . + */ + 'containingType': (string); + 'extensionNumber': (number); +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/FileDescriptorResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/FileDescriptorResponse.ts new file mode 100644 index 000000000..cb1cc38c4 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/FileDescriptorResponse.ts @@ -0,0 +1,30 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + + +/** + * Serialized FileDescriptorProto messages sent by the server answering + * a file_by_filename, file_containing_symbol, or file_containing_extension + * request. + */ +export interface FileDescriptorResponse { + /** + * Serialized FileDescriptorProto messages. We avoid taking a dependency on + * descriptor.proto, which uses proto2 only features, by making them opaque + * bytes instead. + */ + 'fileDescriptorProto'?: (Buffer | Uint8Array | string)[]; +} + +/** + * Serialized FileDescriptorProto messages sent by the server answering + * a file_by_filename, file_containing_symbol, or file_containing_extension + * request. + */ +export interface FileDescriptorResponse__Output { + /** + * Serialized FileDescriptorProto messages. We avoid taking a dependency on + * descriptor.proto, which uses proto2 only features, by making them opaque + * bytes instead. + */ + 'fileDescriptorProto': (Uint8Array)[]; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ListServiceResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ListServiceResponse.ts new file mode 100644 index 000000000..7793a16eb --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ListServiceResponse.ts @@ -0,0 +1,25 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + +import type { ServiceResponse as _grpc_reflection_v1alpha_ServiceResponse, ServiceResponse__Output as _grpc_reflection_v1alpha_ServiceResponse__Output } from '../../../grpc/reflection/v1alpha/ServiceResponse'; + +/** + * A list of ServiceResponse sent by the server answering list_services request. + */ +export interface ListServiceResponse { + /** + * The information of each service may be expanded in the future, so we use + * ServiceResponse message to encapsulate it. + */ + 'service'?: (_grpc_reflection_v1alpha_ServiceResponse)[]; +} + +/** + * A list of ServiceResponse sent by the server answering list_services request. + */ +export interface ListServiceResponse__Output { + /** + * The information of each service may be expanded in the future, so we use + * ServiceResponse message to encapsulate it. + */ + 'service': (_grpc_reflection_v1alpha_ServiceResponse__Output)[]; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflection.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflection.ts new file mode 100644 index 000000000..2ab03e93c --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflection.ts @@ -0,0 +1,9 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + +import type { MethodDefinition } from '@grpc/proto-loader' +import type { ServerReflectionRequest as _grpc_reflection_v1alpha_ServerReflectionRequest, ServerReflectionRequest__Output as _grpc_reflection_v1alpha_ServerReflectionRequest__Output } from '../../../grpc/reflection/v1alpha/ServerReflectionRequest'; +import type { ServerReflectionResponse as _grpc_reflection_v1alpha_ServerReflectionResponse, ServerReflectionResponse__Output as _grpc_reflection_v1alpha_ServerReflectionResponse__Output } from '../../../grpc/reflection/v1alpha/ServerReflectionResponse'; + +export interface ServerReflectionDefinition { + ServerReflectionInfo: MethodDefinition<_grpc_reflection_v1alpha_ServerReflectionRequest, _grpc_reflection_v1alpha_ServerReflectionResponse, _grpc_reflection_v1alpha_ServerReflectionRequest__Output, _grpc_reflection_v1alpha_ServerReflectionResponse__Output> +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionRequest.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionRequest.ts new file mode 100644 index 000000000..097d848d0 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionRequest.ts @@ -0,0 +1,91 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + +import type { ExtensionRequest as _grpc_reflection_v1alpha_ExtensionRequest, ExtensionRequest__Output as _grpc_reflection_v1alpha_ExtensionRequest__Output } from '../../../grpc/reflection/v1alpha/ExtensionRequest'; + +/** + * The message sent by the client when calling ServerReflectionInfo method. + */ +export interface ServerReflectionRequest { + 'host'?: (string); + /** + * Find a proto file by the file name. + */ + 'fileByFilename'?: (string); + /** + * Find the proto file that declares the given fully-qualified symbol name. + * This field should be a fully-qualified symbol name + * (e.g. .[.] or .). + */ + 'fileContainingSymbol'?: (string); + /** + * Find the proto file which defines an extension extending the given + * message type with the given field number. + */ + 'fileContainingExtension'?: (_grpc_reflection_v1alpha_ExtensionRequest | null); + /** + * Finds the tag numbers used by all known extensions of the given message + * type, and appends them to ExtensionNumberResponse in an undefined order. + * Its corresponding method is best-effort: it's not guaranteed that the + * reflection service will implement this method, and it's not guaranteed + * that this method will provide all extensions. Returns + * StatusCode::UNIMPLEMENTED if it's not implemented. + * This field should be a fully-qualified type name. The format is + * . + */ + 'allExtensionNumbersOfType'?: (string); + /** + * List the full names of registered services. The content will not be + * checked. + */ + 'listServices'?: (string); + /** + * To use reflection service, the client should set one of the following + * fields in message_request. The server distinguishes requests by their + * defined field and then handles them using corresponding methods. + */ + 'messageRequest'?: "fileByFilename"|"fileContainingSymbol"|"fileContainingExtension"|"allExtensionNumbersOfType"|"listServices"; +} + +/** + * The message sent by the client when calling ServerReflectionInfo method. + */ +export interface ServerReflectionRequest__Output { + 'host': (string); + /** + * Find a proto file by the file name. + */ + 'fileByFilename'?: (string); + /** + * Find the proto file that declares the given fully-qualified symbol name. + * This field should be a fully-qualified symbol name + * (e.g. .[.] or .). + */ + 'fileContainingSymbol'?: (string); + /** + * Find the proto file which defines an extension extending the given + * message type with the given field number. + */ + 'fileContainingExtension'?: (_grpc_reflection_v1alpha_ExtensionRequest__Output | null); + /** + * Finds the tag numbers used by all known extensions of the given message + * type, and appends them to ExtensionNumberResponse in an undefined order. + * Its corresponding method is best-effort: it's not guaranteed that the + * reflection service will implement this method, and it's not guaranteed + * that this method will provide all extensions. Returns + * StatusCode::UNIMPLEMENTED if it's not implemented. + * This field should be a fully-qualified type name. The format is + * . + */ + 'allExtensionNumbersOfType'?: (string); + /** + * List the full names of registered services. The content will not be + * checked. + */ + 'listServices'?: (string); + /** + * To use reflection service, the client should set one of the following + * fields in message_request. The server distinguishes requests by their + * defined field and then handles them using corresponding methods. + */ + 'messageRequest': "fileByFilename"|"fileContainingSymbol"|"fileContainingExtension"|"allExtensionNumbersOfType"|"listServices"; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionResponse.ts new file mode 100644 index 000000000..eb81a0c2a --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServerReflectionResponse.ts @@ -0,0 +1,75 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + +import type { ServerReflectionRequest as _grpc_reflection_v1alpha_ServerReflectionRequest, ServerReflectionRequest__Output as _grpc_reflection_v1alpha_ServerReflectionRequest__Output } from '../../../grpc/reflection/v1alpha/ServerReflectionRequest'; +import type { FileDescriptorResponse as _grpc_reflection_v1alpha_FileDescriptorResponse, FileDescriptorResponse__Output as _grpc_reflection_v1alpha_FileDescriptorResponse__Output } from '../../../grpc/reflection/v1alpha/FileDescriptorResponse'; +import type { ExtensionNumberResponse as _grpc_reflection_v1alpha_ExtensionNumberResponse, ExtensionNumberResponse__Output as _grpc_reflection_v1alpha_ExtensionNumberResponse__Output } from '../../../grpc/reflection/v1alpha/ExtensionNumberResponse'; +import type { ListServiceResponse as _grpc_reflection_v1alpha_ListServiceResponse, ListServiceResponse__Output as _grpc_reflection_v1alpha_ListServiceResponse__Output } from '../../../grpc/reflection/v1alpha/ListServiceResponse'; +import type { ErrorResponse as _grpc_reflection_v1alpha_ErrorResponse, ErrorResponse__Output as _grpc_reflection_v1alpha_ErrorResponse__Output } from '../../../grpc/reflection/v1alpha/ErrorResponse'; + +/** + * The message sent by the server to answer ServerReflectionInfo method. + */ +export interface ServerReflectionResponse { + 'validHost'?: (string); + 'originalRequest'?: (_grpc_reflection_v1alpha_ServerReflectionRequest | null); + /** + * This message is used to answer file_by_filename, file_containing_symbol, + * file_containing_extension requests with transitive dependencies. As + * the repeated label is not allowed in oneof fields, we use a + * FileDescriptorResponse message to encapsulate the repeated fields. + * The reflection service is allowed to avoid sending FileDescriptorProtos + * that were previously sent in response to earlier requests in the stream. + */ + 'fileDescriptorResponse'?: (_grpc_reflection_v1alpha_FileDescriptorResponse | null); + /** + * This message is used to answer all_extension_numbers_of_type requst. + */ + 'allExtensionNumbersResponse'?: (_grpc_reflection_v1alpha_ExtensionNumberResponse | null); + /** + * This message is used to answer list_services request. + */ + 'listServicesResponse'?: (_grpc_reflection_v1alpha_ListServiceResponse | null); + /** + * This message is used when an error occurs. + */ + 'errorResponse'?: (_grpc_reflection_v1alpha_ErrorResponse | null); + /** + * The server set one of the following fields accroding to the message_request + * in the request. + */ + 'messageResponse'?: "fileDescriptorResponse"|"allExtensionNumbersResponse"|"listServicesResponse"|"errorResponse"; +} + +/** + * The message sent by the server to answer ServerReflectionInfo method. + */ +export interface ServerReflectionResponse__Output { + 'validHost': (string); + 'originalRequest': (_grpc_reflection_v1alpha_ServerReflectionRequest__Output | null); + /** + * This message is used to answer file_by_filename, file_containing_symbol, + * file_containing_extension requests with transitive dependencies. As + * the repeated label is not allowed in oneof fields, we use a + * FileDescriptorResponse message to encapsulate the repeated fields. + * The reflection service is allowed to avoid sending FileDescriptorProtos + * that were previously sent in response to earlier requests in the stream. + */ + 'fileDescriptorResponse'?: (_grpc_reflection_v1alpha_FileDescriptorResponse__Output | null); + /** + * This message is used to answer all_extension_numbers_of_type requst. + */ + 'allExtensionNumbersResponse'?: (_grpc_reflection_v1alpha_ExtensionNumberResponse__Output | null); + /** + * This message is used to answer list_services request. + */ + 'listServicesResponse'?: (_grpc_reflection_v1alpha_ListServiceResponse__Output | null); + /** + * This message is used when an error occurs. + */ + 'errorResponse'?: (_grpc_reflection_v1alpha_ErrorResponse__Output | null); + /** + * The server set one of the following fields accroding to the message_request + * in the request. + */ + 'messageResponse': "fileDescriptorResponse"|"allExtensionNumbersResponse"|"listServicesResponse"|"errorResponse"; +} diff --git a/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServiceResponse.ts b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServiceResponse.ts new file mode 100644 index 000000000..ff35cf522 --- /dev/null +++ b/packages/grpc-reflection/src/generated/grpc/reflection/v1alpha/ServiceResponse.ts @@ -0,0 +1,26 @@ +// Original file: proto/grpc/reflection/v1alpha/reflection.proto + + +/** + * The information of a single service used by ListServiceResponse to answer + * list_services request. + */ +export interface ServiceResponse { + /** + * Full name of a registered service, including its package name. The format + * is . + */ + 'name'?: (string); +} + +/** + * The information of a single service used by ListServiceResponse to answer + * list_services request. + */ +export interface ServiceResponse__Output { + /** + * Full name of a registered service, including its package name. The format + * is . + */ + 'name': (string); +} diff --git a/packages/grpc-reflection/src/protobuf-visitor.ts b/packages/grpc-reflection/src/protobuf-visitor.ts new file mode 100644 index 000000000..349b37d4d --- /dev/null +++ b/packages/grpc-reflection/src/protobuf-visitor.ts @@ -0,0 +1,110 @@ +import { + DescriptorProto, + EnumDescriptorProto, + EnumValueDescriptorProto, + FieldDescriptorProto, + FileDescriptorProto, + MethodDescriptorProto, + OneofDescriptorProto, + ServiceDescriptorProto, +} from 'google-protobuf/google/protobuf/descriptor_pb'; + +/** A set of functions for operating on protobuf objects as we visit them in a traversal */ +interface Visitor { + field?: (fqn: string, file: FileDescriptorProto, field: FieldDescriptorProto) => void; + extension?: (fqn: string, file: FileDescriptorProto, extension: FieldDescriptorProto) => void; + oneOf?: (fqn: string, file: FileDescriptorProto, decl: OneofDescriptorProto) => void; + message?: (fqn: string, file: FileDescriptorProto, msg: DescriptorProto) => void; + enum?: (fqn: string, file: FileDescriptorProto, msg: EnumDescriptorProto) => void; + enumValue?: (fqn: string, file: FileDescriptorProto, msg: EnumValueDescriptorProto) => void; + service?: (fqn: string, file: FileDescriptorProto, msg: ServiceDescriptorProto) => void; + method?: (fqn: string, file: FileDescriptorProto, method: MethodDescriptorProto) => void; +} + +/** Visit each node in a protobuf file and perform an operation on it + * + * This is useful because protocol buffers has nested objects so if we need to + * traverse them multiple times then we don't want to duplicate that traversal + * logic + * + * @see Visitor for the interface to interact with the nodes + */ +export const visit = (file: FileDescriptorProto, visitor: Visitor): void => { + const processField = (prefix: string, file: FileDescriptorProto, field: FieldDescriptorProto) => { + const fqn = `${prefix}.${field.getName()}`; + if (visitor.field) { + visitor.field(fqn, file, field); + } + }; + + const processExtension = ( + prefix: string, + file: FileDescriptorProto, + ext: FieldDescriptorProto, + ) => { + const fqn = `${prefix}.${ext.getName()}`; + if (visitor.extension) { + visitor.extension(fqn, file, ext); + } + }; + + const processOneOf = (prefix: string, file: FileDescriptorProto, decl: OneofDescriptorProto) => { + const fqn = `${prefix}.${decl.getName()}`; + if (visitor.oneOf) { + visitor.oneOf(fqn, file, decl); + } + }; + + const processEnum = (prefix: string, file: FileDescriptorProto, decl: EnumDescriptorProto) => { + const fqn = `${prefix}.${decl.getName()}`; + + if (visitor.enum) { + visitor.enum(fqn, file, decl); + } + + decl.getValueList().forEach((value) => { + const valueFqn = `${fqn}.${value.getName()}`; + if (visitor.enumValue) { + visitor.enumValue(valueFqn, file, value); + } + }); + }; + + const processMessage = (prefix: string, file: FileDescriptorProto, msg: DescriptorProto) => { + const fqn = `${prefix}.${msg.getName()}`; + if (visitor.message) { + visitor.message(fqn, file, msg); + } + + msg.getNestedTypeList().forEach((type) => processMessage(fqn, file, type)); + msg.getEnumTypeList().forEach((type) => processEnum(fqn, file, type)); + msg.getFieldList().forEach((field) => processField(fqn, file, field)); + msg.getOneofDeclList().forEach((decl) => processOneOf(fqn, file, decl)); + msg.getExtensionList().forEach((ext) => processExtension(fqn, file, ext)); + }; + + const processService = ( + prefix: string, + file: FileDescriptorProto, + service: ServiceDescriptorProto, + ) => { + const fqn = `${prefix}.${service.getName()}`; + if (visitor.service) { + visitor.service(fqn, file, service); + } + + service.getMethodList().forEach((method) => { + const methodFqn = `${fqn}.${method.getName()}`; + if (visitor.method) { + visitor.method(methodFqn, file, method); + } + }); + }; + + const packageName = file.getPackage(); + file.getEnumTypeList().forEach((type) => processEnum(packageName, file, type)); + file.getMessageTypeList().forEach((type) => processMessage(packageName, file, type)); + file.getServiceList().forEach((service) => processService(packageName, file, service)); + + file.getExtensionList().forEach((ext) => processExtension(packageName, file, ext)); +}; diff --git a/packages/grpc-reflection/src/reflection-v1-implementation.ts b/packages/grpc-reflection/src/reflection-v1-implementation.ts new file mode 100644 index 000000000..311b40979 --- /dev/null +++ b/packages/grpc-reflection/src/reflection-v1-implementation.ts @@ -0,0 +1,252 @@ +import { + FileDescriptorProto, + FileDescriptorSet, +} from 'google-protobuf/google/protobuf/descriptor_pb'; + +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; + +import { ExtensionNumberResponse__Output } from './generated/grpc/reflection/v1/ExtensionNumberResponse'; +import { FileDescriptorResponse__Output } from './generated/grpc/reflection/v1/FileDescriptorResponse'; +import { ListServiceResponse__Output } from './generated/grpc/reflection/v1/ListServiceResponse'; +import { visit } from './protobuf-visitor'; +import { scope } from './utils'; + +export class ReflectionError extends Error { + constructor( + readonly statusCode: grpc.status, + readonly message: string, + ) { + super(message); + } +} + +/** Analyzes a gRPC server and exposes methods to reflect on it + * + * NOTE: the files returned by this service may not match the handwritten ones 1:1. + * This is because proto-loader reorients files based on their package definition, + * combining any that have the same package. + * + * For example: if files 'a.proto' and 'b.proto' are both for the same package 'c' then + * we will always return a reference to a combined 'c.proto' instead of the 2 files. + */ +export class ReflectionV1Implementation { + + /** The full list of proto files (including imported deps) that the gRPC server includes */ + private fileDescriptorSet = new FileDescriptorSet(); + + /** An index of proto files by file name (eg. 'sample.proto') */ + private fileNameIndex: Record = {}; + + /** An index of proto files by type extension relationship + * + * extensionIndex[.][] contains a reference to the file containing an + * extension for the type "." and field number "" + */ + private extensionIndex: Record> = {}; + + /** An index of fully qualified symbol names (eg. 'sample.Message') to the files that contain them */ + private symbolMap: Record = {}; + + constructor(root: protoLoader.PackageDefinition) { + Object.values(root).forEach(({ fileDescriptorProtos }) => { + // Add file descriptors to the FileDescriptorSet. + // We use the Array check here because a ServiceDefinition could have a method named the same thing + if (Array.isArray(fileDescriptorProtos)) { + fileDescriptorProtos.forEach((bin) => { + const proto = FileDescriptorProto.deserializeBinary(bin); + const isFileInSet = this.fileDescriptorSet + .getFileList() + .map((f) => f.getName()) + .includes(proto.getName()); + if (!isFileInSet) { + this.fileDescriptorSet.addFile(proto); + } + }); + } + }); + + this.fileNameIndex = Object.fromEntries( + this.fileDescriptorSet.getFileList().map((f) => [f.getName(), f]), + ); + + // Pass 1: Index Values + const index = (fqn: string, file: FileDescriptorProto) => (this.symbolMap[fqn] = file); + this.fileDescriptorSet.getFileList().forEach((file) => + visit(file, { + field: index, + oneOf: index, + message: index, + service: index, + method: index, + enum: index, + enumValue: index, + extension: (fqn, file, ext) => { + index(fqn, file); + + const extendeeName = ext.getExtendee(); + this.extensionIndex[extendeeName] = { + ...(this.extensionIndex[extendeeName] || {}), + [ext.getNumber()]: file, + }; + }, + }), + ); + + // Pass 2: Link References To Values + const addReference = (ref: string, sourceFile: FileDescriptorProto, pkgScope: string) => { + if (!ref) { + return; // nothing to do + } + + let referencedFile: FileDescriptorProto | null = null; + if (ref.startsWith('.')) { + // absolute reference -- just remove the leading '.' and use the ref directly + referencedFile = this.symbolMap[ref.replace(/^\./, '')]; + } else { + // relative reference -- need to seek upwards up the current package scope until we find it + let pkg = pkgScope; + while (pkg && !referencedFile) { + referencedFile = this.symbolMap[`${pkg}.${ref}`]; + pkg = scope(pkg); + } + + // if we didn't find anything then try just a FQN lookup + if (!referencedFile) { + referencedFile = this.symbolMap[ref]; + } + } + + if (!referencedFile) { + console.warn(`Could not find file associated with reference ${ref}`); + return; + } + + if (referencedFile !== sourceFile) { + sourceFile.addDependency(referencedFile.getName()); + } + }; + + this.fileDescriptorSet.getFileList().forEach((file) => + visit(file, { + field: (fqn, file, field) => addReference(field.getTypeName(), file, scope(fqn)), + extension: (fqn, file, ext) => addReference(ext.getTypeName(), file, scope(fqn)), + method: (fqn, file, method) => { + addReference(method.getInputType(), file, scope(fqn)); + addReference(method.getOutputType(), file, scope(fqn)); + }, + }), + ); + } + + /** List the full names of registered gRPC services + * + * note: the spec is unclear as to what the 'listServices' param can be; most + * clients seem to only pass '*' but unsure if this should behave like a + * filter. Until we know how this should behave with different inputs this + * just always returns *all* services. + * + * @returns full-qualified service names (eg. 'sample.SampleService') + */ + listServices(listServices: string): ListServiceResponse__Output { + const services = this.fileDescriptorSet + .getFileList() + .map((file) => + file.getServiceList().map((service) => `${file.getPackage()}.${service.getName()}`), + ) + .flat(); + + return { service: services.map((service) => ({ name: service })) }; + } + + /** Find the proto file(s) that declares the given fully-qualified symbol name + * + * @param symbol fully-qualified name of the symbol to lookup + * (e.g. package.service[.method] or package.type) + * + * @returns descriptors of the file which contains this symbol and its imports + */ + fileContainingSymbol(symbol: string): FileDescriptorResponse__Output { + const file = this.symbolMap[symbol]; + + if (!file) { + throw new ReflectionError(grpc.status.NOT_FOUND, `Symbol not found: ${symbol}`); + } + + const deps = this.getFileDependencies(file); + + return { + fileDescriptorProto: [file, ...deps].map((proto) => proto.serializeBinary()), + }; + } + + /** Find a proto file by the file name + * + * @returns descriptors of the file which contains this symbol and its imports + */ + fileByFilename(filename: string): FileDescriptorResponse__Output { + const file = this.fileNameIndex[filename]; + + if (!file) { + throw new ReflectionError(grpc.status.NOT_FOUND, `Proto file not found: ${filename}`); + } + + const deps = this.getFileDependencies(file); + + return { + fileDescriptorProto: [file, ...deps].map((f) => f.serializeBinary()), + }; + } + + /** Find a proto file containing an extension to a message type + * + * @returns descriptors of the file which contains this symbol and its imports + */ + fileContainingExtension(symbol: string, field: number): FileDescriptorResponse__Output { + const extensionsByFieldNumber = this.extensionIndex[symbol] || {}; + const file = extensionsByFieldNumber[field]; + + if (!file) { + throw new ReflectionError( + grpc.status.NOT_FOUND, + `Extension not found for symbol ${symbol} at field ${field}`, + ); + } + + const deps = this.getFileDependencies(file); + + return { + fileDescriptorProto: [file, ...deps].map((f) => f.serializeBinary()), + }; + } + + allExtensionNumbersOfType(symbol: string): ExtensionNumberResponse__Output { + if (!(symbol in this.extensionIndex)) { + throw new ReflectionError(grpc.status.NOT_FOUND, `Extensions not found for symbol ${symbol}`); + } + + const fieldNumbers = Object.keys(this.extensionIndex[symbol]).map((key) => Number(key)); + + return { + baseTypeName: symbol, + extensionNumber: fieldNumbers, + }; + } + + private getFileDependencies( + file: FileDescriptorProto, + visited: Set = new Set(), + ): FileDescriptorProto[] { + const newVisited = visited.add(file); + + const directDeps = file.getDependencyList().map((dep) => this.fileNameIndex[dep]); + const transitiveDeps = directDeps + .filter((dep) => !newVisited.has(dep)) + .map((dep) => this.getFileDependencies(dep, newVisited)) + .flat(); + + const allDeps = [...directDeps, ...transitiveDeps]; + + return [...new Set(allDeps)]; + } +} diff --git a/packages/grpc-reflection/src/utils.ts b/packages/grpc-reflection/src/utils.ts new file mode 100644 index 000000000..4490abd6e --- /dev/null +++ b/packages/grpc-reflection/src/utils.ts @@ -0,0 +1,11 @@ +/** Gets the package scope for a type name + * + * @example scope('grpc.reflection.v1.Type') == 'grpc.reflection.v1' + */ +export const scope = (path: string, separator: string = '.') => { + if (!path.includes(separator)) { + return ''; + } + + return path.split(separator).slice(0, -1).join(separator); +}; diff --git a/packages/grpc-reflection/test/test-reflection-v1-implementation.ts b/packages/grpc-reflection/test/test-reflection-v1-implementation.ts new file mode 100644 index 000000000..3879da70b --- /dev/null +++ b/packages/grpc-reflection/test/test-reflection-v1-implementation.ts @@ -0,0 +1,182 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import { FileDescriptorProto } from 'google-protobuf/google/protobuf/descriptor_pb'; +import * as protoLoader from '@grpc/proto-loader'; + +import { ReflectionV1Implementation } from '../src/reflection-v1-implementation'; + +describe('GrpcReflectionService', () => { + let reflectionService: ReflectionV1Implementation; + + beforeEach(async () => { + console.log(path.join(__dirname, '../proto/sample/sample.proto')); + console.log([path.join(__dirname, '../proto/sample/vendor')]); + const root = protoLoader.loadSync(path.join(__dirname, '../proto/sample/sample.proto'), { + includeDirs: [path.join(__dirname, '../proto/sample/vendor')] + }); + + reflectionService = new ReflectionV1Implementation(root); + }); + + describe('listServices()', () => { + it('lists all services', () => { + const { service: services } = reflectionService.listServices('*'); + assert.equal(services.length, 1); + assert(services.find((s) => s.name === 'sample.SampleService')); + }); + }); + + describe('fileByFilename()', () => { + it('finds files with transitive dependencies', () => { + const descriptors = reflectionService + .fileByFilename('sample.proto') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert.deepEqual( + new Set(names), + new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']) + ); + }); + + it('finds files with fewer transitive dependencies', () => { + const descriptors = reflectionService + .fileByFilename('vendor.proto') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert.deepEqual(new Set(names), new Set(['vendor.proto', 'vendor_dependency.proto'])); + }); + + it('finds files with no transitive dependencies', () => { + const descriptors = reflectionService + .fileByFilename('vendor_dependency.proto') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + assert.equal(descriptors.length, 1); + assert.equal(descriptors[0].getName(), 'vendor_dependency.proto'); + }); + + it('merges files based on package name', () => { + const descriptors = reflectionService + .fileByFilename('vendor.proto') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert(!names.includes('common.proto')); // file merged into vendor.proto + }); + + it('errors with no file found', () => { + assert.throws( + () => reflectionService.fileByFilename('nonexistent.proto'), + 'Proto file not found', + ); + }); + }); + + describe('fileContainingSymbol()', () => { + it('finds symbols and returns transitive file dependencies', () => { + const descriptors = reflectionService + .fileContainingSymbol('sample.HelloRequest') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert.deepEqual( + new Set(names), + new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']), + ); + }); + + it('finds imported message types', () => { + const descriptors = reflectionService + .fileContainingSymbol('vendor.CommonMessage') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert.deepEqual(new Set(names), new Set(['vendor.proto', 'vendor_dependency.proto'])); + }); + + it('finds transitively imported message types', () => { + const descriptors = reflectionService + .fileContainingSymbol('vendor.dependency.DependentMessage') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + assert.equal(descriptors.length, 1); + assert.equal(descriptors[0].getName(), 'vendor_dependency.proto'); + }); + + it('finds nested message types', () => { + const descriptors = reflectionService + .fileContainingSymbol('sample.HelloRequest.HelloNested') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert.deepEqual( + new Set(names), + new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']), + ); + }); + + it('merges files based on package name', () => { + const descriptors = reflectionService + .fileContainingSymbol('vendor.CommonMessage') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert(!names.includes('common.proto')); // file merged into vendor.proto + }); + + it('errors with no symbol found', () => { + assert.throws( + () => reflectionService.fileContainingSymbol('non.existant.symbol'), + 'Symbol not found:', + ); + }); + + it('resolves references to method types', () => { + const descriptors = reflectionService + .fileContainingSymbol('sample.SampleService.Hello2') + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert.deepEqual( + new Set(names), + new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']), + ); + }); + }); + + describe('fileContainingExtension()', () => { + it('finds extensions and returns transitive file dependencies', () => { + const descriptors = reflectionService + .fileContainingExtension('.vendor.CommonMessage', 101) + .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + + const names = descriptors.map((desc) => desc.getName()); + assert.deepEqual(new Set(names), new Set(['vendor.proto', 'vendor_dependency.proto'])); + }); + + it('errors with no symbol found', () => { + assert.throws( + () => reflectionService.fileContainingExtension('non.existant.symbol', 0), + 'Extension not found', + ); + }); + }); + + describe('allExtensionNumbersOfType()', () => { + it('finds extensions and returns transitive file dependencies', () => { + const response = reflectionService.allExtensionNumbersOfType('.vendor.CommonMessage'); + + assert.equal(response.extensionNumber.length, 1); + assert.equal(response.extensionNumber[0], 101); + }); + + it('errors with no symbol found', () => { + assert.throws( + () => reflectionService.allExtensionNumbersOfType('non.existant.symbol'), + 'Extensions not found', + ); + }); + }); +}); diff --git a/packages/grpc-reflection/test/test-utils.ts b/packages/grpc-reflection/test/test-utils.ts new file mode 100644 index 000000000..bd8bc786b --- /dev/null +++ b/packages/grpc-reflection/test/test-utils.ts @@ -0,0 +1,14 @@ +import * as assert from 'assert'; + +import { scope } from '../src/utils'; + +describe('scope', () => { + it('traverses upwards in the package scope', () => { + assert.strictEqual(scope('grpc.health.v1.HealthCheckResponse.ServiceStatus'), 'grpc.health.v1.HealthCheckResponse'); + assert.strictEqual(scope(scope(scope(scope('grpc.health.v1.HealthCheckResponse.ServiceStatus')))), 'grpc'); + }); + it('returns an empty package when at the top', () => { + assert.strictEqual(scope('Message'), ''); + assert.strictEqual(scope(''), ''); + }); +}); diff --git a/packages/grpc-reflection/tsconfig.json b/packages/grpc-reflection/tsconfig.json new file mode 100644 index 000000000..e8a746d0d --- /dev/null +++ b/packages/grpc-reflection/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "declaration": true, + "forceConsistentCasingInFileNames": true, + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "pretty": true, + "sourceMap": true, + "strictNullChecks": false, + "lib": ["es2017"], + "outDir": "build", + "target": "es2017", + "module": "commonjs", + "resolveJsonModule": true, + "incremental": true, + "types": ["mocha"], + "noUnusedLocals": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} From 215078f49a385ea6e9b3d95fbb19038a1c4a0bdc Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Sat, 4 Nov 2023 17:46:13 -0400 Subject: [PATCH 631/694] feat(grpc-reflection): added reflection service to add capability to a users server --- packages/grpc-reflection/example/server.ts | 22 ++++++ packages/grpc-reflection/package.json | 8 +-- packages/grpc-reflection/src/constants.ts | 14 ++++ packages/grpc-reflection/src/index.ts | 1 + .../src/reflection-v1-implementation.ts | 68 +++++++++++++++++++ .../grpc-reflection/src/reflection-v1alpha.ts | 42 ++++++++++++ packages/grpc-reflection/src/service.ts | 55 +++++++++++++++ 7 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 packages/grpc-reflection/example/server.ts create mode 100644 packages/grpc-reflection/src/constants.ts create mode 100644 packages/grpc-reflection/src/index.ts create mode 100644 packages/grpc-reflection/src/reflection-v1alpha.ts create mode 100644 packages/grpc-reflection/src/service.ts diff --git a/packages/grpc-reflection/example/server.ts b/packages/grpc-reflection/example/server.ts new file mode 100644 index 000000000..fa691f769 --- /dev/null +++ b/packages/grpc-reflection/example/server.ts @@ -0,0 +1,22 @@ +import * as path from 'path'; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; + +import { ReflectionService } from '../src'; + +const PROTO_PATH = path.join(__dirname, '../proto/sample/sample.proto'); +const INCLUDE_PATH = path.join(__dirname, '../proto/sample/vendor'); + +const server = new grpc.Server(); +const packageDefinition = protoLoader.loadSync(PROTO_PATH, { includeDirs: [INCLUDE_PATH] }); +const reflection = new ReflectionService(packageDefinition); +reflection.addToServer(server); + +server.bindAsync('localhost:5000', grpc.ServerCredentials.createInsecure(), () => { + server.start(); +}); + +// const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); + + + diff --git a/packages/grpc-reflection/package.json b/packages/grpc-reflection/package.json index b413dddae..8f8ea8ef2 100644 --- a/packages/grpc-reflection/package.json +++ b/packages/grpc-reflection/package.json @@ -15,12 +15,10 @@ "email": "justinmtimmons@gmail.com" } ], + "main": "build/src/index.js", + "types": "build/src/index.d.ts", "files": [ - "LICENSE", - "README.md", - "src", - "build", - "proto" + "build" ], "license": "Apache-2.0", "scripts": { diff --git a/packages/grpc-reflection/src/constants.ts b/packages/grpc-reflection/src/constants.ts new file mode 100644 index 000000000..93ad27825 --- /dev/null +++ b/packages/grpc-reflection/src/constants.ts @@ -0,0 +1,14 @@ +import * as protoLoader from '@grpc/proto-loader'; + +/** Options to use when loading protobuf files in this repo +* +* @remarks *must* match the proto-loader-gen-types usage in the package.json +* otherwise the generated types may not match the data coming into this service +*/ +export const PROTO_LOADER_OPTS: protoLoader.Options = { + longs: String, + enums: String, + bytes: Array, + defaults: true, + oneofs: true +}; diff --git a/packages/grpc-reflection/src/index.ts b/packages/grpc-reflection/src/index.ts new file mode 100644 index 000000000..deba7fe33 --- /dev/null +++ b/packages/grpc-reflection/src/index.ts @@ -0,0 +1 @@ +export { ReflectionService } from './service'; diff --git a/packages/grpc-reflection/src/reflection-v1-implementation.ts b/packages/grpc-reflection/src/reflection-v1-implementation.ts index 311b40979..4f298f862 100644 --- a/packages/grpc-reflection/src/reflection-v1-implementation.ts +++ b/packages/grpc-reflection/src/reflection-v1-implementation.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import { FileDescriptorProto, FileDescriptorSet, @@ -9,8 +10,11 @@ import * as protoLoader from '@grpc/proto-loader'; import { ExtensionNumberResponse__Output } from './generated/grpc/reflection/v1/ExtensionNumberResponse'; import { FileDescriptorResponse__Output } from './generated/grpc/reflection/v1/FileDescriptorResponse'; import { ListServiceResponse__Output } from './generated/grpc/reflection/v1/ListServiceResponse'; +import { ServerReflectionRequest } from './generated/grpc/reflection/v1/ServerReflectionRequest'; +import { ServerReflectionResponse } from './generated/grpc/reflection/v1/ServerReflectionResponse'; import { visit } from './protobuf-visitor'; import { scope } from './utils'; +import { PROTO_LOADER_OPTS } from './constants'; export class ReflectionError extends Error { constructor( @@ -139,6 +143,70 @@ export class ReflectionV1Implementation { ); } + addToServer(server: Pick) { + const protoPath = path.join(__dirname, '../proto/grpc/reflection/v1/reflection.proto'); + const pkgDefinition = protoLoader.loadSync(protoPath, PROTO_LOADER_OPTS); + const pkg = grpc.loadPackageDefinition(pkgDefinition) as any; + + server.addService(pkg.grpc.reflection.v1.ServerReflection.service, { + ServerReflectionInfo: ( + stream: grpc.ServerDuplexStream + ) => { + stream.on('end', () => stream.end()); + + stream.on('data', (message: ServerReflectionRequest) => { + stream.write(this.handleServerReflectionRequest(message)); + }); + } + }); + } + + /** Assemble a response for a single server reflection request in the stream */ + handleServerReflectionRequest(message: ServerReflectionRequest): ServerReflectionResponse { + const response: ServerReflectionResponse = { + validHost: message.host, + originalRequest: message, + fileDescriptorResponse: undefined, + allExtensionNumbersResponse: undefined, + listServicesResponse: undefined, + errorResponse: undefined, + }; + + try { + if (message.listServices !== undefined) { + response.listServicesResponse = this.listServices(message.listServices); + } else if (message.fileContainingSymbol !== undefined) { + response.fileDescriptorResponse = this.fileContainingSymbol(message.fileContainingSymbol); + } else if (message.fileByFilename !== undefined) { + response.fileDescriptorResponse = this.fileByFilename(message.fileByFilename); + } else if (message.fileContainingExtension !== undefined) { + const { containingType, extensionNumber } = message.fileContainingExtension; + response.fileDescriptorResponse = this.fileContainingExtension(containingType, extensionNumber); + } else if (message.allExtensionNumbersOfType) { + response.allExtensionNumbersResponse = this.allExtensionNumbersOfType(message.allExtensionNumbersOfType); + } else { + throw new ReflectionError( + grpc.status.UNIMPLEMENTED, + `Unimplemented method for request: ${message}`, + ); + } + } catch (e) { + if (e instanceof ReflectionError) { + response.errorResponse = { + errorCode: e.statusCode, + errorMessage: e.message, + }; + } else { + response.errorResponse = { + errorCode: grpc.status.UNKNOWN, + errorMessage: 'Failed to process gRPC reflection request: unknown error', + }; + } + } + + return response; + } + /** List the full names of registered gRPC services * * note: the spec is unclear as to what the 'listServices' param can be; most diff --git a/packages/grpc-reflection/src/reflection-v1alpha.ts b/packages/grpc-reflection/src/reflection-v1alpha.ts new file mode 100644 index 000000000..b0d86e594 --- /dev/null +++ b/packages/grpc-reflection/src/reflection-v1alpha.ts @@ -0,0 +1,42 @@ +import * as path from 'path'; + +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; + +import { ServerReflectionRequest } from './generated/grpc/reflection/v1/ServerReflectionRequest'; +import { ServerReflectionResponse } from './generated/grpc/reflection/v1/ServerReflectionResponse'; +import { PROTO_LOADER_OPTS } from './constants'; +import { ReflectionV1Implementation } from './reflection-v1-implementation'; + + +/** Analyzes a gRPC server and exposes methods to reflect on it + * + * NOTE: the files returned by this service may not match the handwritten ones 1:1. + * This is because proto-loader reorients files based on their package definition, + * combining any that have the same package. + * + * For example: if files 'a.proto' and 'b.proto' are both for the same package 'c' then + * we will always return a reference to a combined 'c.proto' instead of the 2 files. + * + * @remarks as the v1 and v1alpha specs are identical, this implementation extends v1 + * and just exposes it at the v1alpha package instead + */ +export class ReflectionV1AlphaImplementation extends ReflectionV1Implementation { + addToServer(server: Pick) { + const protoPath = path.join(__dirname, '../proto/grpc/reflection/v1alpha/reflection.proto'); + const pkgDefinition = protoLoader.loadSync(protoPath, PROTO_LOADER_OPTS); + const pkg = grpc.loadPackageDefinition(pkgDefinition) as any; + + server.addService(pkg.grpc.reflection.v1alpha.ServerReflection.service, { + ServerReflectionInfo: ( + stream: grpc.ServerDuplexStream + ) => { + stream.on('end', () => stream.end()); + + stream.on('data', (message: ServerReflectionRequest) => { + stream.write(this.handleServerReflectionRequest(message)); + }); + } + }); + } +} diff --git a/packages/grpc-reflection/src/service.ts b/packages/grpc-reflection/src/service.ts new file mode 100644 index 000000000..7f08610b8 --- /dev/null +++ b/packages/grpc-reflection/src/service.ts @@ -0,0 +1,55 @@ +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; + +import { ReflectionV1Implementation } from './reflection-v1-implementation'; +import { ReflectionV1AlphaImplementation } from './reflection-v1alpha'; + +interface ReflectionServerOptions { + /** whitelist of fully-qualified service names to expose. (Default: expose all) */ + services?: string[]; +} + +/** Analyzes a gRPC package and exposes endpoints providing information about + * it according to the gRPC Server Reflection API Specification + * + * @see https://github.com/grpc/grpc/blob/master/doc/server-reflection.md + * + * @remarks + * + * in order to keep backwards compatibility as the reflection schema evolves + * this service contains implementations for each of the published versions + * + * @privateRemarks + * + * this class acts mostly as a facade to several underlying implementations. This + * allows us to add or remove support for different versions of the reflection + * schema without affecting the consumer + * + */ +export class ReflectionService { + private readonly v1: ReflectionV1Implementation; + private readonly v1Alpha: ReflectionV1AlphaImplementation; + + constructor(pkg: protoLoader.PackageDefinition, options?: ReflectionServerOptions) { + + if (options.services) { + const whitelist = new Set(options.services); + + for (const key in Object.keys(pkg)) { + const value = pkg[key]; + const isService = value.format !== 'Protocol Buffer 3 DescriptorProto' && value.format !== 'Protocol Buffer 3 EnumDescriptorProto'; + if (isService && !whitelist.has(key)) { + delete pkg[key]; + } + } + } + + this.v1 = new ReflectionV1Implementation(pkg); + this.v1Alpha = new ReflectionV1AlphaImplementation(pkg); + } + + addToServer(server: Pick) { + this.v1.addToServer(server); + this.v1Alpha.addToServer(server); + } +} From 3b4f92ee6201c99ef7bb1ab56c6520f35b15b1c0 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Wed, 8 Nov 2023 20:42:20 -0500 Subject: [PATCH 632/694] refactor(grpc-reflection): file cleanup and enabled ts strict mode --- packages/grpc-reflection/example/server.ts | 7 +- .../grpc-reflection/proto/sample/sample.proto | 4 ++ .../{ => implementations/common}/constants.ts | 0 .../src/implementations/common/interfaces.ts | 5 ++ .../common}/protobuf-visitor.ts | 2 +- .../src/{ => implementations/common}/utils.ts | 0 .../reflection-v1.ts} | 64 +++++++++++-------- .../reflection-v1alpha.ts | 14 ++-- packages/grpc-reflection/src/service.ts | 27 ++------ .../test/test-reflection-v1-implementation.ts | 16 ++++- packages/grpc-reflection/test/test-utils.ts | 2 +- packages/grpc-reflection/tsconfig.json | 2 +- 12 files changed, 77 insertions(+), 66 deletions(-) rename packages/grpc-reflection/src/{ => implementations/common}/constants.ts (100%) create mode 100644 packages/grpc-reflection/src/implementations/common/interfaces.ts rename packages/grpc-reflection/src/{ => implementations/common}/protobuf-visitor.ts (98%) rename packages/grpc-reflection/src/{ => implementations/common}/utils.ts (100%) rename packages/grpc-reflection/src/{reflection-v1-implementation.ts => implementations/reflection-v1.ts} (81%) rename packages/grpc-reflection/src/{ => implementations}/reflection-v1alpha.ts (69%) diff --git a/packages/grpc-reflection/example/server.ts b/packages/grpc-reflection/example/server.ts index fa691f769..00a3e1150 100644 --- a/packages/grpc-reflection/example/server.ts +++ b/packages/grpc-reflection/example/server.ts @@ -9,14 +9,9 @@ const INCLUDE_PATH = path.join(__dirname, '../proto/sample/vendor'); const server = new grpc.Server(); const packageDefinition = protoLoader.loadSync(PROTO_PATH, { includeDirs: [INCLUDE_PATH] }); -const reflection = new ReflectionService(packageDefinition); +const reflection = new ReflectionService(packageDefinition, { services: ['sample.SampleService'] }); reflection.addToServer(server); server.bindAsync('localhost:5000', grpc.ServerCredentials.createInsecure(), () => { server.start(); }); - -// const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); - - - diff --git a/packages/grpc-reflection/proto/sample/sample.proto b/packages/grpc-reflection/proto/sample/sample.proto index b5b532087..acf969c1a 100644 --- a/packages/grpc-reflection/proto/sample/sample.proto +++ b/packages/grpc-reflection/proto/sample/sample.proto @@ -9,6 +9,10 @@ service SampleService { rpc Hello2 (HelloRequest) returns (CommonMessage) {} } +service IgnoreService { + rpc Hello (HelloRequest) returns (HelloResponse) {} +} + message HelloRequest { string hello = 1; HelloNested nested = 2; diff --git a/packages/grpc-reflection/src/constants.ts b/packages/grpc-reflection/src/implementations/common/constants.ts similarity index 100% rename from packages/grpc-reflection/src/constants.ts rename to packages/grpc-reflection/src/implementations/common/constants.ts diff --git a/packages/grpc-reflection/src/implementations/common/interfaces.ts b/packages/grpc-reflection/src/implementations/common/interfaces.ts new file mode 100644 index 000000000..c6b07bccb --- /dev/null +++ b/packages/grpc-reflection/src/implementations/common/interfaces.ts @@ -0,0 +1,5 @@ +/** Options to create a reflection server */ +export interface ReflectionServerOptions { + /** whitelist of fully-qualified service names to expose. (Default: expose all) */ + services?: string[]; +} diff --git a/packages/grpc-reflection/src/protobuf-visitor.ts b/packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts similarity index 98% rename from packages/grpc-reflection/src/protobuf-visitor.ts rename to packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts index 349b37d4d..556a8bf3e 100644 --- a/packages/grpc-reflection/src/protobuf-visitor.ts +++ b/packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts @@ -101,7 +101,7 @@ export const visit = (file: FileDescriptorProto, visitor: Visitor): void => { }); }; - const packageName = file.getPackage(); + const packageName = file.getPackage() || ''; file.getEnumTypeList().forEach((type) => processEnum(packageName, file, type)); file.getMessageTypeList().forEach((type) => processMessage(packageName, file, type)); file.getServiceList().forEach((service) => processService(packageName, file, service)); diff --git a/packages/grpc-reflection/src/utils.ts b/packages/grpc-reflection/src/implementations/common/utils.ts similarity index 100% rename from packages/grpc-reflection/src/utils.ts rename to packages/grpc-reflection/src/implementations/common/utils.ts diff --git a/packages/grpc-reflection/src/reflection-v1-implementation.ts b/packages/grpc-reflection/src/implementations/reflection-v1.ts similarity index 81% rename from packages/grpc-reflection/src/reflection-v1-implementation.ts rename to packages/grpc-reflection/src/implementations/reflection-v1.ts index 4f298f862..2f43d99f4 100644 --- a/packages/grpc-reflection/src/reflection-v1-implementation.ts +++ b/packages/grpc-reflection/src/implementations/reflection-v1.ts @@ -7,14 +7,15 @@ import { import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; -import { ExtensionNumberResponse__Output } from './generated/grpc/reflection/v1/ExtensionNumberResponse'; -import { FileDescriptorResponse__Output } from './generated/grpc/reflection/v1/FileDescriptorResponse'; -import { ListServiceResponse__Output } from './generated/grpc/reflection/v1/ListServiceResponse'; -import { ServerReflectionRequest } from './generated/grpc/reflection/v1/ServerReflectionRequest'; -import { ServerReflectionResponse } from './generated/grpc/reflection/v1/ServerReflectionResponse'; -import { visit } from './protobuf-visitor'; -import { scope } from './utils'; -import { PROTO_LOADER_OPTS } from './constants'; +import { ExtensionNumberResponse__Output } from '../generated/grpc/reflection/v1/ExtensionNumberResponse'; +import { FileDescriptorResponse__Output } from '../generated/grpc/reflection/v1/FileDescriptorResponse'; +import { ListServiceResponse__Output } from '../generated/grpc/reflection/v1/ListServiceResponse'; +import { ServerReflectionRequest } from '../generated/grpc/reflection/v1/ServerReflectionRequest'; +import { ServerReflectionResponse } from '../generated/grpc/reflection/v1/ServerReflectionResponse'; +import { visit } from './common/protobuf-visitor'; +import { scope } from './common/utils'; +import { PROTO_LOADER_OPTS } from './common/constants'; +import { ReflectionServerOptions } from './common/interfaces'; export class ReflectionError extends Error { constructor( @@ -37,22 +38,27 @@ export class ReflectionError extends Error { export class ReflectionV1Implementation { /** The full list of proto files (including imported deps) that the gRPC server includes */ - private fileDescriptorSet = new FileDescriptorSet(); + private readonly fileDescriptorSet = new FileDescriptorSet(); /** An index of proto files by file name (eg. 'sample.proto') */ - private fileNameIndex: Record = {}; + private readonly fileNameIndex: Record = {}; /** An index of proto files by type extension relationship * * extensionIndex[.][] contains a reference to the file containing an * extension for the type "." and field number "" */ - private extensionIndex: Record> = {}; + private readonly extensionIndex: Record> = {}; /** An index of fully qualified symbol names (eg. 'sample.Message') to the files that contain them */ - private symbolMap: Record = {}; + private readonly symbolMap: Record = {}; + + /** Options that the user provided for this service */ + private readonly options?: ReflectionServerOptions; + + constructor(root: protoLoader.PackageDefinition, options?: ReflectionServerOptions) { + this.options = options; - constructor(root: protoLoader.PackageDefinition) { Object.values(root).forEach(({ fileDescriptorProtos }) => { // Add file descriptors to the FileDescriptorSet. // We use the Array check here because a ServiceDefinition could have a method named the same thing @@ -88,10 +94,10 @@ export class ReflectionV1Implementation { extension: (fqn, file, ext) => { index(fqn, file); - const extendeeName = ext.getExtendee(); + const extendeeName = ext.getExtendee() || ''; this.extensionIndex[extendeeName] = { ...(this.extensionIndex[extendeeName] || {}), - [ext.getNumber()]: file, + [ext.getNumber() || -1]: file, }; }, }), @@ -126,25 +132,26 @@ export class ReflectionV1Implementation { return; } - if (referencedFile !== sourceFile) { - sourceFile.addDependency(referencedFile.getName()); + const fname = referencedFile.getName(); + if (referencedFile !== sourceFile && fname) { + sourceFile.addDependency(fname); } }; this.fileDescriptorSet.getFileList().forEach((file) => visit(file, { - field: (fqn, file, field) => addReference(field.getTypeName(), file, scope(fqn)), - extension: (fqn, file, ext) => addReference(ext.getTypeName(), file, scope(fqn)), + field: (fqn, file, field) => addReference(field.getTypeName() || '', file, scope(fqn)), + extension: (fqn, file, ext) => addReference(ext.getTypeName() || '', file, scope(fqn)), method: (fqn, file, method) => { - addReference(method.getInputType(), file, scope(fqn)); - addReference(method.getOutputType(), file, scope(fqn)); + addReference(method.getInputType() || '', file, scope(fqn)); + addReference(method.getOutputType() || '', file, scope(fqn)); }, }), ); } addToServer(server: Pick) { - const protoPath = path.join(__dirname, '../proto/grpc/reflection/v1/reflection.proto'); + const protoPath = path.join(__dirname, '../../proto/grpc/reflection/v1/reflection.proto'); const pkgDefinition = protoLoader.loadSync(protoPath, PROTO_LOADER_OPTS); const pkg = grpc.loadPackageDefinition(pkgDefinition) as any; @@ -180,8 +187,10 @@ export class ReflectionV1Implementation { } else if (message.fileByFilename !== undefined) { response.fileDescriptorResponse = this.fileByFilename(message.fileByFilename); } else if (message.fileContainingExtension !== undefined) { - const { containingType, extensionNumber } = message.fileContainingExtension; - response.fileDescriptorResponse = this.fileContainingExtension(containingType, extensionNumber); + response.fileDescriptorResponse = this.fileContainingExtension( + message.fileContainingExtension?.containingType || '', + message.fileContainingExtension?.extensionNumber || -1 + ); } else if (message.allExtensionNumbersOfType) { response.allExtensionNumbersResponse = this.allExtensionNumbersOfType(message.allExtensionNumbersOfType); } else { @@ -224,7 +233,12 @@ export class ReflectionV1Implementation { ) .flat(); - return { service: services.map((service) => ({ name: service })) }; + const whitelist = new Set(this.options?.services ?? undefined); + const exposedServices = this.options?.services ? + services.filter(service => whitelist.has(service)) + : services; + + return { service: exposedServices.map((service) => ({ name: service })) }; } /** Find the proto file(s) that declares the given fully-qualified symbol name diff --git a/packages/grpc-reflection/src/reflection-v1alpha.ts b/packages/grpc-reflection/src/implementations/reflection-v1alpha.ts similarity index 69% rename from packages/grpc-reflection/src/reflection-v1alpha.ts rename to packages/grpc-reflection/src/implementations/reflection-v1alpha.ts index b0d86e594..f3fdcafe0 100644 --- a/packages/grpc-reflection/src/reflection-v1alpha.ts +++ b/packages/grpc-reflection/src/implementations/reflection-v1alpha.ts @@ -3,10 +3,10 @@ import * as path from 'path'; import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; -import { ServerReflectionRequest } from './generated/grpc/reflection/v1/ServerReflectionRequest'; -import { ServerReflectionResponse } from './generated/grpc/reflection/v1/ServerReflectionResponse'; -import { PROTO_LOADER_OPTS } from './constants'; -import { ReflectionV1Implementation } from './reflection-v1-implementation'; +import { ServerReflectionRequest } from '../generated/grpc/reflection/v1/ServerReflectionRequest'; +import { ServerReflectionResponse } from '../generated/grpc/reflection/v1/ServerReflectionResponse'; +import { PROTO_LOADER_OPTS } from './common/constants'; +import { ReflectionV1Implementation } from './reflection-v1'; /** Analyzes a gRPC server and exposes methods to reflect on it @@ -18,12 +18,12 @@ import { ReflectionV1Implementation } from './reflection-v1-implementation'; * For example: if files 'a.proto' and 'b.proto' are both for the same package 'c' then * we will always return a reference to a combined 'c.proto' instead of the 2 files. * - * @remarks as the v1 and v1alpha specs are identical, this implementation extends v1 - * and just exposes it at the v1alpha package instead + * @privateRemarks as the v1 and v1alpha specs are identical, this implementation extends + * reflection-v1 and exposes it at the v1alpha package instead */ export class ReflectionV1AlphaImplementation extends ReflectionV1Implementation { addToServer(server: Pick) { - const protoPath = path.join(__dirname, '../proto/grpc/reflection/v1alpha/reflection.proto'); + const protoPath = path.join(__dirname, '../../proto/grpc/reflection/v1alpha/reflection.proto'); const pkgDefinition = protoLoader.loadSync(protoPath, PROTO_LOADER_OPTS); const pkg = grpc.loadPackageDefinition(pkgDefinition) as any; diff --git a/packages/grpc-reflection/src/service.ts b/packages/grpc-reflection/src/service.ts index 7f08610b8..616d210de 100644 --- a/packages/grpc-reflection/src/service.ts +++ b/packages/grpc-reflection/src/service.ts @@ -1,13 +1,9 @@ import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; -import { ReflectionV1Implementation } from './reflection-v1-implementation'; -import { ReflectionV1AlphaImplementation } from './reflection-v1alpha'; - -interface ReflectionServerOptions { - /** whitelist of fully-qualified service names to expose. (Default: expose all) */ - services?: string[]; -} +import { ReflectionV1Implementation } from './implementations/reflection-v1'; +import { ReflectionV1AlphaImplementation } from './implementations/reflection-v1alpha'; +import { ReflectionServerOptions } from './implementations/common/interfaces'; /** Analyzes a gRPC package and exposes endpoints providing information about * it according to the gRPC Server Reflection API Specification @@ -31,21 +27,8 @@ export class ReflectionService { private readonly v1Alpha: ReflectionV1AlphaImplementation; constructor(pkg: protoLoader.PackageDefinition, options?: ReflectionServerOptions) { - - if (options.services) { - const whitelist = new Set(options.services); - - for (const key in Object.keys(pkg)) { - const value = pkg[key]; - const isService = value.format !== 'Protocol Buffer 3 DescriptorProto' && value.format !== 'Protocol Buffer 3 EnumDescriptorProto'; - if (isService && !whitelist.has(key)) { - delete pkg[key]; - } - } - } - - this.v1 = new ReflectionV1Implementation(pkg); - this.v1Alpha = new ReflectionV1AlphaImplementation(pkg); + this.v1 = new ReflectionV1Implementation(pkg, options); + this.v1Alpha = new ReflectionV1AlphaImplementation(pkg, options); } addToServer(server: Pick) { diff --git a/packages/grpc-reflection/test/test-reflection-v1-implementation.ts b/packages/grpc-reflection/test/test-reflection-v1-implementation.ts index 3879da70b..81613ac69 100644 --- a/packages/grpc-reflection/test/test-reflection-v1-implementation.ts +++ b/packages/grpc-reflection/test/test-reflection-v1-implementation.ts @@ -3,14 +3,12 @@ import * as path from 'path'; import { FileDescriptorProto } from 'google-protobuf/google/protobuf/descriptor_pb'; import * as protoLoader from '@grpc/proto-loader'; -import { ReflectionV1Implementation } from '../src/reflection-v1-implementation'; +import { ReflectionV1Implementation } from '../src/implementations/reflection-v1'; describe('GrpcReflectionService', () => { let reflectionService: ReflectionV1Implementation; beforeEach(async () => { - console.log(path.join(__dirname, '../proto/sample/sample.proto')); - console.log([path.join(__dirname, '../proto/sample/vendor')]); const root = protoLoader.loadSync(path.join(__dirname, '../proto/sample/sample.proto'), { includeDirs: [path.join(__dirname, '../proto/sample/vendor')] }); @@ -20,6 +18,18 @@ describe('GrpcReflectionService', () => { describe('listServices()', () => { it('lists all services', () => { + const { service: services } = reflectionService.listServices('*'); + assert.equal(services.length, 2); + assert(services.find((s) => s.name === 'sample.SampleService')); + }); + + it('whitelists services properly', () => { + const root = protoLoader.loadSync(path.join(__dirname, '../proto/sample/sample.proto'), { + includeDirs: [path.join(__dirname, '../proto/sample/vendor')] + }); + + reflectionService = new ReflectionV1Implementation(root, { services: ['sample.SampleService'] }); + const { service: services } = reflectionService.listServices('*'); assert.equal(services.length, 1); assert(services.find((s) => s.name === 'sample.SampleService')); diff --git a/packages/grpc-reflection/test/test-utils.ts b/packages/grpc-reflection/test/test-utils.ts index bd8bc786b..2f197652a 100644 --- a/packages/grpc-reflection/test/test-utils.ts +++ b/packages/grpc-reflection/test/test-utils.ts @@ -1,6 +1,6 @@ import * as assert from 'assert'; -import { scope } from '../src/utils'; +import { scope } from '../src/implementations/common/utils'; describe('scope', () => { it('traverses upwards in the package scope', () => { diff --git a/packages/grpc-reflection/tsconfig.json b/packages/grpc-reflection/tsconfig.json index e8a746d0d..763ceda98 100644 --- a/packages/grpc-reflection/tsconfig.json +++ b/packages/grpc-reflection/tsconfig.json @@ -9,7 +9,7 @@ "noImplicitReturns": true, "pretty": true, "sourceMap": true, - "strictNullChecks": false, + "strict": true, "lib": ["es2017"], "outDir": "build", "target": "es2017", From 66f972cb8758ca16eb15f69fc0d4c195d086a5d0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 27 Oct 2023 10:06:08 -0700 Subject: [PATCH 633/694] grpc-js: Implement unbind --- packages/grpc-js/src/server-credentials.ts | 89 ++- packages/grpc-js/src/server.ts | 795 ++++++++++++--------- packages/grpc-js/src/subchannel.ts | 1 + packages/grpc-js/src/uri-parser.ts | 13 + packages/grpc-js/test/test-server.ts | 86 +++ 5 files changed, 655 insertions(+), 329 deletions(-) diff --git a/packages/grpc-js/src/server-credentials.ts b/packages/grpc-js/src/server-credentials.ts index 17ab29805..0dd5f8cae 100644 --- a/packages/grpc-js/src/server-credentials.ts +++ b/packages/grpc-js/src/server-credentials.ts @@ -26,6 +26,7 @@ export interface KeyCertPair { export abstract class ServerCredentials { abstract _isSecure(): boolean; abstract _getSettings(): SecureServerOptions | null; + abstract _equals(other: ServerCredentials): boolean; static createInsecure(): ServerCredentials { return new InsecureServerCredentials(); @@ -48,8 +49,8 @@ export abstract class ServerCredentials { throw new TypeError('checkClientCertificate must be a boolean'); } - const cert = []; - const key = []; + const cert: Buffer[] = []; + const key: Buffer[] = []; for (let i = 0; i < keyCertPairs.length; i++) { const pair = keyCertPairs[i]; @@ -71,7 +72,7 @@ export abstract class ServerCredentials { } return new SecureServerCredentials({ - ca: rootCerts || getDefaultRootsData() || undefined, + ca: rootCerts ?? getDefaultRootsData() ?? undefined, cert, key, requestCert: checkClientCertificate, @@ -88,6 +89,10 @@ class InsecureServerCredentials extends ServerCredentials { _getSettings(): null { return null; } + + _equals(other: ServerCredentials): boolean { + return other instanceof InsecureServerCredentials; + } } class SecureServerCredentials extends ServerCredentials { @@ -105,4 +110,82 @@ class SecureServerCredentials extends ServerCredentials { _getSettings(): SecureServerOptions { return this.options; } + + /** + * Checks equality by checking the options that are actually set by + * createSsl. + * @param other + * @returns + */ + _equals(other: ServerCredentials): boolean { + if (this === other) { + return true; + } + if (!(other instanceof SecureServerCredentials)) { + return false; + } + // options.ca equality check + if (Buffer.isBuffer(this.options.ca) && Buffer.isBuffer(other.options.ca)) { + if (!this.options.ca.equals(other.options.ca)) { + return false; + } + } else { + if (this.options.ca !== other.options.ca) { + return false; + } + } + // options.cert equality check + if (Array.isArray(this.options.cert) && Array.isArray(other.options.cert)) { + if (this.options.cert.length !== other.options.cert.length) { + return false; + } + for (let i = 0; i < this.options.cert.length; i++) { + const thisCert = this.options.cert[i]; + const otherCert = other.options.cert[i]; + if (Buffer.isBuffer(thisCert) && Buffer.isBuffer(otherCert)) { + if (!thisCert.equals(otherCert)) { + return false; + } + } else { + if (thisCert !== otherCert) { + return false; + } + } + } + } else { + if (this.options.cert !== other.options.cert) { + return false; + } + } + // options.key equality check + if (Array.isArray(this.options.key) && Array.isArray(other.options.key)) { + if (this.options.key.length !== other.options.key.length) { + return false; + } + for (let i = 0; i < this.options.key.length; i++) { + const thisKey = this.options.key[i]; + const otherKey = other.options.key[i]; + if (Buffer.isBuffer(thisKey) && Buffer.isBuffer(otherKey)) { + if (!thisKey.equals(otherKey)) { + return false; + } + } else { + if (thisKey !== otherKey) { + return false; + } + } + } + } else { + if (this.options.key !== other.options.key) { + return false; + } + } + // options.requestCert equality check + if (this.options.requestCert !== other.options.requestCert) { + return false; + } + /* ciphers is derived from a value that is constant for the process, so no + * equality check is needed. */ + return true; + } } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index f04cd9810..8ff0d0f22 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -17,7 +17,6 @@ import * as http2 from 'http2'; import * as util from 'util'; -import { AddressInfo } from 'net'; import { ServiceError } from './call'; import { Status, LogVerbosity } from './constants'; @@ -54,12 +53,11 @@ import { import * as logging from './logging'; import { SubchannelAddress, - TcpSubchannelAddress, isTcpSubchannelAddress, subchannelAddressToString, stringToSubchannelAddress, } from './subchannel-address'; -import { parseUri } from './uri-parser'; +import { GrpcUri, combineHostPort, parseUri, splitHostPort, uriToString } from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, @@ -83,9 +81,17 @@ const { HTTP2_HEADER_PATH } = http2.constants; const TRACER_NAME = 'server'; +type AnyHttp2Server = http2.Http2Server | http2.Http2SecureServer; + interface BindResult { port: number; count: number; + errors: string[]; +} + +interface SingleAddressBindResult { + port: number; + error?: string; } function noop(): void {} @@ -161,11 +167,61 @@ interface ChannelzSessionInfo { lastMessageReceivedTimestamp: Date | null; } +/** + * Information related to a single invocation of bindAsync. This should be + * tracked in a map keyed by target string, normalized with a pass through + * parseUri -> mapUriDefaultScheme -> uriToString. If the target has a port + * number and the port number is 0, the target string is modified with the + * concrete bound port. + */ +interface BoundPort { + /** + * The key used to refer to this object in the boundPorts map. + */ + mapKey: string; + /** + * The target string, passed through parseUri -> mapUriDefaultScheme. Used + * to determine the final key when the port number is 0. + */ + originalUri: GrpcUri; + /** + * If there is a pending bindAsync operation, this is a promise that resolves + * with the port number when that operation succeeds. If there is no such + * operation pending, this is null. + */ + completionPromise: Promise | null; + /** + * The port number that was actually bound. Populated only after + * completionPromise resolves. + */ + portNumber: number; + /** + * Set by unbind if called while pending is true. + */ + cancelled: boolean; + /** + * The credentials object passed to the original bindAsync call. + */ + credentials: ServerCredentials; + /** + * The set of servers associated with this listening port. A target string + * that expands to multiple addresses will result in multiple listening + * servers. + */ + listeningServers: Set +} + +/** + * Should be in a map keyed by AnyHttp2Server. + */ +interface Http2ServerInfo { + channelzRef: SocketRef; + sessions: Set; +} + export class Server { - private http2ServerList: { - server: http2.Http2Server | http2.Http2SecureServer; - channelzRef: SocketRef; - }[] = []; + private boundPorts: Map= new Map(); + private http2Servers: Map = new Map(); private handlers: Map = new Map< string, @@ -194,6 +250,12 @@ export class Server { private readonly keepaliveTimeMs: number; private readonly keepaliveTimeoutMs: number; + /** + * Options that will be used to construct all Http2Server instances for this + * Server. + */ + private commonServerOptions: http2.ServerOptions; + constructor(options?: ChannelOptions) { this.options = options ?? {}; if (this.options['grpc.enable_channelz'] === 0) { @@ -215,6 +277,24 @@ export class Server { this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS; this.keepaliveTimeoutMs = this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; + this.commonServerOptions = { + maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, + }; + if ('grpc-node.max_session_memory' in this.options) { + this.commonServerOptions.maxSessionMemory = + this.options['grpc-node.max_session_memory']; + } else { + /* By default, set a very large max session memory limit, to effectively + * disable enforcement of the limit. Some testing indicates that Node's + * behavior degrades badly when this limit is reached, so we solve that + * by disabling the check entirely. */ + this.commonServerOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; + } + if ('grpc.max_concurrent_streams' in this.options) { + this.commonServerOptions.settings = { + maxConcurrentStreams: this.options['grpc.max_concurrent_streams'], + }; + } this.trace('Server constructed'); } @@ -382,6 +462,238 @@ export class Server { throw new Error('Not implemented. Use bindAsync() instead'); } + private registerListenerToChannelz(boundAddress: SubchannelAddress) { + return registerChannelzSocket( + subchannelAddressToString(boundAddress), + () => { + return { + localAddress: boundAddress, + remoteAddress: null, + security: null, + remoteName: null, + streamsStarted: 0, + streamsSucceeded: 0, + streamsFailed: 0, + messagesSent: 0, + messagesReceived: 0, + keepAlivesSent: 0, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: null, + lastMessageSentTimestamp: null, + lastMessageReceivedTimestamp: null, + localFlowControlWindow: null, + remoteFlowControlWindow: null, + }; + }, + this.channelzEnabled + ); + } + + private createHttp2Server(credentials: ServerCredentials) { + let http2Server: http2.Http2Server | http2.Http2SecureServer; + if (credentials._isSecure()) { + const secureServerOptions = Object.assign( + this.commonServerOptions, + credentials._getSettings()! + ); + secureServerOptions.enableTrace = + this.options['grpc-node.tls_enable_trace'] === 1; + http2Server = http2.createSecureServer(secureServerOptions); + http2Server.on('secureConnection', (socket: TLSSocket) => { + /* These errors need to be handled by the user of Http2SecureServer, + * according to https://github.com/nodejs/node/issues/35824 */ + socket.on('error', (e: Error) => { + this.trace( + 'An incoming TLS connection closed with error: ' + e.message + ); + }); + }); + } else { + http2Server = http2.createServer(this.commonServerOptions); + } + + http2Server.setTimeout(0, noop); + this._setupHandlers(http2Server); + return http2Server; + } + + private bindOneAddress(address: SubchannelAddress, boundPortObject: BoundPort): Promise { + this.trace( + 'Attempting to bind ' + subchannelAddressToString(address) + ); + const http2Server = this.createHttp2Server(boundPortObject.credentials); + return new Promise((resolve, reject) => { + const onError = (err: Error) => { + this.trace( + 'Failed to bind ' + + subchannelAddressToString(address) + + ' with error ' + + err.message + ); + resolve({ + port: 'port' in address ? address.port : 1, + error: err.message + }); + }; + + http2Server.once('error', onError); + + http2Server.listen(address, () => { + const boundAddress = http2Server.address()!; + let boundSubchannelAddress: SubchannelAddress; + if (typeof boundAddress === 'string') { + boundSubchannelAddress = { + path: boundAddress, + }; + } else { + boundSubchannelAddress = { + host: boundAddress.address, + port: boundAddress.port, + }; + } + + const channelzRef = this.registerListenerToChannelz(boundSubchannelAddress); + if (this.channelzEnabled) { + this.listenerChildrenTracker.refChild(channelzRef); + } + this.http2Servers.set(http2Server, { + channelzRef: channelzRef, + sessions: new Set() + }); + boundPortObject.listeningServers.add(http2Server); + this.trace( + 'Successfully bound ' + + subchannelAddressToString(boundSubchannelAddress) + ); + resolve({ + port: 'port' in boundSubchannelAddress + ? boundSubchannelAddress.port + : 1 + }); + http2Server.removeListener('error', onError); + }); + }); + } + + private async bindManyPorts(addressList: SubchannelAddress[], boundPortObject: BoundPort): Promise { + if (addressList.length === 0) { + return { + count: 0, + port: 0, + errors: [] + }; + } + if (isTcpSubchannelAddress(addressList[0]) && addressList[0].port === 0) { + /* If binding to port 0, first try to bind the first address, then bind + * the rest of the address list to the specific port that it binds. */ + const firstAddressResult = await this.bindOneAddress(addressList[0], boundPortObject); + if (firstAddressResult.error) { + /* If the first address fails to bind, try the same operation starting + * from the second item in the list. */ + const restAddressResult = await this.bindManyPorts(addressList.slice(1), boundPortObject); + return { + ...restAddressResult, + errors: [firstAddressResult.error, ...restAddressResult.errors] + }; + } else { + const restAddresses = addressList.slice(1).map(address => isTcpSubchannelAddress(address) ? {host: address.host, port: firstAddressResult.port} : address) + const restAddressResult = await Promise.all(restAddresses.map(address => this.bindOneAddress(address, boundPortObject))); + const allResults = [firstAddressResult, ...restAddressResult]; + return { + count: allResults.filter(result => result.error === undefined).length, + port: firstAddressResult.port, + errors: allResults.filter(result => result.error).map(result => result.error!) + }; + } + } else { + const allResults = await Promise.all(addressList.map(address => this.bindOneAddress(address, boundPortObject))); + return { + count: allResults.filter(result => result.error === undefined).length, + port: allResults[0].port, + errors: allResults.filter(result => result.error).map(result => result.error!) + }; + } + } + + private async bindAddressList(addressList: SubchannelAddress[], boundPortObject: BoundPort): Promise { + let bindResult: BindResult; + try { + bindResult = await this.bindManyPorts(addressList, boundPortObject); + } catch (error) { + throw error; + } + if (bindResult.count > 0) { + if (bindResult.count < addressList.length) { + logging.log( + LogVerbosity.INFO, + `WARNING Only ${bindResult.count} addresses added out of total ${addressList.length} resolved` + ); + } + return bindResult.port; + } else { + const errorString = `No address added out of total ${addressList.length} resolved`; + logging.log(LogVerbosity.ERROR, errorString); + throw new Error(`${errorString} errors: [${bindResult.errors.join(',')}]`); + } + } + + private resolvePort(port: GrpcUri): Promise { + return new Promise((resolve, reject) => { + const resolverListener: ResolverListener = { + onSuccessfulResolution: ( + endpointList, + serviceConfig, + serviceConfigError + ) => { + // We only want one resolution result. Discard all future results + resolverListener.onSuccessfulResolution = () => {}; + const addressList = ([] as SubchannelAddress[]).concat( + ...endpointList.map(endpoint => endpoint.addresses) + ); + if (addressList.length === 0) { + reject( + new Error(`No addresses resolved for port ${port}`) + ); + return; + } + resolve(addressList); + }, + onError: error => { + reject(new Error(error.details)); + }, + }; + const resolver = createResolver(port, resolverListener, this.options); + resolver.updateResolution(); + }); + } + + private async bindPort(port: GrpcUri, boundPortObject: BoundPort): Promise { + const addressList = await this.resolvePort(port); + if (boundPortObject.cancelled) { + this.completeUnbind(boundPortObject); + throw new Error('bindAsync operation cancelled by unbind call'); + } + const portNumber = await this.bindAddressList(addressList, boundPortObject); + if (boundPortObject.cancelled) { + this.completeUnbind(boundPortObject); + throw new Error('bindAsync operation cancelled by unbind call'); + } + return portNumber; + } + + private normalizePort(port: string): GrpcUri { + + const initialPortUri = parseUri(port); + if (initialPortUri === null) { + throw new Error(`Could not parse port "${port}"`); + } + const portUri = mapUriDefaultScheme(initialPortUri); + if (portUri === null) { + throw new Error(`Could not get a default scheme for port "${port}"`); + } + return portUri; + } + bindAsync( port: string, creds: ServerCredentials, @@ -399,331 +711,162 @@ export class Server { throw new TypeError('callback must be a function'); } - const initialPortUri = parseUri(port); - if (initialPortUri === null) { - throw new Error(`Could not parse port "${port}"`); - } - const portUri = mapUriDefaultScheme(initialPortUri); - if (portUri === null) { - throw new Error(`Could not get a default scheme for port "${port}"`); - } + this.trace('bindAsync port=' + port); - const serverOptions: http2.ServerOptions = { - maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, - }; - if ('grpc-node.max_session_memory' in this.options) { - serverOptions.maxSessionMemory = - this.options['grpc-node.max_session_memory']; - } else { - /* By default, set a very large max session memory limit, to effectively - * disable enforcement of the limit. Some testing indicates that Node's - * behavior degrades badly when this limit is reached, so we solve that - * by disabling the check entirely. */ - serverOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; - } - if ('grpc.max_concurrent_streams' in this.options) { - serverOptions.settings = { - maxConcurrentStreams: this.options['grpc.max_concurrent_streams'], - }; - } + const portUri = this.normalizePort(port); const deferredCallback = (error: Error | null, port: number) => { process.nextTick(() => callback(error, port)); }; - const setupServer = (): http2.Http2Server | http2.Http2SecureServer => { - let http2Server: http2.Http2Server | http2.Http2SecureServer; - if (creds._isSecure()) { - const secureServerOptions = Object.assign( - serverOptions, - creds._getSettings()! - ); - secureServerOptions.enableTrace = - this.options['grpc-node.tls_enable_trace'] === 1; - http2Server = http2.createSecureServer(secureServerOptions); - http2Server.on('secureConnection', (socket: TLSSocket) => { - /* These errors need to be handled by the user of Http2SecureServer, - * according to https://github.com/nodejs/node/issues/35824 */ - socket.on('error', (e: Error) => { - this.trace( - 'An incoming TLS connection closed with error: ' + e.message - ); - }); - }); + /* First, if this port is already bound or that bind operation is in + * progress, use that result. */ + let boundPortObject = this.boundPorts.get(uriToString(portUri)); + if (boundPortObject) { + if (!creds._equals(boundPortObject.credentials)) { + deferredCallback(new Error(`${port} already bound with incompatible credentials`), 0); + return; + } + /* If that operation has previously been cancelled by an unbind call, + * uncancel it. */ + boundPortObject.cancelled = false; + if (boundPortObject.completionPromise) { + boundPortObject.completionPromise.then(portNum => callback(null, portNum), error => callback(error as Error, 0)); } else { - http2Server = http2.createServer(serverOptions); + deferredCallback(null, boundPortObject.portNumber); } - - http2Server.setTimeout(0, noop); - this._setupHandlers(http2Server); - return http2Server; + return; + } + boundPortObject = { + mapKey: uriToString(portUri), + originalUri: portUri, + completionPromise: null, + cancelled: false, + portNumber: 0, + credentials: creds, + listeningServers: new Set() }; - - const bindSpecificPort = ( - addressList: SubchannelAddress[], - portNum: number, - previousCount: number - ): Promise => { - if (addressList.length === 0) { - return Promise.resolve({ port: portNum, count: previousCount }); - } - return Promise.all( - addressList.map(address => { - this.trace( - 'Attempting to bind ' + subchannelAddressToString(address) - ); - let addr: SubchannelAddress; - if (isTcpSubchannelAddress(address)) { - addr = { - host: (address as TcpSubchannelAddress).host, - port: portNum, - }; - } else { - addr = address; - } - - const http2Server = setupServer(); - return new Promise((resolve, reject) => { - const onError = (err: Error) => { - this.trace( - 'Failed to bind ' + - subchannelAddressToString(address) + - ' with error ' + - err.message - ); - resolve(err); - }; - - http2Server.once('error', onError); - - http2Server.listen(addr, () => { - const boundAddress = http2Server.address()!; - let boundSubchannelAddress: SubchannelAddress; - if (typeof boundAddress === 'string') { - boundSubchannelAddress = { - path: boundAddress, - }; - } else { - boundSubchannelAddress = { - host: boundAddress.address, - port: boundAddress.port, - }; - } - - const channelzRef = registerChannelzSocket( - subchannelAddressToString(boundSubchannelAddress), - () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null, - }; - }, - this.channelzEnabled - ); - if (this.channelzEnabled) { - this.listenerChildrenTracker.refChild(channelzRef); - } - this.http2ServerList.push({ - server: http2Server, - channelzRef: channelzRef, - }); - this.trace( - 'Successfully bound ' + - subchannelAddressToString(boundSubchannelAddress) - ); - resolve( - 'port' in boundSubchannelAddress - ? boundSubchannelAddress.port - : portNum - ); - http2Server.removeListener('error', onError); - }); - }); - }) - ).then(results => { - let count = 0; - for (const result of results) { - if (typeof result === 'number') { - count += 1; - if (result !== portNum) { - throw new Error( - 'Invalid state: multiple port numbers added from single address' - ); - } - } - } - return { - port: portNum, - count: count + previousCount, + const splitPort = splitHostPort(portUri.path); + const completionPromise = this.bindPort(portUri, boundPortObject); + boundPortObject.completionPromise = completionPromise; + /* If the port number is 0, defer populating the map entry until after the + * bind operation completes and we have a specific port number. Otherwise, + * populate it immediately. */ + if (splitPort?.port === 0) { + completionPromise.then(portNum => { + const finalUri: GrpcUri = { + scheme: portUri.scheme, + authority: portUri.authority, + path: combineHostPort({host: splitPort.host, port: portNum}) }; + boundPortObject!.mapKey = uriToString(finalUri); + boundPortObject!.completionPromise = null; + boundPortObject!.portNumber = portNum; + this.boundPorts.set(boundPortObject!.mapKey, boundPortObject!); + callback(null, portNum); + }, error => { + callback(error, 0); + }) + } else { + this.boundPorts.set(boundPortObject.mapKey, boundPortObject); + completionPromise.then(portNum => { + boundPortObject!.completionPromise = null; + boundPortObject!.portNumber = portNum; + callback(null, portNum); + }, error => { + callback(error, 0); }); - }; + } + } - const bindWildcardPort = ( - addressList: SubchannelAddress[] - ): Promise => { - if (addressList.length === 0) { - return Promise.resolve({ port: 0, count: 0 }); + private closeServer(server: AnyHttp2Server, callback?: () => void) { + this.trace('Closing server with address ' + JSON.stringify(server.address())); + const serverInfo = this.http2Servers.get(server); + server.close(() => { + if (this.channelzEnabled && serverInfo) { + this.listenerChildrenTracker.unrefChild(serverInfo.channelzRef); + unregisterChannelzRef(serverInfo.channelzRef); } - const address = addressList[0]; - const http2Server = setupServer(); - return new Promise((resolve, reject) => { - const onError = (err: Error) => { - this.trace( - 'Failed to bind ' + - subchannelAddressToString(address) + - ' with error ' + - err.message - ); - resolve(bindWildcardPort(addressList.slice(1))); - }; + this.http2Servers.delete(server); + callback?.(); + }); - http2Server.once('error', onError); + } - http2Server.listen(address, () => { - const boundAddress = http2Server.address() as AddressInfo; - const boundSubchannelAddress: SubchannelAddress = { - host: boundAddress.address, - port: boundAddress.port, - }; - const channelzRef = registerChannelzSocket( - subchannelAddressToString(boundSubchannelAddress), - () => { - return { - localAddress: boundSubchannelAddress, - remoteAddress: null, - security: null, - remoteName: null, - streamsStarted: 0, - streamsSucceeded: 0, - streamsFailed: 0, - messagesSent: 0, - messagesReceived: 0, - keepAlivesSent: 0, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: null, - lastMessageSentTimestamp: null, - lastMessageReceivedTimestamp: null, - localFlowControlWindow: null, - remoteFlowControlWindow: null, - }; - }, - this.channelzEnabled - ); - if (this.channelzEnabled) { - this.listenerChildrenTracker.refChild(channelzRef); - } - this.http2ServerList.push({ - server: http2Server, - channelzRef: channelzRef, - }); - this.trace( - 'Successfully bound ' + - subchannelAddressToString(boundSubchannelAddress) - ); - resolve(bindSpecificPort(addressList.slice(1), boundAddress.port, 1)); - http2Server.removeListener('error', onError); - }); - }); + private closeSession(session: http2.ServerHttp2Session, callback?: () => void) { + this.trace('Closing session initiated by ' + session.socket?.remoteAddress); + const sessionInfo = this.sessions.get(session); + const closeCallback = () => { + if (this.channelzEnabled && sessionInfo) { + this.sessionChildrenTracker.unrefChild(sessionInfo.ref); + unregisterChannelzRef(sessionInfo.ref); + } + this.sessions.delete(session); + callback?.(); }; + if (session.closed) { + process.nextTick(closeCallback); + } else { + session.close(closeCallback); + } + } - const resolverListener: ResolverListener = { - onSuccessfulResolution: ( - endpointList, - serviceConfig, - serviceConfigError - ) => { - // We only want one resolution result. Discard all future results - resolverListener.onSuccessfulResolution = () => {}; - const addressList = ([] as SubchannelAddress[]).concat( - ...endpointList.map(endpoint => endpoint.addresses) - ); - if (addressList.length === 0) { - deferredCallback( - new Error(`No addresses resolved for port ${port}`), - 0 - ); - return; - } - let bindResultPromise: Promise; - if (isTcpSubchannelAddress(addressList[0])) { - if (addressList[0].port === 0) { - bindResultPromise = bindWildcardPort(addressList); - } else { - bindResultPromise = bindSpecificPort( - addressList, - addressList[0].port, - 0 - ); - } - } else { - // Use an arbitrary non-zero port for non-TCP addresses - bindResultPromise = bindSpecificPort(addressList, 1, 0); + private completeUnbind(boundPortObject: BoundPort) { + for (const server of boundPortObject.listeningServers) { + const serverInfo = this.http2Servers.get(server); + this.closeServer(server, () => { + boundPortObject.listeningServers.delete(server); + }); + if (serverInfo) { + for (const session of serverInfo.sessions) { + this.closeSession(session); } - bindResultPromise.then( - bindResult => { - if (bindResult.count === 0) { - const errorString = `No address added out of total ${addressList.length} resolved`; - logging.log(LogVerbosity.ERROR, errorString); - deferredCallback(new Error(errorString), 0); - } else { - if (bindResult.count < addressList.length) { - logging.log( - LogVerbosity.INFO, - `WARNING Only ${bindResult.count} addresses added out of total ${addressList.length} resolved` - ); - } - deferredCallback(null, bindResult.port); - } - }, - error => { - const errorString = `No address added out of total ${addressList.length} resolved`; - logging.log(LogVerbosity.ERROR, errorString); - deferredCallback(new Error(errorString), 0); - } - ); - }, - onError: error => { - deferredCallback(new Error(error.details), 0); - }, - }; + } + } + this.boundPorts.delete(boundPortObject.mapKey); + } - const resolver = createResolver(portUri, resolverListener, this.options); - resolver.updateResolution(); + /** + * Unbind a previously bound port, or cancel an in-progress bindAsync + * operation. If port 0 was bound, only the actual bound port can be + * unbound. For example, if bindAsync was called with "localhost:0" and the + * bound port result was 54321, it can be unbound as "localhost:54321". + * @param port + */ + unbind(port: string): void { + this.trace('unbind port=' + port); + const portUri = this.normalizePort(port); + const splitPort = splitHostPort(portUri.path); + if (splitPort?.port === 0) { + throw new Error('Cannot unbind port 0'); + } + const boundPortObject = this.boundPorts.get(uriToString(portUri)); + if (boundPortObject) { + this.trace('unbinding ' + boundPortObject.mapKey + ' originally bound as ' + uriToString(boundPortObject.originalUri)); + /* If the bind operation is pending, the cancelled flag will trigger + * the unbind operation later. */ + if (boundPortObject.completionPromise) { + boundPortObject.cancelled = true; + } else { + this.completeUnbind(boundPortObject); + } + } } forceShutdown(): void { + for (const boundPortObject of this.boundPorts.values()) { + boundPortObject.cancelled = true; + } + this.boundPorts.clear(); // Close the server if it is still running. - - for (const { server: http2Server, channelzRef: ref } of this - .http2ServerList) { - if (http2Server.listening) { - http2Server.close(() => { - if (this.channelzEnabled) { - this.listenerChildrenTracker.unrefChild(ref); - unregisterChannelzRef(ref); - } - }); - } + for (const server of this.http2Servers.keys()) { + this.closeServer(server); } // Always destroy any available sessions. It's possible that one or more // tryShutdown() calls are in progress. Don't wait on them to finish. this.sessions.forEach((channelzInfo, session) => { + this.closeSession(session); // Cast NGHTTP2_CANCEL to any because TypeScript doesn't seem to // recognize destroy(code) as a valid signature. // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -766,9 +909,9 @@ export class Server { @deprecate('Calling start() is no longer necessary. It can be safely omitted.') start(): void { if ( - this.http2ServerList.length === 0 || - this.http2ServerList.every( - ({ server: http2Server }) => http2Server.listening !== true + this.http2Servers.size === 0 || + [...this.http2Servers.keys()].every( + server => !server.listening ) ) { throw new Error('server must be bound in order to start'); @@ -797,26 +940,24 @@ export class Server { } } - for (const { server: http2Server, channelzRef: ref } of this - .http2ServerList) { - if (http2Server.listening) { - pendingChecks++; - http2Server.close(() => { - if (this.channelzEnabled) { - this.listenerChildrenTracker.unrefChild(ref); - unregisterChannelzRef(ref); - } - maybeCallback(); - }); - } + for (const server of this.http2Servers.keys()) { + pendingChecks++; + const serverString = this.http2Servers.get(server)!.channelzRef.name; + this.trace('Waiting for server ' + serverString + ' to close'); + this.closeServer(server, () => { + this.trace('Server ' + serverString + ' finished closing'); + maybeCallback(); + }); + } + for (const session of this.sessions.keys()) { + pendingChecks++; + const sessionString = session.socket?.remoteAddress; + this.trace('Waiting for session ' + sessionString + ' to close'); + this.closeSession(session, () => { + this.trace('Session ' + sessionString + ' finished closing'); + maybeCallback(); + }); } - - this.sessions.forEach((channelzInfo, session) => { - if (!session.closed) { - pendingChecks += 1; - session.close(maybeCallback); - } - }); if (pendingChecks === 0) { wrappedCallback(); } @@ -1077,6 +1218,7 @@ export class Server { lastMessageReceivedTimestamp: null, }; + this.http2Servers.get(http2Server)?.sessions.add(session); this.sessions.set(session, channelzSessionInfo); const clientAddress = session.socket.remoteAddress; if (this.channelzEnabled) { @@ -1164,6 +1306,7 @@ export class Server { if (keeapliveTimeTimer) { clearTimeout(keeapliveTimeTimer); } + this.http2Servers.get(http2Server)?.sessions.delete(session); this.sessions.delete(session); }); }); diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index cf49ced77..d9a2dbd80 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -120,6 +120,7 @@ export class Subchannel { this.backoffTimeout = new BackoffTimeout(() => { this.handleBackoffTimer(); }, backoffOptions); + this.backoffTimeout.unref(); this.subchannelAddressString = subchannelAddressToString(subchannelAddress); this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1; diff --git a/packages/grpc-js/src/uri-parser.ts b/packages/grpc-js/src/uri-parser.ts index 20c3d53b3..2b2efeca0 100644 --- a/packages/grpc-js/src/uri-parser.ts +++ b/packages/grpc-js/src/uri-parser.ts @@ -101,6 +101,19 @@ export function splitHostPort(path: string): HostPort | null { } } +export function combineHostPort(hostPort: HostPort): string { + if (hostPort.port === undefined) { + return hostPort.host; + } else { + // Only an IPv6 host should include a colon + if (hostPort.host.includes(':')) { + return `[${hostPort.host}]:${hostPort.port}`; + } else { + return `${hostPort.host}:${hostPort.port}`; + } + } +} + export function uriToString(uri: GrpcUri): string { let result = ''; if (uri.scheme !== undefined) { diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index d1b485ec3..56388a868 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -63,6 +63,13 @@ const cert = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.pem')); function noop(): void {} describe('Server', () => { + let server: Server; + beforeEach(() => { + server = new Server(); + }); + afterEach(() => { + server.forceShutdown(); + }); describe('constructor', () => { it('should work with no arguments', () => { assert.doesNotThrow(() => { @@ -140,6 +147,85 @@ describe('Server', () => { ); }, /callback must be a function/); }); + + it('succeeds when called with an already bound port', done => { + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { + assert.ifError(err); + server.bindAsync(`localhost:${port}`, ServerCredentials.createInsecure(), (err2, port2) => { + assert.ifError(err2); + assert.strictEqual(port, port2); + done(); + }); + }); + }); + + it('fails when called on a bound port with different credentials', done => { + const secureCreds = ServerCredentials.createSsl( + ca, + [{ private_key: key, cert_chain: cert }], + true + ); + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { + assert.ifError(err); + server.bindAsync(`localhost:${port}`, secureCreds, (err2, port2) => { + assert(err2 !== null); + assert.match(err2.message, /credentials/); + done(); + }) + }); + }) + }); + + describe('unbind', () => { + let client: grpc.Client | null = null; + beforeEach(() => { + client = null; + }); + afterEach(() => { + client?.close(); + }); + it('refuses to unbind port 0', done => { + assert.throws(() => { + server.unbind('localhost:0'); + }, /port 0/); + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { + assert.ifError(err); + assert.notStrictEqual(port, 0); + assert.throws(() => { + server.unbind('localhost:0'); + }, /port 0/); + done(); + }) + }); + + it('successfully unbinds a bound ephemeral port', done => { + server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { + client = new grpc.Client(`localhost:${port}`, grpc.credentials.createInsecure()); + client.makeUnaryRequest('/math.Math/Div', x => x, x => x, Buffer.from('abc'), (callError1, result) => { + assert(callError1); + // UNIMPLEMENTED means that the request reached the call handling code + assert.strictEqual(callError1.code, grpc.status.UNIMPLEMENTED); + server.unbind(`localhost:${port}`); + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + client!.makeUnaryRequest('/math.Math/Div', x => x, x => x, Buffer.from('abc'), {deadline: deadline}, (callError2, result) => { + assert(callError2); + // DEADLINE_EXCEEDED means that the server is unreachable + assert.strictEqual(callError2.code, grpc.status.DEADLINE_EXCEEDED); + done(); + }); + }); + }) + }); + + it('cancels a bindAsync in progress', done => { + server.bindAsync('localhost:50051', ServerCredentials.createInsecure(), (err, port) => { + assert(err); + assert.match(err.message, /cancelled by unbind/); + done(); + }); + server.unbind('localhost:50051'); + }); }); describe('start', () => { From 3a161874513653090b3f211e5cc70d1b813b2c08 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 14 Nov 2023 14:37:13 -0800 Subject: [PATCH 634/694] grpc-js: Implement server drain method --- packages/grpc-js/src/server.ts | 44 +++++++++++++++++++++ packages/grpc-js/test/test-server.ts | 58 ++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 8ff0d0f22..9bd71fd42 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -853,6 +853,50 @@ export class Server { } } + /** + * Gracefully close all connections associated with a previously bound port. + * After the grace time, forcefully close all remaining open connections. + * + * If port 0 was bound, only the actual bound port can be + * drained. For example, if bindAsync was called with "localhost:0" and the + * bound port result was 54321, it can be drained as "localhost:54321". + * @param port + * @param graceTimeMs + * @returns + */ + drain(port: string, graceTimeMs: number): void { + this.trace('drain port=' + port + ' graceTimeMs=' + graceTimeMs); + const portUri = this.normalizePort(port); + const splitPort = splitHostPort(portUri.path); + if (splitPort?.port === 0) { + throw new Error('Cannot drain port 0'); + } + const boundPortObject = this.boundPorts.get(uriToString(portUri)); + if (!boundPortObject) { + return; + } + const allSessions: Set = new Set(); + for (const http2Server of boundPortObject.listeningServers) { + const serverEntry = this.http2Servers.get(http2Server); + if (!serverEntry) { + continue; + } + for (const session of serverEntry.sessions) { + allSessions.add(session); + this.closeSession(session, () => { + allSessions.delete(session); + }); + } + } + /* After the grace time ends, send another goaway to all remaining sessions + * with the CANCEL code. */ + setTimeout(() => { + for (const session of allSessions) { + session.destroy(http2.constants.NGHTTP2_CANCEL as any); + } + }, graceTimeMs).unref?.(); + } + forceShutdown(): void { for (const boundPortObject of this.boundPorts.values()) { boundPortObject.cancelled = true; diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 56388a868..c497b8e27 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -228,6 +228,64 @@ describe('Server', () => { }); }); + describe.only('drain', () => { + let client: ServiceClient; + let portNumber: number; + const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); + const echoService = loadProtoFile(protoFile) + .EchoService as ServiceClientConstructor; + + const serviceImplementation = { + echo(call: ServerUnaryCall, callback: sendUnaryData) { + callback(null, call.request); + }, + echoBidiStream(call: ServerDuplexStream) { + call.on('data', data => { + call.write(data); + }); + call.on('end', () => { + call.end(); + }); + }, + }; + + beforeEach(done => { + server.addService(echoService.service, serviceImplementation); + + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + portNumber = port; + client = new echoService( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + server.start(); + done(); + } + ); + }); + + afterEach(done => { + client.close(); + server.tryShutdown(done); + }); + + it('Should cancel open calls after the grace period ends', done => { + const call = client.echoBidiStream(); + call.on('error', (error: ServiceError) => { + assert.strictEqual(error.code, grpc.status.CANCELLED); + done(); + }); + call.on('data', () => { + server.drain(`localhost:${portNumber!}`, 100); + }); + call.write({value: 'abc'}); + }); + }); + describe('start', () => { let server: Server; From 89a5cbbdf48ae9279d3ef82e39a8a6cbf01579bd Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Tue, 14 Nov 2023 22:28:08 -0500 Subject: [PATCH 635/694] chore(grpc-reflection): cleaned up package dependencies --- packages/grpc-reflection/package.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/grpc-reflection/package.json b/packages/grpc-reflection/package.json index 8f8ea8ef2..e89f71427 100644 --- a/packages/grpc-reflection/package.json +++ b/packages/grpc-reflection/package.json @@ -1,7 +1,9 @@ { "name": "@grpc/reflection", "version": "1.0.0", - "author": "Justin Timmons", + "author": { + "name": "Google Inc." + }, "description": "Reflection API service for use with gRPC-node", "repository": { "type": "git", @@ -29,15 +31,14 @@ "generate-types": "proto-loader-gen-types --longs String --enums String --bytes Array --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated grpc/reflection/v1/reflection.proto grpc/reflection/v1alpha/reflection.proto" }, "dependencies": { - "google-protobuf": "^3.21.2" + "google-protobuf": "^3.21.2", + "@grpc/proto-loader": "^0.7.10" }, "peerDependencies": { - "@grpc/grpc-js": ">=1.5.4", - "@grpc/proto-loader": ">=0.6.9" + "@grpc/grpc-js": "^1.8.21" }, "devDependencies": { - "@grpc/grpc-js": "^1.8.21", - "@grpc/proto-loader": "^0.7.10", + "@grpc/grpc-js": "file:../grpc-js", "@types/google-protobuf": "^3.15.7", "copyfiles": "^2.4.1", "typescript": "^5.2.2" From 2449abe3987ee46fdc10a944d0a67fb74f063f6c Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Wed, 15 Nov 2023 08:28:47 -0500 Subject: [PATCH 636/694] refactor(grpc-reflection): simplified request handling and file dependency logic --- .../src/implementations/reflection-v1.ts | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/packages/grpc-reflection/src/implementations/reflection-v1.ts b/packages/grpc-reflection/src/implementations/reflection-v1.ts index 2f43d99f4..ac676e7ec 100644 --- a/packages/grpc-reflection/src/implementations/reflection-v1.ts +++ b/packages/grpc-reflection/src/implementations/reflection-v1.ts @@ -26,7 +26,7 @@ export class ReflectionError extends Error { } } -/** Analyzes a gRPC server and exposes methods to reflect on it +/** Analyzes a gRPC package definition and exposes methods to reflect on it * * NOTE: the files returned by this service may not match the handwritten ones 1:1. * This is because proto-loader reorients files based on their package definition, @@ -37,7 +37,7 @@ export class ReflectionError extends Error { */ export class ReflectionV1Implementation { - /** The full list of proto files (including imported deps) that the gRPC server includes */ + /** The full list of proto files (including imported deps) that the gRPC package includes */ private readonly fileDescriptorSet = new FileDescriptorSet(); /** An index of proto files by file name (eg. 'sample.proto') */ @@ -172,32 +172,34 @@ export class ReflectionV1Implementation { handleServerReflectionRequest(message: ServerReflectionRequest): ServerReflectionResponse { const response: ServerReflectionResponse = { validHost: message.host, - originalRequest: message, - fileDescriptorResponse: undefined, - allExtensionNumbersResponse: undefined, - listServicesResponse: undefined, - errorResponse: undefined, + originalRequest: message }; try { - if (message.listServices !== undefined) { - response.listServicesResponse = this.listServices(message.listServices); - } else if (message.fileContainingSymbol !== undefined) { - response.fileDescriptorResponse = this.fileContainingSymbol(message.fileContainingSymbol); - } else if (message.fileByFilename !== undefined) { - response.fileDescriptorResponse = this.fileByFilename(message.fileByFilename); - } else if (message.fileContainingExtension !== undefined) { - response.fileDescriptorResponse = this.fileContainingExtension( - message.fileContainingExtension?.containingType || '', - message.fileContainingExtension?.extensionNumber || -1 - ); - } else if (message.allExtensionNumbersOfType) { - response.allExtensionNumbersResponse = this.allExtensionNumbersOfType(message.allExtensionNumbersOfType); - } else { - throw new ReflectionError( - grpc.status.UNIMPLEMENTED, - `Unimplemented method for request: ${message}`, - ); + switch(message.messageRequest) { + case 'listServices': + response.listServicesResponse = this.listServices(message.listServices || ''); + break; + case 'fileContainingSymbol': + response.fileDescriptorResponse = this.fileContainingSymbol(message.fileContainingSymbol || ''); + break; + case 'fileByFilename': + response.fileDescriptorResponse = this.fileByFilename(message.fileByFilename || ''); + break; + case 'fileContainingExtension': + response.fileDescriptorResponse = this.fileContainingExtension( + message.fileContainingExtension?.containingType || '', + message.fileContainingExtension?.extensionNumber || -1 + ); + break; + case 'allExtensionNumbersOfType': + response.allExtensionNumbersResponse = this.allExtensionNumbersOfType(message.allExtensionNumbersOfType || ''); + break; + default: + throw new ReflectionError( + grpc.status.UNIMPLEMENTED, + `Unimplemented method for request: ${message.messageRequest}`, + ); } } catch (e) { if (e instanceof ReflectionError) { @@ -315,20 +317,26 @@ export class ReflectionV1Implementation { }; } - private getFileDependencies( - file: FileDescriptorProto, - visited: Set = new Set(), - ): FileDescriptorProto[] { - const newVisited = visited.add(file); + private getFileDependencies(file: FileDescriptorProto): FileDescriptorProto[] { + const visited: Set = new Set(); + const toVisit: FileDescriptorProto[] = file.getDependencyList().map((dep) => this.fileNameIndex[dep]); - const directDeps = file.getDependencyList().map((dep) => this.fileNameIndex[dep]); - const transitiveDeps = directDeps - .filter((dep) => !newVisited.has(dep)) - .map((dep) => this.getFileDependencies(dep, newVisited)) - .flat(); + while (toVisit.length > 0) { + const current = toVisit.pop(); - const allDeps = [...directDeps, ...transitiveDeps]; + if (!current || visited.has(current)) { + continue; + } - return [...new Set(allDeps)]; + visited.add(current); + toVisit.push( + ...current.getDependencyList() + .map((dep) => this.fileNameIndex[dep]) + .filter((dep) => !visited.has(dep)) + ); + } + + return Array.from(visited); } + } From 87e1f798460f028dccf104d3e417baa7a44311a7 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Wed, 15 Nov 2023 20:49:03 -0500 Subject: [PATCH 637/694] docs(grpc-reflection): moved example to common directory and match grpc-go server --- examples/package.json | 1 + examples/reflection/server.js | 15 +++++++++++++++ packages/grpc-reflection/example/server.ts | 17 ----------------- 3 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 examples/reflection/server.js delete mode 100644 packages/grpc-reflection/example/server.ts diff --git a/examples/package.json b/examples/package.json index aee948c3f..6857aa5d9 100644 --- a/examples/package.json +++ b/examples/package.json @@ -7,6 +7,7 @@ "google-protobuf": "^3.0.0", "@grpc/grpc-js": "^1.8.0", "@grpc/grpc-js-xds": "^1.8.0", + "@grpc/reflection": "^1.0.0", "lodash": "^4.6.1", "minimist": "^1.2.0" } diff --git a/examples/reflection/server.js b/examples/reflection/server.js new file mode 100644 index 000000000..0fa2a8a76 --- /dev/null +++ b/examples/reflection/server.js @@ -0,0 +1,15 @@ +var path = require('path'); +var grpc = require('@grpc/grpc-js'); +var protoLoader = require('@grpc/proto-loader'); +var reflection = require('@grpc/reflection'); + +var PROTO_PATH = path.join(__dirname, '../protos/helloworld.proto'); + +var server = new grpc.Server(); +var packageDefinition = protoLoader.loadSync(PROTO_PATH); +var reflection = new reflection.ReflectionService(packageDefinition); +reflection.addToServer(server); + +server.bindAsync('localhost:5000', grpc.ServerCredentials.createInsecure(), () => { + server.start(); +}); diff --git a/packages/grpc-reflection/example/server.ts b/packages/grpc-reflection/example/server.ts deleted file mode 100644 index 00a3e1150..000000000 --- a/packages/grpc-reflection/example/server.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as path from 'path'; -import * as grpc from '@grpc/grpc-js'; -import * as protoLoader from '@grpc/proto-loader'; - -import { ReflectionService } from '../src'; - -const PROTO_PATH = path.join(__dirname, '../proto/sample/sample.proto'); -const INCLUDE_PATH = path.join(__dirname, '../proto/sample/vendor'); - -const server = new grpc.Server(); -const packageDefinition = protoLoader.loadSync(PROTO_PATH, { includeDirs: [INCLUDE_PATH] }); -const reflection = new ReflectionService(packageDefinition, { services: ['sample.SampleService'] }); -reflection.addToServer(server); - -server.bindAsync('localhost:5000', grpc.ServerCredentials.createInsecure(), () => { - server.start(); -}); From 7a15a1cccb989b6352322951392ae2463ae22494 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Wed, 15 Nov 2023 20:51:48 -0500 Subject: [PATCH 638/694] build(grpc-reflection): moved reflection tests to proper job and added test coverage --- gulpfile.ts | 4 ++-- package.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gulpfile.ts b/gulpfile.ts index 3bdd6bb68..2c2c2e374 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -37,11 +37,11 @@ const clean = gulp.series(jsCore.clean, protobuf.clean, jsXds.clean); const cleanAll = gulp.series(jsXds.cleanAll, jsCore.cleanAll, internalTest.cleanAll, protobuf.cleanAll); -const nativeTestOnly = gulp.parallel(healthCheck.test, reflection.test); +const nativeTestOnly = gulp.parallel(healthCheck.test); const nativeTest = gulp.series(build, nativeTestOnly); -const testOnly = gulp.parallel(jsCore.test, nativeTestOnly, protobuf.test, jsXds.test); +const testOnly = gulp.parallel(jsCore.test, nativeTestOnly, protobuf.test, jsXds.test, reflection.test); const test = gulp.series(build, testOnly, internalTest.test); diff --git a/package.json b/package.json index 70a15fbbf..a1fd3d59e 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,8 @@ "include": [ "packages/grpc-health-check/health.js", "packages/grpc-js/build/src/*", - "packages/proto-loader/build/src/*" + "packages/proto-loader/build/src/*", + "packages/grpc-reflection/build/src/*" ], "cache": true, "all": true From bc8f2ead26b4f102f53eac70017c5d186272959c Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Wed, 15 Nov 2023 20:52:17 -0500 Subject: [PATCH 639/694] docs(grpc-reflection): fixed link to example image --- packages/grpc-reflection/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-reflection/README.md b/packages/grpc-reflection/README.md index cb2501877..47d790f5a 100644 --- a/packages/grpc-reflection/README.md +++ b/packages/grpc-reflection/README.md @@ -6,7 +6,7 @@ gRPC reflection API service for use with gRPC-node. This package provides an implementation of the [gRPC Server Reflection Protocol](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md) service which can be added to an existing gRPC server. Adding this service to your server will allow clients [such as postman](https://blog.postman.com/postman-now-supports-grpc/) to dynamically load the API specification from your running application rather than needing to pass around and load proto files manually. -![example of reflection working with postman](https://gitlab.com/jtimmons/nestjs-grpc-reflection-module/-/raw/master/images/example.gif) +![example of reflection working with postman](./images/example.gif) ## Installation From 8843706ec7a42efb2a083e565f661b667dbbb589 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 16 Nov 2023 10:15:48 -0800 Subject: [PATCH 640/694] grpc-js: Make pick_first use exitIdle --- packages/grpc-js/package.json | 2 +- .../grpc-js/src/load-balancer-pick-first.ts | 39 +++++++++++-------- packages/grpc-js/test/test-pick-first.ts | 28 +++++++++++++ 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index be29ff870..4c408873e 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.10", + "version": "1.9.11", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 5e8361ace..214f92e22 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -186,6 +186,8 @@ export class PickFirstLoadBalancer implements LoadBalancer { */ private lastError: string | null = null; + private latestAddressList: SubchannelAddress[] | null = null; + /** * Load balancer that attempts to connect to each backend in the address list * in order, and picks the first one that connects, using it for every @@ -404,19 +406,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.requestedResolutionSinceLastUpdate = false; } - updateAddressList( - addressList: SubchannelAddress[], - lbConfig: LoadBalancingConfig - ): void { - if (!(lbConfig instanceof PickFirstLoadBalancingConfig)) { - return; - } - /* Previously, an update would be discarded if it was identical to the - * previous update, to minimize churn. Now the DNS resolver is - * rate-limited, so that is less of a concern. */ - if (lbConfig.getShuffleAddressList()) { - addressList = shuffled(addressList); - } + private connectToAddressList(addressList: SubchannelAddress[]) { const newChildrenList = addressList.map(address => ({ subchannel: this.channelControlHelper.createSubchannel(address, {}), hasReportedTransientFailure: false, @@ -449,10 +439,27 @@ export class PickFirstLoadBalancer implements LoadBalancer { this.calculateAndReportNewState(); } + updateAddressList( + addressList: SubchannelAddress[], + lbConfig: LoadBalancingConfig + ): void { + if (!(lbConfig instanceof PickFirstLoadBalancingConfig)) { + return; + } + /* Previously, an update would be discarded if it was identical to the + * previous update, to minimize churn. Now the DNS resolver is + * rate-limited, so that is less of a concern. */ + if (lbConfig.getShuffleAddressList()) { + addressList = shuffled(addressList); + } + this.latestAddressList = addressList; + this.connectToAddressList(addressList); + } + exitIdle() { - /* The pick_first LB policy is only in the IDLE state if it has no - * addresses to try to connect to and it has no picked subchannel. - * In that case, there is no meaningful action that can be taken here. */ + if (this.currentState === ConnectivityState.IDLE && this.latestAddressList) { + this.connectToAddressList(this.latestAddressList); + } } resetBackoff() { diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index 075448882..30455fcb8 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -550,6 +550,34 @@ describe('pick_first load balancing policy', () => { }); }); }); + it('Should reconnect to the same address list if exitIdle is called', done => { + const currentStartState = ConnectivityState.READY; + const channelControlHelper = createChildChannelControlHelper( + baseChannelControlHelper, + { + createSubchannel: (subchannelAddress, subchannelArgs) => { + const subchannel = new MockSubchannel( + subchannelAddressToString(subchannelAddress), + currentStartState + ); + subchannels.push(subchannel); + return subchannel; + }, + updateState: updateStateCallBackForExpectedStateSequence( + [ConnectivityState.READY, ConnectivityState.IDLE, ConnectivityState.READY], + done + ), + } + ); + const pickFirst = new PickFirstLoadBalancer(channelControlHelper); + pickFirst.updateAddressList([{ host: 'localhost', port: 1 }], config); + process.nextTick(() => { + subchannels[0].transitionToState(ConnectivityState.IDLE); + process.nextTick(() => { + pickFirst.exitIdle(); + }); + }); + }); describe('Address list randomization', () => { const shuffleConfig = new PickFirstLoadBalancingConfig(true); it('Should pick different subchannels after multiple updates', done => { From 736d6df80b0065d5048b7bcadeb44317898a08cb Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 16 Nov 2023 10:19:26 -0800 Subject: [PATCH 641/694] grpc-js: Return the result from the UDS resolver only once --- packages/grpc-js/src/resolver-uds.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/src/resolver-uds.ts b/packages/grpc-js/src/resolver-uds.ts index 24095ec29..2110e8564 100644 --- a/packages/grpc-js/src/resolver-uds.ts +++ b/packages/grpc-js/src/resolver-uds.ts @@ -21,6 +21,7 @@ import { ChannelOptions } from './channel-options'; class UdsResolver implements Resolver { private addresses: SubchannelAddress[] = []; + private hasReturnedResult = false; constructor( target: GrpcUri, private listener: ResolverListener, @@ -35,14 +36,17 @@ class UdsResolver implements Resolver { this.addresses = [{ path }]; } updateResolution(): void { - process.nextTick( - this.listener.onSuccessfulResolution, - this.addresses, - null, - null, - null, - {} - ); + if (!this.hasReturnedResult) { + this.hasReturnedResult = true; + process.nextTick( + this.listener.onSuccessfulResolution, + this.addresses, + null, + null, + null, + {} + ); + } } destroy() { From 234f7f0a0cff6e00711bc36819c0d81fe768c7cf Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Sat, 18 Nov 2023 19:13:59 -0500 Subject: [PATCH 642/694] refactor(grpc-reflection): switch to using protobufjs library for message encoding/decoding --- packages/grpc-reflection/package.json | 5 +- .../common/protobuf-visitor.ts | 93 +++++++------- .../src/implementations/reflection-v1.ts | 113 ++++++++---------- .../test/test-reflection-v1-implementation.ts | 46 +++---- 4 files changed, 120 insertions(+), 137 deletions(-) diff --git a/packages/grpc-reflection/package.json b/packages/grpc-reflection/package.json index e89f71427..755ae5083 100644 --- a/packages/grpc-reflection/package.json +++ b/packages/grpc-reflection/package.json @@ -31,15 +31,14 @@ "generate-types": "proto-loader-gen-types --longs String --enums String --bytes Array --defaults --oneofs --includeComments --includeDirs proto/ -O src/generated grpc/reflection/v1/reflection.proto grpc/reflection/v1alpha/reflection.proto" }, "dependencies": { - "google-protobuf": "^3.21.2", - "@grpc/proto-loader": "^0.7.10" + "@grpc/proto-loader": "^0.7.10", + "protobufjs": "^7.2.5" }, "peerDependencies": { "@grpc/grpc-js": "^1.8.21" }, "devDependencies": { "@grpc/grpc-js": "file:../grpc-js", - "@types/google-protobuf": "^3.15.7", "copyfiles": "^2.4.1", "typescript": "^5.2.2" } diff --git a/packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts b/packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts index 556a8bf3e..451a2c0ea 100644 --- a/packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts +++ b/packages/grpc-reflection/src/implementations/common/protobuf-visitor.ts @@ -1,24 +1,24 @@ import { - DescriptorProto, - EnumDescriptorProto, - EnumValueDescriptorProto, - FieldDescriptorProto, - FileDescriptorProto, - MethodDescriptorProto, - OneofDescriptorProto, - ServiceDescriptorProto, -} from 'google-protobuf/google/protobuf/descriptor_pb'; + IDescriptorProto, + IEnumDescriptorProto, + IEnumValueDescriptorProto, + IFieldDescriptorProto, + IFileDescriptorProto, + IMethodDescriptorProto, + IOneofDescriptorProto, + IServiceDescriptorProto, +} from 'protobufjs/ext/descriptor'; /** A set of functions for operating on protobuf objects as we visit them in a traversal */ interface Visitor { - field?: (fqn: string, file: FileDescriptorProto, field: FieldDescriptorProto) => void; - extension?: (fqn: string, file: FileDescriptorProto, extension: FieldDescriptorProto) => void; - oneOf?: (fqn: string, file: FileDescriptorProto, decl: OneofDescriptorProto) => void; - message?: (fqn: string, file: FileDescriptorProto, msg: DescriptorProto) => void; - enum?: (fqn: string, file: FileDescriptorProto, msg: EnumDescriptorProto) => void; - enumValue?: (fqn: string, file: FileDescriptorProto, msg: EnumValueDescriptorProto) => void; - service?: (fqn: string, file: FileDescriptorProto, msg: ServiceDescriptorProto) => void; - method?: (fqn: string, file: FileDescriptorProto, method: MethodDescriptorProto) => void; + field?: (fqn: string, file: IFileDescriptorProto, field: IFieldDescriptorProto) => void; + extension?: (fqn: string, file: IFileDescriptorProto, extension: IFieldDescriptorProto) => void; + oneOf?: (fqn: string, file: IFileDescriptorProto, decl: IOneofDescriptorProto) => void; + message?: (fqn: string, file: IFileDescriptorProto, msg: IDescriptorProto) => void; + enum?: (fqn: string, file: IFileDescriptorProto, msg: IEnumDescriptorProto) => void; + enumValue?: (fqn: string, file: IFileDescriptorProto, msg: IEnumValueDescriptorProto) => void; + service?: (fqn: string, file: IFileDescriptorProto, msg: IServiceDescriptorProto) => void; + method?: (fqn: string, file: IFileDescriptorProto, method: IMethodDescriptorProto) => void; } /** Visit each node in a protobuf file and perform an operation on it @@ -29,9 +29,9 @@ interface Visitor { * * @see Visitor for the interface to interact with the nodes */ -export const visit = (file: FileDescriptorProto, visitor: Visitor): void => { - const processField = (prefix: string, file: FileDescriptorProto, field: FieldDescriptorProto) => { - const fqn = `${prefix}.${field.getName()}`; +export const visit = (file: IFileDescriptorProto, visitor: Visitor): void => { + const processField = (prefix: string, file: IFileDescriptorProto, field: IFieldDescriptorProto) => { + const fqn = `${prefix}.${field.name}`; if (visitor.field) { visitor.field(fqn, file, field); } @@ -39,72 +39,71 @@ export const visit = (file: FileDescriptorProto, visitor: Visitor): void => { const processExtension = ( prefix: string, - file: FileDescriptorProto, - ext: FieldDescriptorProto, + file: IFileDescriptorProto, + ext: IFieldDescriptorProto, ) => { - const fqn = `${prefix}.${ext.getName()}`; + const fqn = `${prefix}.${ext.name}`; if (visitor.extension) { visitor.extension(fqn, file, ext); } }; - const processOneOf = (prefix: string, file: FileDescriptorProto, decl: OneofDescriptorProto) => { - const fqn = `${prefix}.${decl.getName()}`; + const processOneOf = (prefix: string, file: IFileDescriptorProto, decl: IOneofDescriptorProto) => { + const fqn = `${prefix}.${decl.name}`; if (visitor.oneOf) { visitor.oneOf(fqn, file, decl); } }; - const processEnum = (prefix: string, file: FileDescriptorProto, decl: EnumDescriptorProto) => { - const fqn = `${prefix}.${decl.getName()}`; + const processEnum = (prefix: string, file: IFileDescriptorProto, decl: IEnumDescriptorProto) => { + const fqn = `${prefix}.${decl.name}`; if (visitor.enum) { visitor.enum(fqn, file, decl); } - decl.getValueList().forEach((value) => { - const valueFqn = `${fqn}.${value.getName()}`; + decl.value?.forEach((value) => { + const valueFqn = `${fqn}.${value.name}`; if (visitor.enumValue) { visitor.enumValue(valueFqn, file, value); } }); }; - const processMessage = (prefix: string, file: FileDescriptorProto, msg: DescriptorProto) => { - const fqn = `${prefix}.${msg.getName()}`; + const processMessage = (prefix: string, file: IFileDescriptorProto, msg: IDescriptorProto) => { + const fqn = `${prefix}.${msg.name}`; if (visitor.message) { visitor.message(fqn, file, msg); } - msg.getNestedTypeList().forEach((type) => processMessage(fqn, file, type)); - msg.getEnumTypeList().forEach((type) => processEnum(fqn, file, type)); - msg.getFieldList().forEach((field) => processField(fqn, file, field)); - msg.getOneofDeclList().forEach((decl) => processOneOf(fqn, file, decl)); - msg.getExtensionList().forEach((ext) => processExtension(fqn, file, ext)); + msg.nestedType?.forEach((type) => processMessage(fqn, file, type)); + msg.enumType?.forEach((type) => processEnum(fqn, file, type)); + msg.field?.forEach((field) => processField(fqn, file, field)); + msg.oneofDecl?.forEach((decl) => processOneOf(fqn, file, decl)); + msg.extension?.forEach((ext) => processExtension(fqn, file, ext)); }; const processService = ( prefix: string, - file: FileDescriptorProto, - service: ServiceDescriptorProto, + file: IFileDescriptorProto, + service: IServiceDescriptorProto, ) => { - const fqn = `${prefix}.${service.getName()}`; + const fqn = `${prefix}.${service.name}`; if (visitor.service) { visitor.service(fqn, file, service); } - service.getMethodList().forEach((method) => { - const methodFqn = `${fqn}.${method.getName()}`; + service.method?.forEach((method) => { + const methodFqn = `${fqn}.${method.name}`; if (visitor.method) { visitor.method(methodFqn, file, method); } }); }; - const packageName = file.getPackage() || ''; - file.getEnumTypeList().forEach((type) => processEnum(packageName, file, type)); - file.getMessageTypeList().forEach((type) => processMessage(packageName, file, type)); - file.getServiceList().forEach((service) => processService(packageName, file, service)); - - file.getExtensionList().forEach((ext) => processExtension(packageName, file, ext)); + const packageName = file.package || ''; + file.enumType?.forEach((type) => processEnum(packageName, file, type)); + file.messageType?.forEach((type) => processMessage(packageName, file, type)); + file.service?.forEach((service) => processService(packageName, file, service)); + file.extension?.forEach((ext) => processExtension(packageName, file, ext)); }; diff --git a/packages/grpc-reflection/src/implementations/reflection-v1.ts b/packages/grpc-reflection/src/implementations/reflection-v1.ts index ac676e7ec..cf4b62818 100644 --- a/packages/grpc-reflection/src/implementations/reflection-v1.ts +++ b/packages/grpc-reflection/src/implementations/reflection-v1.ts @@ -1,8 +1,5 @@ import * as path from 'path'; -import { - FileDescriptorProto, - FileDescriptorSet, -} from 'google-protobuf/google/protobuf/descriptor_pb'; +import { FileDescriptorProto, IFileDescriptorProto } from 'protobufjs/ext/descriptor'; import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; @@ -37,21 +34,21 @@ export class ReflectionError extends Error { */ export class ReflectionV1Implementation { - /** The full list of proto files (including imported deps) that the gRPC package includes */ - private readonly fileDescriptorSet = new FileDescriptorSet(); - /** An index of proto files by file name (eg. 'sample.proto') */ - private readonly fileNameIndex: Record = {}; + private readonly files: Record = {}; + + /** A graph of file dependencies */ + private readonly fileDependencies = new Map(); /** An index of proto files by type extension relationship * * extensionIndex[.][] contains a reference to the file containing an * extension for the type "." and field number "" */ - private readonly extensionIndex: Record> = {}; + private readonly extensions: Record> = {}; /** An index of fully qualified symbol names (eg. 'sample.Message') to the files that contain them */ - private readonly symbolMap: Record = {}; + private readonly symbols: Record = {}; /** Options that the user provided for this service */ private readonly options?: ReflectionServerOptions; @@ -60,29 +57,20 @@ export class ReflectionV1Implementation { this.options = options; Object.values(root).forEach(({ fileDescriptorProtos }) => { - // Add file descriptors to the FileDescriptorSet. - // We use the Array check here because a ServiceDefinition could have a method named the same thing - if (Array.isArray(fileDescriptorProtos)) { + if (Array.isArray(fileDescriptorProtos)) { // we use an array check to narrow the type fileDescriptorProtos.forEach((bin) => { - const proto = FileDescriptorProto.deserializeBinary(bin); - const isFileInSet = this.fileDescriptorSet - .getFileList() - .map((f) => f.getName()) - .includes(proto.getName()); - if (!isFileInSet) { - this.fileDescriptorSet.addFile(proto); + const proto = FileDescriptorProto.decode(bin) as IFileDescriptorProto; + + if (proto.name && !this.files[proto.name]) { + this.files[proto.name] = proto; } }); } }); - this.fileNameIndex = Object.fromEntries( - this.fileDescriptorSet.getFileList().map((f) => [f.getName(), f]), - ); - // Pass 1: Index Values - const index = (fqn: string, file: FileDescriptorProto) => (this.symbolMap[fqn] = file); - this.fileDescriptorSet.getFileList().forEach((file) => + const index = (fqn: string, file: IFileDescriptorProto) => (this.symbols[fqn] = file); + Object.values(this.files).forEach((file) => visit(file, { field: index, oneOf: index, @@ -94,36 +82,37 @@ export class ReflectionV1Implementation { extension: (fqn, file, ext) => { index(fqn, file); - const extendeeName = ext.getExtendee() || ''; - this.extensionIndex[extendeeName] = { - ...(this.extensionIndex[extendeeName] || {}), - [ext.getNumber() || -1]: file, + const extendeeName = ext.extendee || ''; + this.extensions[extendeeName] = { + ...(this.extensions[extendeeName] || {}), + [ext.number || -1]: file, }; }, }), ); // Pass 2: Link References To Values - const addReference = (ref: string, sourceFile: FileDescriptorProto, pkgScope: string) => { + // NOTE: this should be unnecessary after https://github.com/grpc/grpc-node/issues/2595 is resolved + const addReference = (ref: string, sourceFile: IFileDescriptorProto, pkgScope: string) => { if (!ref) { return; // nothing to do } - let referencedFile: FileDescriptorProto | null = null; + let referencedFile: IFileDescriptorProto | null = null; if (ref.startsWith('.')) { // absolute reference -- just remove the leading '.' and use the ref directly - referencedFile = this.symbolMap[ref.replace(/^\./, '')]; + referencedFile = this.symbols[ref.replace(/^\./, '')]; } else { // relative reference -- need to seek upwards up the current package scope until we find it let pkg = pkgScope; while (pkg && !referencedFile) { - referencedFile = this.symbolMap[`${pkg}.${ref}`]; + referencedFile = this.symbols[`${pkg}.${ref}`]; pkg = scope(pkg); } // if we didn't find anything then try just a FQN lookup if (!referencedFile) { - referencedFile = this.symbolMap[ref]; + referencedFile = this.symbols[ref]; } } @@ -132,19 +121,19 @@ export class ReflectionV1Implementation { return; } - const fname = referencedFile.getName(); - if (referencedFile !== sourceFile && fname) { - sourceFile.addDependency(fname); + if (referencedFile !== sourceFile) { + const existingDeps = this.fileDependencies.get(sourceFile) || []; + this.fileDependencies.set(sourceFile, [referencedFile, ...existingDeps]); } }; - this.fileDescriptorSet.getFileList().forEach((file) => + Object.values(this.files).forEach((file) => visit(file, { - field: (fqn, file, field) => addReference(field.getTypeName() || '', file, scope(fqn)), - extension: (fqn, file, ext) => addReference(ext.getTypeName() || '', file, scope(fqn)), + field: (fqn, file, field) => addReference(field.typeName || '', file, scope(fqn)), + extension: (fqn, file, ext) => addReference(ext.typeName || '', file, scope(fqn)), method: (fqn, file, method) => { - addReference(method.getInputType() || '', file, scope(fqn)); - addReference(method.getOutputType() || '', file, scope(fqn)); + addReference(method.inputType || '', file, scope(fqn)); + addReference(method.outputType || '', file, scope(fqn)); }, }), ); @@ -228,15 +217,15 @@ export class ReflectionV1Implementation { * @returns full-qualified service names (eg. 'sample.SampleService') */ listServices(listServices: string): ListServiceResponse__Output { - const services = this.fileDescriptorSet - .getFileList() + const services = Object.values(this.files) .map((file) => - file.getServiceList().map((service) => `${file.getPackage()}.${service.getName()}`), + file.service?.map((service) => `${file.package}.${service.name}`), ) - .flat(); + .flat() + .filter((service): service is string => !!service); const whitelist = new Set(this.options?.services ?? undefined); - const exposedServices = this.options?.services ? + const exposedServices = whitelist.size ? services.filter(service => whitelist.has(service)) : services; @@ -251,7 +240,7 @@ export class ReflectionV1Implementation { * @returns descriptors of the file which contains this symbol and its imports */ fileContainingSymbol(symbol: string): FileDescriptorResponse__Output { - const file = this.symbolMap[symbol]; + const file = this.symbols[symbol]; if (!file) { throw new ReflectionError(grpc.status.NOT_FOUND, `Symbol not found: ${symbol}`); @@ -260,7 +249,7 @@ export class ReflectionV1Implementation { const deps = this.getFileDependencies(file); return { - fileDescriptorProto: [file, ...deps].map((proto) => proto.serializeBinary()), + fileDescriptorProto: [file, ...deps].map((proto) => FileDescriptorProto.encode(proto).finish()), }; } @@ -269,7 +258,7 @@ export class ReflectionV1Implementation { * @returns descriptors of the file which contains this symbol and its imports */ fileByFilename(filename: string): FileDescriptorResponse__Output { - const file = this.fileNameIndex[filename]; + const file = this.files[filename]; if (!file) { throw new ReflectionError(grpc.status.NOT_FOUND, `Proto file not found: ${filename}`); @@ -278,7 +267,7 @@ export class ReflectionV1Implementation { const deps = this.getFileDependencies(file); return { - fileDescriptorProto: [file, ...deps].map((f) => f.serializeBinary()), + fileDescriptorProto: [file, ...deps].map((f) => FileDescriptorProto.encode(f).finish()), }; } @@ -287,7 +276,7 @@ export class ReflectionV1Implementation { * @returns descriptors of the file which contains this symbol and its imports */ fileContainingExtension(symbol: string, field: number): FileDescriptorResponse__Output { - const extensionsByFieldNumber = this.extensionIndex[symbol] || {}; + const extensionsByFieldNumber = this.extensions[symbol] || {}; const file = extensionsByFieldNumber[field]; if (!file) { @@ -300,16 +289,16 @@ export class ReflectionV1Implementation { const deps = this.getFileDependencies(file); return { - fileDescriptorProto: [file, ...deps].map((f) => f.serializeBinary()), + fileDescriptorProto: [file, ...deps].map((f) => FileDescriptorProto.encode(f).finish()), }; } allExtensionNumbersOfType(symbol: string): ExtensionNumberResponse__Output { - if (!(symbol in this.extensionIndex)) { + if (!(symbol in this.extensions)) { throw new ReflectionError(grpc.status.NOT_FOUND, `Extensions not found for symbol ${symbol}`); } - const fieldNumbers = Object.keys(this.extensionIndex[symbol]).map((key) => Number(key)); + const fieldNumbers = Object.keys(this.extensions[symbol]).map((key) => Number(key)); return { baseTypeName: symbol, @@ -317,9 +306,9 @@ export class ReflectionV1Implementation { }; } - private getFileDependencies(file: FileDescriptorProto): FileDescriptorProto[] { - const visited: Set = new Set(); - const toVisit: FileDescriptorProto[] = file.getDependencyList().map((dep) => this.fileNameIndex[dep]); + private getFileDependencies(file: IFileDescriptorProto): IFileDescriptorProto[] { + const visited: Set = new Set(); + const toVisit: IFileDescriptorProto[] = this.fileDependencies.get(file) || []; while (toVisit.length > 0) { const current = toVisit.pop(); @@ -329,11 +318,7 @@ export class ReflectionV1Implementation { } visited.add(current); - toVisit.push( - ...current.getDependencyList() - .map((dep) => this.fileNameIndex[dep]) - .filter((dep) => !visited.has(dep)) - ); + toVisit.push(...this.fileDependencies.get(current)?.filter((dep) => !visited.has(dep)) || []); } return Array.from(visited); diff --git a/packages/grpc-reflection/test/test-reflection-v1-implementation.ts b/packages/grpc-reflection/test/test-reflection-v1-implementation.ts index 81613ac69..552160f74 100644 --- a/packages/grpc-reflection/test/test-reflection-v1-implementation.ts +++ b/packages/grpc-reflection/test/test-reflection-v1-implementation.ts @@ -1,6 +1,6 @@ import * as assert from 'assert'; import * as path from 'path'; -import { FileDescriptorProto } from 'google-protobuf/google/protobuf/descriptor_pb'; +import { FileDescriptorProto, IFileDescriptorProto } from 'protobufjs/ext/descriptor'; import * as protoLoader from '@grpc/proto-loader'; import { ReflectionV1Implementation } from '../src/implementations/reflection-v1'; @@ -40,9 +40,9 @@ describe('GrpcReflectionService', () => { it('finds files with transitive dependencies', () => { const descriptors = reflectionService .fileByFilename('sample.proto') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert.deepEqual( new Set(names), new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']) @@ -52,27 +52,27 @@ describe('GrpcReflectionService', () => { it('finds files with fewer transitive dependencies', () => { const descriptors = reflectionService .fileByFilename('vendor.proto') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert.deepEqual(new Set(names), new Set(['vendor.proto', 'vendor_dependency.proto'])); }); it('finds files with no transitive dependencies', () => { const descriptors = reflectionService .fileByFilename('vendor_dependency.proto') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); assert.equal(descriptors.length, 1); - assert.equal(descriptors[0].getName(), 'vendor_dependency.proto'); + assert.equal(descriptors[0].name, 'vendor_dependency.proto'); }); it('merges files based on package name', () => { const descriptors = reflectionService .fileByFilename('vendor.proto') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert(!names.includes('common.proto')); // file merged into vendor.proto }); @@ -88,9 +88,9 @@ describe('GrpcReflectionService', () => { it('finds symbols and returns transitive file dependencies', () => { const descriptors = reflectionService .fileContainingSymbol('sample.HelloRequest') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert.deepEqual( new Set(names), new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']), @@ -100,27 +100,27 @@ describe('GrpcReflectionService', () => { it('finds imported message types', () => { const descriptors = reflectionService .fileContainingSymbol('vendor.CommonMessage') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert.deepEqual(new Set(names), new Set(['vendor.proto', 'vendor_dependency.proto'])); }); it('finds transitively imported message types', () => { const descriptors = reflectionService .fileContainingSymbol('vendor.dependency.DependentMessage') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); assert.equal(descriptors.length, 1); - assert.equal(descriptors[0].getName(), 'vendor_dependency.proto'); + assert.equal(descriptors[0].name, 'vendor_dependency.proto'); }); it('finds nested message types', () => { const descriptors = reflectionService .fileContainingSymbol('sample.HelloRequest.HelloNested') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert.deepEqual( new Set(names), new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']), @@ -130,9 +130,9 @@ describe('GrpcReflectionService', () => { it('merges files based on package name', () => { const descriptors = reflectionService .fileContainingSymbol('vendor.CommonMessage') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert(!names.includes('common.proto')); // file merged into vendor.proto }); @@ -146,9 +146,9 @@ describe('GrpcReflectionService', () => { it('resolves references to method types', () => { const descriptors = reflectionService .fileContainingSymbol('sample.SampleService.Hello2') - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert.deepEqual( new Set(names), new Set(['sample.proto', 'vendor.proto', 'vendor_dependency.proto']), @@ -160,9 +160,9 @@ describe('GrpcReflectionService', () => { it('finds extensions and returns transitive file dependencies', () => { const descriptors = reflectionService .fileContainingExtension('.vendor.CommonMessage', 101) - .fileDescriptorProto.map(FileDescriptorProto.deserializeBinary); + .fileDescriptorProto.map(f => FileDescriptorProto.decode(f) as IFileDescriptorProto); - const names = descriptors.map((desc) => desc.getName()); + const names = descriptors.map((desc) => desc.name); assert.deepEqual(new Set(names), new Set(['vendor.proto', 'vendor_dependency.proto'])); }); From c53656d67bb3ba8a83fee5539dd34ea55b98d981 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Sat, 18 Nov 2023 19:44:48 -0500 Subject: [PATCH 643/694] refactor(grpc-reflection): precompute service list and file encodings --- .../src/implementations/reflection-v1.ts | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/grpc-reflection/src/implementations/reflection-v1.ts b/packages/grpc-reflection/src/implementations/reflection-v1.ts index cf4b62818..913f93719 100644 --- a/packages/grpc-reflection/src/implementations/reflection-v1.ts +++ b/packages/grpc-reflection/src/implementations/reflection-v1.ts @@ -1,5 +1,9 @@ import * as path from 'path'; -import { FileDescriptorProto, IFileDescriptorProto } from 'protobufjs/ext/descriptor'; +import { + FileDescriptorProto, + IFileDescriptorProto, + IServiceDescriptorProto +} from 'protobufjs/ext/descriptor'; import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; @@ -40,6 +44,9 @@ export class ReflectionV1Implementation { /** A graph of file dependencies */ private readonly fileDependencies = new Map(); + /** Pre-computed encoded-versions of each file */ + private readonly fileEncodings = new Map(); + /** An index of proto files by type extension relationship * * extensionIndex[.][] contains a reference to the file containing an @@ -50,12 +57,11 @@ export class ReflectionV1Implementation { /** An index of fully qualified symbol names (eg. 'sample.Message') to the files that contain them */ private readonly symbols: Record = {}; - /** Options that the user provided for this service */ - private readonly options?: ReflectionServerOptions; + /** An index of the services in the analyzed package(s) */ + private readonly services: Record = {}; - constructor(root: protoLoader.PackageDefinition, options?: ReflectionServerOptions) { - this.options = options; + constructor(root: protoLoader.PackageDefinition, options?: ReflectionServerOptions) { Object.values(root).forEach(({ fileDescriptorProtos }) => { if (Array.isArray(fileDescriptorProtos)) { // we use an array check to narrow the type fileDescriptorProtos.forEach((bin) => { @@ -69,16 +75,23 @@ export class ReflectionV1Implementation { }); // Pass 1: Index Values + const serviceWhitelist = new Set(options?.services); const index = (fqn: string, file: IFileDescriptorProto) => (this.symbols[fqn] = file); Object.values(this.files).forEach((file) => visit(file, { field: index, oneOf: index, message: index, - service: index, method: index, enum: index, enumValue: index, + service: (fqn, file, service) => { + index(fqn, file); + + if (options?.services === undefined || serviceWhitelist.has(fqn)) { + this.services[fqn] = service; + } + }, extension: (fqn, file, ext) => { index(fqn, file); @@ -137,6 +150,11 @@ export class ReflectionV1Implementation { }, }), ); + + // Pass 3: pre-compute file encoding since that can be slow and is done frequently + Object.values(this.files).forEach(file => { + this.fileEncodings.set(file, FileDescriptorProto.encode(file).finish()) + }); } addToServer(server: Pick) { @@ -217,19 +235,7 @@ export class ReflectionV1Implementation { * @returns full-qualified service names (eg. 'sample.SampleService') */ listServices(listServices: string): ListServiceResponse__Output { - const services = Object.values(this.files) - .map((file) => - file.service?.map((service) => `${file.package}.${service.name}`), - ) - .flat() - .filter((service): service is string => !!service); - - const whitelist = new Set(this.options?.services ?? undefined); - const exposedServices = whitelist.size ? - services.filter(service => whitelist.has(service)) - : services; - - return { service: exposedServices.map((service) => ({ name: service })) }; + return { service: Object.keys(this.services).map((service) => ({ name: service })) }; } /** Find the proto file(s) that declares the given fully-qualified symbol name @@ -249,7 +255,7 @@ export class ReflectionV1Implementation { const deps = this.getFileDependencies(file); return { - fileDescriptorProto: [file, ...deps].map((proto) => FileDescriptorProto.encode(proto).finish()), + fileDescriptorProto: [file, ...deps].map((file) => this.fileEncodings.get(file) || new Uint8Array()) }; } @@ -267,7 +273,7 @@ export class ReflectionV1Implementation { const deps = this.getFileDependencies(file); return { - fileDescriptorProto: [file, ...deps].map((f) => FileDescriptorProto.encode(f).finish()), + fileDescriptorProto: [file, ...deps].map((file) => this.fileEncodings.get(file) || new Uint8Array), }; } @@ -289,7 +295,7 @@ export class ReflectionV1Implementation { const deps = this.getFileDependencies(file); return { - fileDescriptorProto: [file, ...deps].map((f) => FileDescriptorProto.encode(f).finish()), + fileDescriptorProto: [file, ...deps].map((file) => this.fileEncodings.get(file) || new Uint8Array()), }; } From 6d4e08cfd40cf755d55b4871777c16e4e46ecaee Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 27 Nov 2023 12:03:12 -0500 Subject: [PATCH 644/694] grpc-js: pick_first: fix currentPick comparison in resetSubchannelList --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/load-balancer-pick-first.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 4c408873e..465f56fb3 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.11", + "version": "1.9.12", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 214f92e22..75c9d4cfe 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -382,7 +382,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { private resetSubchannelList() { for (const child of this.children) { - if (child.subchannel !== this.currentPick) { + if (!(this.currentPick && child.subchannel.realSubchannelEquals(this.currentPick))) { /* The connectivity state listener is the same whether the subchannel * is in the list of children or it is the currentPick, so if it is in * both, removing it here would cause problems. In particular, that From 8016d8758bda7cfdd87a3eaef535cdfc48174ad3 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Mon, 27 Nov 2023 21:20:02 -0500 Subject: [PATCH 645/694] docs(grpc-reflection): updated reflection example to reflect on the reflection API itself --- examples/reflection/server.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/reflection/server.js b/examples/reflection/server.js index 0fa2a8a76..57ac01350 100644 --- a/examples/reflection/server.js +++ b/examples/reflection/server.js @@ -3,7 +3,11 @@ var grpc = require('@grpc/grpc-js'); var protoLoader = require('@grpc/proto-loader'); var reflection = require('@grpc/reflection'); -var PROTO_PATH = path.join(__dirname, '../protos/helloworld.proto'); +var PROTO_PATH = [ + path.join(__dirname, '../protos/helloworld.proto'), + require.resolve('@grpc/reflection/build/proto/grpc/reflection/v1/reflection.proto'), + require.resolve('@grpc/reflection/build/proto/grpc/reflection/v1alpha/reflection.proto') +]; var server = new grpc.Server(); var packageDefinition = protoLoader.loadSync(PROTO_PATH); From 674a6716d02af542bbe5622e95c91afb96c96203 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Tue, 28 Nov 2023 21:13:52 -0500 Subject: [PATCH 646/694] Revert "docs(grpc-reflection): updated reflection example to reflect on the reflection API itself" This reverts commit 8016d8758bda7cfdd87a3eaef535cdfc48174ad3. --- examples/reflection/server.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/reflection/server.js b/examples/reflection/server.js index 57ac01350..0fa2a8a76 100644 --- a/examples/reflection/server.js +++ b/examples/reflection/server.js @@ -3,11 +3,7 @@ var grpc = require('@grpc/grpc-js'); var protoLoader = require('@grpc/proto-loader'); var reflection = require('@grpc/reflection'); -var PROTO_PATH = [ - path.join(__dirname, '../protos/helloworld.proto'), - require.resolve('@grpc/reflection/build/proto/grpc/reflection/v1/reflection.proto'), - require.resolve('@grpc/reflection/build/proto/grpc/reflection/v1alpha/reflection.proto') -]; +var PROTO_PATH = path.join(__dirname, '../protos/helloworld.proto'); var server = new grpc.Server(); var packageDefinition = protoLoader.loadSync(PROTO_PATH); From 6a88cf21f47eb7e4b011a91edeb1a13dc2988af9 Mon Sep 17 00:00:00 2001 From: Justin Timmons Date: Tue, 28 Nov 2023 22:04:24 -0500 Subject: [PATCH 647/694] docs(grpc-reflection): added helloworld implementation for reflection example --- examples/reflection/server.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/reflection/server.js b/examples/reflection/server.js index 0fa2a8a76..83232e8e5 100644 --- a/examples/reflection/server.js +++ b/examples/reflection/server.js @@ -7,8 +7,13 @@ var PROTO_PATH = path.join(__dirname, '../protos/helloworld.proto'); var server = new grpc.Server(); var packageDefinition = protoLoader.loadSync(PROTO_PATH); +var proto = grpc.loadPackageDefinition(packageDefinition); var reflection = new reflection.ReflectionService(packageDefinition); + reflection.addToServer(server); +server.addService(proto.helloworld.Greeter.service, { + sayHello: (call, callback) => { callback(null, { message: 'Hello' }) } +}); server.bindAsync('localhost:5000', grpc.ServerCredentials.createInsecure(), () => { server.start(); From 4dfd8c43d73231aa2d1b9e26cc0bdb2ff6525328 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 1 Dec 2023 10:26:49 -0500 Subject: [PATCH 648/694] grpc-js: Fix call ref timer handling --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/internal-channel.ts | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 465f56fb3..78c404c94 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.12", + "version": "1.9.13", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index f36849ef8..ff3ccb5f9 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -296,7 +296,9 @@ export class InternalChannel { this.currentPicker = picker; const queueCopy = this.pickQueue.slice(); this.pickQueue = []; - this.callRefTimerUnref(); + if (queueCopy.length > 0) { + this.callRefTimerUnref(); + } for (const call of queueCopy) { call.doPick(); } @@ -349,11 +351,12 @@ export class InternalChannel { process.nextTick(() => { const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; - this.callRefTimerUnref(); + if (localQueue.length > 0) { + this.callRefTimerUnref(); + } for (const call of localQueue) { call.getConfig(); } - this.configSelectionQueue = []; }); }, status => { @@ -380,7 +383,9 @@ export class InternalChannel { } const localQueue = this.configSelectionQueue; this.configSelectionQueue = []; - this.callRefTimerUnref(); + if (localQueue.length > 0) { + this.callRefTimerUnref(); + } for (const call of localQueue) { call.reportResolverError(status); } From 3f2217e220adbbadaa6514fe00e7580598b83a62 Mon Sep 17 00:00:00 2001 From: Pitos Date: Thu, 14 Dec 2023 13:53:18 +0100 Subject: [PATCH 649/694] Fix issue #2631 --- packages/grpc-reflection/src/implementations/reflection-v1.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-reflection/src/implementations/reflection-v1.ts b/packages/grpc-reflection/src/implementations/reflection-v1.ts index 913f93719..3858d7f42 100644 --- a/packages/grpc-reflection/src/implementations/reflection-v1.ts +++ b/packages/grpc-reflection/src/implementations/reflection-v1.ts @@ -314,8 +314,8 @@ export class ReflectionV1Implementation { private getFileDependencies(file: IFileDescriptorProto): IFileDescriptorProto[] { const visited: Set = new Set(); - const toVisit: IFileDescriptorProto[] = this.fileDependencies.get(file) || []; - + const toVisit: IFileDescriptorProto[] = [...(this.fileDependencies.get(file) || [])]; + while (toVisit.length > 0) { const current = toVisit.pop(); From 5fe8afc4e778f7d26933161e8e6568900c0bbda4 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 14 Dec 2023 10:38:11 -0500 Subject: [PATCH 650/694] grpc-reflection: Increment version to 1.0.1 --- packages/grpc-reflection/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-reflection/package.json b/packages/grpc-reflection/package.json index 755ae5083..8f0d8c934 100644 --- a/packages/grpc-reflection/package.json +++ b/packages/grpc-reflection/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/reflection", - "version": "1.0.0", + "version": "1.0.1", "author": { "name": "Google Inc." }, From bda01f97f48b982237aa973ef336cdfcd9350864 Mon Sep 17 00:00:00 2001 From: Filippo Spinella Date: Thu, 14 Dec 2023 18:56:05 +0100 Subject: [PATCH 651/694] fix README --- packages/grpc-reflection/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-reflection/README.md b/packages/grpc-reflection/README.md index 47d790f5a..c4aa03b63 100644 --- a/packages/grpc-reflection/README.md +++ b/packages/grpc-reflection/README.md @@ -21,12 +21,12 @@ npm install @grpc/reflection Any gRPC-node server can use `@grpc/reflection` to expose reflection information about their gRPC API. ```typescript -import { ReflectionServer } from '@grpc/reflection'; +import { ReflectionService } from '@grpc/reflection'; const pkg = protoLoader.load(...); // Load your gRPC package definition as normal // Create the reflection implementation based on your gRPC package and add it to your existing server -const reflection = new ReflectionServer(pkg); +const reflection = new ReflectionService(pkg); reflection.addToServer(server); ``` From 493f9bfa6733ce9807c8d1ed866eb2fae3ae0806 Mon Sep 17 00:00:00 2001 From: Xuan Wang Date: Mon, 18 Dec 2023 20:51:23 +0000 Subject: [PATCH 652/694] buildscripts: Use the Kokoro shared install lib from the new repo --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index e4a0cf214..504c3ff37 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -17,7 +17,7 @@ set -eo pipefail # Constants readonly GITHUB_REPOSITORY_NAME="grpc-node" -readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" ## xDS test client Docker images readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:558b5b0bfac8e21755c223063274a779b3898afe" readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index fc74718f2..9344d054b 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -17,7 +17,7 @@ set -eo pipefail # Constants readonly GITHUB_REPOSITORY_NAME="grpc-node" -readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" ## xDS test client Docker images readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" From 3cdaebdd0c6347c55cf8a57235c3eec7ef067f00 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 21:19:02 +0800 Subject: [PATCH 653/694] fix: export type VerifyOptions --- packages/grpc-js/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index d44a2dc6e..6733246ba 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -27,7 +27,7 @@ import { StatusObject } from './call-interface'; import { Channel, ChannelImplementation } from './channel'; import { CompressionAlgorithms } from './compression-algorithms'; import { ConnectivityState } from './connectivity-state'; -import { ChannelCredentials } from './channel-credentials'; +import { ChannelCredentials, VerifyOptions } from './channel-credentials'; import { CallOptions, Client, @@ -182,6 +182,7 @@ export { ServiceDefinition, UntypedHandleCall, UntypedServiceImplementation, + VerifyOptions }; /**** Server ****/ From 6e6f942f1918738ea2c4b0abc7d2a30befcdfbbc Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 4 Jan 2024 13:11:54 -0800 Subject: [PATCH 654/694] Merge pull request #2635 from XuanWang-Amos/psm-interop-shared-build buildscripts: Use the Kokoro shared install lib from the new repo --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 2 +- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 729fb9293..ed7c77fe2 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -17,7 +17,7 @@ set -eo pipefail # Constants readonly GITHUB_REPOSITORY_NAME="grpc-node" -readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" ## xDS test client Docker images readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:558b5b0bfac8e21755c223063274a779b3898afe" readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index fc74718f2..9344d054b 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -17,7 +17,7 @@ set -eo pipefail # Constants readonly GITHUB_REPOSITORY_NAME="grpc-node" -readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/grpc/${TEST_DRIVER_BRANCH:-master}/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh" +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" ## xDS test client Docker images readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" From 6da0b49dbc7efd201d464397ec626e4db01a6560 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 16 Jan 2024 14:18:05 -0800 Subject: [PATCH 655/694] grpc-js: Fix and optimize IDLE timeouts --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/internal-channel.ts | 43 +++++++++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 78c404c94..8d8f4fd90 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.13", + "version": "1.9.14", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index ff3ccb5f9..6a65b712f 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -184,6 +184,7 @@ export class InternalChannel { private callCount = 0; private idleTimer: NodeJS.Timeout | null = null; private readonly idleTimeoutMs: number; + private lastActivityTimestamp: Date; // Channelz info private readonly channelzEnabled: boolean = true; @@ -409,6 +410,7 @@ export class InternalChannel { 'Channel constructed \n' + error.stack?.substring(error.stack.indexOf('\n') + 1) ); + this.lastActivityTimestamp = new Date(); } private getChannelzInfo(): ChannelInfo { @@ -556,19 +558,44 @@ export class InternalChannel { this.resolvingLoadBalancer.destroy(); this.updateState(ConnectivityState.IDLE); this.currentPicker = new QueuePicker(this.resolvingLoadBalancer); + if (this.idleTimer) { + clearTimeout(this.idleTimer); + this.idleTimer = null; + } } - private maybeStartIdleTimer() { - if (this.connectivityState !== ConnectivityState.SHUTDOWN && this.callCount === 0) { - this.idleTimer = setTimeout(() => { + private startIdleTimeout(timeoutMs: number) { + this.idleTimer = setTimeout(() => { + if (this.callCount > 0) { + /* If there is currently a call, the channel will not go idle for a + * period of at least idleTimeoutMs, so check again after that time. + */ + this.startIdleTimeout(this.idleTimeoutMs); + return; + } + const now = new Date(); + const timeSinceLastActivity = now.valueOf() - this.lastActivityTimestamp.valueOf(); + if (timeSinceLastActivity >= this.idleTimeoutMs) { this.trace( 'Idle timer triggered after ' + this.idleTimeoutMs + 'ms of inactivity' ); this.enterIdle(); - }, this.idleTimeoutMs); - this.idleTimer.unref?.(); + } else { + /* Whenever the timer fires with the latest activity being too recent, + * set the timer again for the time when the time since the last + * activity is equal to the timeout. This should result in the timer + * firing no more than once every idleTimeoutMs/2 on average. */ + this.startIdleTimeout(this.idleTimeoutMs - timeSinceLastActivity); + } + }, timeoutMs); + this.idleTimer.unref?.(); + } + + private maybeStartIdleTimer() { + if (this.connectivityState !== ConnectivityState.SHUTDOWN && !this.idleTimer) { + this.startIdleTimeout(this.idleTimeoutMs); } } @@ -577,10 +604,6 @@ export class InternalChannel { this.callTracker.addCallStarted(); } this.callCount += 1; - if (this.idleTimer) { - clearTimeout(this.idleTimer); - this.idleTimer = null; - } } private onCallEnd(status: StatusObject) { @@ -592,6 +615,7 @@ export class InternalChannel { } } this.callCount -= 1; + this.lastActivityTimestamp = new Date(); this.maybeStartIdleTimer(); } @@ -729,6 +753,7 @@ export class InternalChannel { const connectivityState = this.connectivityState; if (tryToConnect) { this.resolvingLoadBalancer.exitIdle(); + this.lastActivityTimestamp = new Date(); this.maybeStartIdleTimer(); } return connectivityState; From 2b31f8c148b790ca8df4deb1fa644cbbc465f264 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 16 Jan 2024 14:37:55 -0800 Subject: [PATCH 656/694] grpc-js: Shutdown transport if a state change occurs while connecting --- packages/grpc-js/src/subchannel.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 03bbf035f..eff7509a5 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -245,6 +245,10 @@ export class Subchannel { ); } }); + } else { + /* If we can't transition from CONNECTING to READY here, we will + * not be using this transport, so release its resources. */ + transport.shutdown(); } }, error => { From f52d1429fb66ef34c0321d6422c3417972323144 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 16 Jan 2024 13:39:07 -0800 Subject: [PATCH 657/694] grpc-js: Implement server interceptors --- packages/grpc-js/src/call-interface.ts | 2 +- packages/grpc-js/src/index.ts | 15 +- packages/grpc-js/src/server-call.ts | 784 ++-------------- packages/grpc-js/src/server-interceptors.ts | 886 ++++++++++++++++++ packages/grpc-js/src/server.ts | 400 +++++--- packages/grpc-js/src/transport.ts | 4 +- packages/grpc-js/test/common.ts | 7 +- packages/grpc-js/test/test-channelz.ts | 4 +- .../grpc-js/test/test-server-deadlines.ts | 4 +- .../grpc-js/test/test-server-interceptors.ts | 296 ++++++ 10 files changed, 1544 insertions(+), 858 deletions(-) create mode 100644 packages/grpc-js/src/server-interceptors.ts create mode 100644 packages/grpc-js/test/test-server-interceptors.ts diff --git a/packages/grpc-js/src/call-interface.ts b/packages/grpc-js/src/call-interface.ts index 15035aeac..c0c63b957 100644 --- a/packages/grpc-js/src/call-interface.ts +++ b/packages/grpc-js/src/call-interface.ts @@ -37,7 +37,7 @@ export interface StatusObject { } export type PartialStatusObject = Pick & { - metadata: Metadata | null; + metadata?: Metadata | null | undefined; }; export const enum WriteFlags { diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index 50671b01c..b37f61103 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -49,6 +49,7 @@ import { import { Metadata, MetadataOptions, MetadataValue } from './metadata'; import { Server, + ServerOptions, UntypedHandleCall, UntypedServiceImplementation, } from './server'; @@ -226,7 +227,7 @@ export const setLogVerbosity = (verbosity: LogVerbosity): void => { logging.setLoggerVerbosity(verbosity); }; -export { Server }; +export { Server, ServerOptions }; export { ServerCredentials }; export { KeyCertPair }; @@ -264,6 +265,18 @@ export { addAdminServicesToServer } from './admin'; export { ServiceConfig, LoadBalancingConfig, MethodConfig, RetryPolicy } from './service-config'; +export { + ServerListener, + FullServerListener, + ServerListenerBuilder, + Responder, + FullResponder, + ResponderBuilder, + ServerInterceptingCallInterface, + ServerInterceptingCall, + ServerInterceptor +} from './server-interceptors'; + import * as experimental from './experimental'; export { experimental }; diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 107c2e3ef..8fb1de2aa 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -16,66 +16,17 @@ */ import { EventEmitter } from 'events'; -import * as http2 from 'http2'; import { Duplex, Readable, Writable } from 'stream'; -import * as zlib from 'zlib'; -import { promisify } from 'util'; import { Status, - DEFAULT_MAX_SEND_MESSAGE_LENGTH, - DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, - LogVerbosity, } from './constants'; import { Deserialize, Serialize } from './make-client'; import { Metadata } from './metadata'; -import { StreamDecoder } from './stream-decoder'; import { ObjectReadable, ObjectWritable } from './object-stream'; -import { ChannelOptions } from './channel-options'; -import * as logging from './logging'; import { StatusObject, PartialStatusObject } from './call-interface'; import { Deadline } from './deadline'; -import { getErrorCode, getErrorMessage } from './error'; - -const TRACER_NAME = 'server_call'; -const unzip = promisify(zlib.unzip); -const inflate = promisify(zlib.inflate); - -function trace(text: string): void { - logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); -} - -interface DeadlineUnitIndexSignature { - [name: string]: number; -} - -const GRPC_ACCEPT_ENCODING_HEADER = 'grpc-accept-encoding'; -const GRPC_ENCODING_HEADER = 'grpc-encoding'; -const GRPC_MESSAGE_HEADER = 'grpc-message'; -const GRPC_STATUS_HEADER = 'grpc-status'; -const GRPC_TIMEOUT_HEADER = 'grpc-timeout'; -const DEADLINE_REGEX = /(\d{1,8})\s*([HMSmun])/; -const deadlineUnitsToMs: DeadlineUnitIndexSignature = { - H: 3600000, - M: 60000, - S: 1000, - m: 1, - u: 0.001, - n: 0.000001, -}; -const defaultCompressionHeaders = { - // TODO(cjihrig): Remove these encoding headers from the default response - // once compression is integrated. - [GRPC_ACCEPT_ENCODING_HEADER]: 'identity,deflate,gzip', - [GRPC_ENCODING_HEADER]: 'identity', -}; -const defaultResponseHeaders = { - [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, - [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', -}; -const defaultResponseOptions = { - waitForTrailers: true, -} as http2.ServerStreamResponseOptions; +import { ServerInterceptingCallInterface } from './server-interceptors'; export type ServerStatusResponse = Partial; @@ -105,6 +56,38 @@ export type ServerDuplexStream = ServerSurfaceCall & ObjectReadable & ObjectWritable & { end: (metadata?: Metadata) => void }; +export function serverErrorToStatus(error: ServerErrorResponse | ServerStatusResponse, extraTrailers?: Metadata | undefined): PartialStatusObject { + const status: PartialStatusObject = { + code: Status.UNKNOWN, + details: 'message' in error ? error.message : 'Unknown Error', + metadata: + 'metadata' in error && error.metadata !== undefined + ? error.metadata + : null, + }; + + if ( + 'code' in error && + typeof error.code === 'number' && + Number.isInteger(error.code) + ) { + status.code = error.code; + + if ('details' in error && typeof error.details === 'string') { + status.details = error.details!; + } + } + + if (extraTrailers) { + if (status.metadata) { + status.metadata.merge(extraTrailers); + } else { + status.metadata = extraTrailers; + } + } + return status; +} + export class ServerUnaryCallImpl extends EventEmitter implements ServerUnaryCall @@ -112,13 +95,13 @@ export class ServerUnaryCallImpl cancelled: boolean; constructor( - private call: Http2ServerCallStream, + private path: string, + private call: ServerInterceptingCallInterface, public metadata: Metadata, public request: RequestType ) { super(); this.cancelled = false; - this.call.setupSurfaceCall(this); } getPeer(): string { @@ -134,7 +117,7 @@ export class ServerUnaryCallImpl } getPath(): string { - return this.call.getPath(); + return this.path; } } @@ -145,23 +128,16 @@ export class ServerReadableStreamImpl cancelled: boolean; constructor( - private call: Http2ServerCallStream, - public metadata: Metadata, - public deserialize: Deserialize, - encoding: string + private path: string, + private call: ServerInterceptingCallInterface, + public metadata: Metadata ) { super({ objectMode: true }); this.cancelled = false; - this.call.setupSurfaceCall(this); - this.call.setupReadable(this, encoding); } _read(size: number) { - if (!this.call.consumeUnpushedMessages(this)) { - return; - } - - this.call.resume(); + this.call.startRead(); } getPeer(): string { @@ -177,7 +153,7 @@ export class ServerReadableStreamImpl } getPath(): string { - return this.call.getPath(); + return this.path; } } @@ -187,20 +163,23 @@ export class ServerWritableStreamImpl { cancelled: boolean; private trailingMetadata: Metadata; + private pendingStatus: PartialStatusObject = { + code: Status.OK, + details: 'OK' + }; constructor( - private call: Http2ServerCallStream, + private path: string, + private call: ServerInterceptingCallInterface, public metadata: Metadata, - public serialize: Serialize, public request: RequestType ) { super({ objectMode: true }); this.cancelled = false; this.trailingMetadata = new Metadata(); - this.call.setupSurfaceCall(this); this.on('error', err => { - this.call.sendError(err); + this.pendingStatus = serverErrorToStatus(err); this.end(); }); } @@ -218,7 +197,7 @@ export class ServerWritableStreamImpl } getPath(): string { - return this.call.getPath(); + return this.path; } _write( @@ -227,28 +206,13 @@ export class ServerWritableStreamImpl // eslint-disable-next-line @typescript-eslint/no-explicit-any callback: (...args: any[]) => void ) { - try { - const response = this.call.serializeMessage(chunk); - - if (!this.call.write(response)) { - this.call.once('drain', callback); - return; - } - } catch (err) { - this.emit('error', { - details: getErrorMessage(err), - code: Status.INTERNAL, - }); - } - - callback(); + this.call.sendMessage(chunk, callback); } _final(callback: Function): void { this.call.sendStatus({ - code: Status.OK, - details: 'OK', - metadata: this.trailingMetadata, + ...this.pendingStatus, + metadata: this.pendingStatus.metadata ?? this.trailingMetadata, }); callback(null); } @@ -268,27 +232,23 @@ export class ServerDuplexStreamImpl implements ServerDuplexStream { cancelled: boolean; - /* This field appears to be unsued, but it is actually used in _final, which is assiged from - * ServerWritableStreamImpl.prototype._final below. */ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore noUnusedLocals private trailingMetadata: Metadata; + private pendingStatus: PartialStatusObject = { + code: Status.OK, + details: 'OK' + }; constructor( - private call: Http2ServerCallStream, - public metadata: Metadata, - public serialize: Serialize, - public deserialize: Deserialize, - encoding: string + private path: string, + private call: ServerInterceptingCallInterface, + public metadata: Metadata ) { super({ objectMode: true }); this.cancelled = false; this.trailingMetadata = new Metadata(); - this.call.setupSurfaceCall(this); - this.call.setupReadable(this, encoding); this.on('error', err => { - this.call.sendError(err); + this.pendingStatus = serverErrorToStatus(err); this.end(); }); } @@ -306,7 +266,28 @@ export class ServerDuplexStreamImpl } getPath(): string { - return this.call.getPath(); + return this.path; + } + + _read(size: number) { + this.call.startRead(); + } + + _write( + chunk: ResponseType, + encoding: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback: (...args: any[]) => void + ) { + this.call.sendMessage(chunk, callback); + } + + _final(callback: Function): void { + this.call.sendStatus({ + ...this.pendingStatus, + metadata: this.pendingStatus.metadata ?? this.trailingMetadata, + }); + callback(null); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -319,13 +300,6 @@ export class ServerDuplexStreamImpl } } -ServerDuplexStreamImpl.prototype._read = - ServerReadableStreamImpl.prototype._read; -ServerDuplexStreamImpl.prototype._write = - ServerWritableStreamImpl.prototype._write; -ServerDuplexStreamImpl.prototype._final = - ServerWritableStreamImpl.prototype._final; - // Unary response callback signature. export type sendUnaryData = ( error: ServerErrorResponse | ServerStatusResponse | null, @@ -401,597 +375,3 @@ export type Handler = | BidiStreamingHandler; export type HandlerType = 'bidi' | 'clientStream' | 'serverStream' | 'unary'; - -// Internal class that wraps the HTTP2 request. -export class Http2ServerCallStream< - RequestType, - ResponseType -> extends EventEmitter { - cancelled = false; - deadlineTimer: NodeJS.Timeout | null = null; - private statusSent = false; - private deadline: Deadline = Infinity; - private wantTrailers = false; - private metadataSent = false; - private canPush = false; - private isPushPending = false; - private bufferedMessages: Array = []; - private messagesToPush: Array = []; - private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; - private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; - - constructor( - private stream: http2.ServerHttp2Stream, - private handler: Handler, - options: ChannelOptions - ) { - super(); - - this.stream.once('error', (err: ServerErrorResponse) => { - /* We need an error handler to avoid uncaught error event exceptions, but - * there is nothing we can reasonably do here. Any error event should - * have a corresponding close event, which handles emitting the cancelled - * event. And the stream is now in a bad state, so we can't reasonably - * expect to be able to send an error over it. */ - }); - - this.stream.once('close', () => { - trace( - 'Request to method ' + - this.handler?.path + - ' stream closed with rstCode ' + - this.stream.rstCode - ); - - if (!this.statusSent) { - this.cancelled = true; - this.emit('cancelled', 'cancelled'); - this.emit('streamEnd', false); - this.sendStatus({ - code: Status.CANCELLED, - details: 'Cancelled by client', - metadata: null, - }); - if (this.deadlineTimer) clearTimeout(this.deadlineTimer); - } - }); - - this.stream.on('drain', () => { - this.emit('drain'); - }); - - if ('grpc.max_send_message_length' in options) { - this.maxSendMessageSize = options['grpc.max_send_message_length']!; - } - if ('grpc.max_receive_message_length' in options) { - this.maxReceiveMessageSize = options['grpc.max_receive_message_length']!; - } - } - - private checkCancelled(): boolean { - /* In some cases the stream can become destroyed before the close event - * fires. That creates a race condition that this check works around */ - if (this.stream.destroyed || this.stream.closed) { - this.cancelled = true; - } - return this.cancelled; - } - - private getDecompressedMessage( - message: Buffer, - encoding: string - ): Buffer | Promise { - if (encoding === 'deflate') { - return inflate(message.subarray(5)); - } else if (encoding === 'gzip') { - return unzip(message.subarray(5)); - } else if (encoding === 'identity') { - return message.subarray(5); - } - - return Promise.reject({ - code: Status.UNIMPLEMENTED, - details: `Received message compressed with unsupported encoding "${encoding}"`, - }); - } - - sendMetadata(customMetadata?: Metadata) { - if (this.checkCancelled()) { - return; - } - - if (this.metadataSent) { - return; - } - - this.metadataSent = true; - const custom = customMetadata ? customMetadata.toHttp2Headers() : null; - // TODO(cjihrig): Include compression headers. - const headers = { - ...defaultResponseHeaders, - ...defaultCompressionHeaders, - ...custom, - }; - this.stream.respond(headers, defaultResponseOptions); - } - - receiveMetadata(headers: http2.IncomingHttpHeaders) { - const metadata = Metadata.fromHttp2Headers(headers); - - if (logging.isTracerEnabled(TRACER_NAME)) { - trace( - 'Request to ' + - this.handler.path + - ' received headers ' + - JSON.stringify(metadata.toJSON()) - ); - } - - // TODO(cjihrig): Receive compression metadata. - - const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER); - - if (timeoutHeader.length > 0) { - const match = timeoutHeader[0].toString().match(DEADLINE_REGEX); - - if (match === null) { - const err = new Error('Invalid deadline') as ServerErrorResponse; - err.code = Status.OUT_OF_RANGE; - this.sendError(err); - return metadata; - } - - const timeout = (+match[1] * deadlineUnitsToMs[match[2]]) | 0; - - const now = new Date(); - this.deadline = now.setMilliseconds(now.getMilliseconds() + timeout); - this.deadlineTimer = setTimeout(handleExpiredDeadline, timeout, this); - metadata.remove(GRPC_TIMEOUT_HEADER); - } - - // Remove several headers that should not be propagated to the application - metadata.remove(http2.constants.HTTP2_HEADER_ACCEPT_ENCODING); - metadata.remove(http2.constants.HTTP2_HEADER_TE); - metadata.remove(http2.constants.HTTP2_HEADER_CONTENT_TYPE); - metadata.remove('grpc-accept-encoding'); - - return metadata; - } - - receiveUnaryMessage(encoding: string): Promise { - return new Promise((resolve, reject) => { - const { stream } = this; - - let receivedLength = 0; - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const call = this; - const body: Buffer[] = []; - const limit = this.maxReceiveMessageSize; - - this.stream.on('data', onData); - this.stream.on('end', onEnd); - this.stream.on('error', onEnd); - - function onData(chunk: Buffer) { - receivedLength += chunk.byteLength; - - if (limit !== -1 && receivedLength > limit) { - stream.removeListener('data', onData); - stream.removeListener('end', onEnd); - stream.removeListener('error', onEnd); - - reject({ - code: Status.RESOURCE_EXHAUSTED, - details: `Received message larger than max (${receivedLength} vs. ${limit})`, - }); - return; - } - - body.push(chunk); - } - - function onEnd(err?: Error) { - stream.removeListener('data', onData); - stream.removeListener('end', onEnd); - stream.removeListener('error', onEnd); - - if (err !== undefined) { - reject({ code: Status.INTERNAL, details: err.message }); - return; - } - - if (receivedLength === 0) { - reject({ - code: Status.INTERNAL, - details: 'received empty unary message', - }); - return; - } - - call.emit('receiveMessage'); - - const requestBytes = Buffer.concat(body, receivedLength); - const compressed = requestBytes.readUInt8(0) === 1; - const compressedMessageEncoding = compressed ? encoding : 'identity'; - const decompressedMessage = call.getDecompressedMessage( - requestBytes, - compressedMessageEncoding - ); - - if (Buffer.isBuffer(decompressedMessage)) { - resolve( - call.deserializeMessageWithInternalError(decompressedMessage) - ); - return; - } - - decompressedMessage.then( - decompressed => - resolve(call.deserializeMessageWithInternalError(decompressed)), - (err: any) => - reject( - err.code - ? err - : { - code: Status.INTERNAL, - details: `Received "grpc-encoding" header "${encoding}" but ${encoding} decompression failed`, - } - ) - ); - } - }); - } - - private async deserializeMessageWithInternalError(buffer: Buffer) { - try { - return this.deserializeMessage(buffer); - } catch (err) { - throw { - details: getErrorMessage(err), - code: Status.INTERNAL, - }; - } - } - - serializeMessage(value: ResponseType) { - const messageBuffer = this.handler.serialize(value); - - // TODO(cjihrig): Call compression aware serializeMessage(). - const byteLength = messageBuffer.byteLength; - const output = Buffer.allocUnsafe(byteLength + 5); - output.writeUInt8(0, 0); - output.writeUInt32BE(byteLength, 1); - messageBuffer.copy(output, 5); - return output; - } - - deserializeMessage(bytes: Buffer) { - return this.handler.deserialize(bytes); - } - - async sendUnaryMessage( - err: ServerErrorResponse | ServerStatusResponse | null, - value?: ResponseType | null, - metadata?: Metadata | null, - flags?: number - ) { - if (this.checkCancelled()) { - return; - } - - if (metadata === undefined) { - metadata = null; - } - - if (err) { - if (!Object.prototype.hasOwnProperty.call(err, 'metadata') && metadata) { - err.metadata = metadata; - } - this.sendError(err); - return; - } - - try { - const response = this.serializeMessage(value!); - - this.write(response); - this.sendStatus({ code: Status.OK, details: 'OK', metadata }); - } catch (err) { - this.sendError({ - details: getErrorMessage(err), - code: Status.INTERNAL, - }); - } - } - - sendStatus(statusObj: PartialStatusObject) { - this.emit('callEnd', statusObj.code); - this.emit('streamEnd', statusObj.code === Status.OK); - if (this.checkCancelled()) { - return; - } - - trace( - 'Request to method ' + - this.handler?.path + - ' ended with status code: ' + - Status[statusObj.code] + - ' details: ' + - statusObj.details - ); - - if (this.deadlineTimer) clearTimeout(this.deadlineTimer); - - if (this.stream.headersSent) { - if (!this.wantTrailers) { - this.wantTrailers = true; - this.stream.once('wantTrailers', () => { - const trailersToSend = { - [GRPC_STATUS_HEADER]: statusObj.code, - [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), - ...statusObj.metadata?.toHttp2Headers(), - }; - - this.stream.sendTrailers(trailersToSend); - this.statusSent = true; - }); - this.stream.end(); - } - } else { - // Trailers-only response - const trailersToSend = { - [GRPC_STATUS_HEADER]: statusObj.code, - [GRPC_MESSAGE_HEADER]: encodeURI(statusObj.details), - ...defaultResponseHeaders, - ...statusObj.metadata?.toHttp2Headers(), - }; - this.stream.respond(trailersToSend, { endStream: true }); - this.statusSent = true; - } - } - - sendError(error: ServerErrorResponse | ServerStatusResponse) { - const status: PartialStatusObject = { - code: Status.UNKNOWN, - details: 'message' in error ? error.message : 'Unknown Error', - metadata: - 'metadata' in error && error.metadata !== undefined - ? error.metadata - : null, - }; - - if ( - 'code' in error && - typeof error.code === 'number' && - Number.isInteger(error.code) - ) { - status.code = error.code; - - if ('details' in error && typeof error.details === 'string') { - status.details = error.details!; - } - } - - this.sendStatus(status); - } - - write(chunk: Buffer) { - if (this.checkCancelled()) { - return; - } - - if ( - this.maxSendMessageSize !== -1 && - chunk.length > this.maxSendMessageSize - ) { - this.sendError({ - code: Status.RESOURCE_EXHAUSTED, - details: `Sent message larger than max (${chunk.length} vs. ${this.maxSendMessageSize})`, - }); - return; - } - - this.sendMetadata(); - this.emit('sendMessage'); - return this.stream.write(chunk); - } - - resume() { - this.stream.resume(); - } - - setupSurfaceCall(call: ServerSurfaceCall) { - this.once('cancelled', reason => { - call.cancelled = true; - call.emit('cancelled', reason); - }); - - this.once('callEnd', status => call.emit('callEnd', status)); - } - - setupReadable( - readable: - | ServerReadableStream - | ServerDuplexStream, - encoding: string - ) { - const decoder = new StreamDecoder(); - - let readsDone = false; - - let pendingMessageProcessing = false; - - let pushedEnd = false; - - const maybePushEnd = async () => { - if (!pushedEnd && readsDone && !pendingMessageProcessing) { - pushedEnd = true; - await this.pushOrBufferMessage(readable, null); - } - }; - - this.stream.on('data', async (data: Buffer) => { - const messages = decoder.write(data); - - pendingMessageProcessing = true; - this.stream.pause(); - for (const message of messages) { - if ( - this.maxReceiveMessageSize !== -1 && - message.length > this.maxReceiveMessageSize - ) { - this.sendError({ - code: Status.RESOURCE_EXHAUSTED, - details: `Received message larger than max (${message.length} vs. ${this.maxReceiveMessageSize})`, - }); - return; - } - this.emit('receiveMessage'); - - const compressed = message.readUInt8(0) === 1; - const compressedMessageEncoding = compressed ? encoding : 'identity'; - const decompressedMessage = await this.getDecompressedMessage( - message, - compressedMessageEncoding - ); - - // Encountered an error with decompression; it'll already have been propogated back - // Just return early - if (!decompressedMessage) return; - - await this.pushOrBufferMessage(readable, decompressedMessage); - } - pendingMessageProcessing = false; - this.stream.resume(); - await maybePushEnd(); - }); - - this.stream.once('end', async () => { - readsDone = true; - await maybePushEnd(); - }); - } - - consumeUnpushedMessages( - readable: - | ServerReadableStream - | ServerDuplexStream - ): boolean { - this.canPush = true; - - while (this.messagesToPush.length > 0) { - const nextMessage = this.messagesToPush.shift(); - const canPush = readable.push(nextMessage); - - if (nextMessage === null || canPush === false) { - this.canPush = false; - break; - } - } - - return this.canPush; - } - - private async pushOrBufferMessage( - readable: - | ServerReadableStream - | ServerDuplexStream, - messageBytes: Buffer | null - ): Promise { - if (this.isPushPending) { - this.bufferedMessages.push(messageBytes); - } else { - await this.pushMessage(readable, messageBytes); - } - } - - private async pushMessage( - readable: - | ServerReadableStream - | ServerDuplexStream, - messageBytes: Buffer | null - ) { - if (messageBytes === null) { - trace('Received end of stream'); - if (this.canPush) { - readable.push(null); - } else { - this.messagesToPush.push(null); - } - - return; - } - - trace('Received message of length ' + messageBytes.length); - - this.isPushPending = true; - - try { - const deserialized = await this.deserializeMessage(messageBytes); - - if (this.canPush) { - if (!readable.push(deserialized)) { - this.canPush = false; - this.stream.pause(); - } - } else { - this.messagesToPush.push(deserialized); - } - } catch (error) { - // Ignore any remaining messages when errors occur. - this.bufferedMessages.length = 0; - let code = getErrorCode(error); - if (code === null || code < Status.OK || code > Status.UNAUTHENTICATED) { - code = Status.INTERNAL; - } - - readable.emit('error', { - details: getErrorMessage(error), - code: code, - }); - } - - this.isPushPending = false; - - if (this.bufferedMessages.length > 0) { - await this.pushMessage( - readable, - this.bufferedMessages.shift() as Buffer | null - ); - } - } - - getPeer(): string { - const socket = this.stream.session?.socket; - if (socket?.remoteAddress) { - if (socket.remotePort) { - return `${socket.remoteAddress}:${socket.remotePort}`; - } else { - return socket.remoteAddress; - } - } else { - return 'unknown'; - } - } - - getDeadline(): Deadline { - return this.deadline; - } - - getPath(): string { - return this.handler.path; - } -} - -/* eslint-disable @typescript-eslint/no-explicit-any */ -type UntypedServerCall = Http2ServerCallStream; - -function handleExpiredDeadline(call: UntypedServerCall) { - const err = new Error('Deadline exceeded') as ServerErrorResponse; - err.code = Status.DEADLINE_EXCEEDED; - - call.sendError(err); - call.cancelled = true; - call.emit('cancelled', 'deadline'); -} diff --git a/packages/grpc-js/src/server-interceptors.ts b/packages/grpc-js/src/server-interceptors.ts new file mode 100644 index 000000000..c03f3028c --- /dev/null +++ b/packages/grpc-js/src/server-interceptors.ts @@ -0,0 +1,886 @@ +/* + * Copyright 2024 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 { PartialStatusObject} from "./call-interface"; +import { ServerMethodDefinition } from "./make-client"; +import { Metadata } from "./metadata"; +import { ChannelOptions } from "./channel-options"; +import { Handler, ServerErrorResponse } from "./server-call"; +import { Deadline } from "./deadline"; +import { DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, DEFAULT_MAX_SEND_MESSAGE_LENGTH, LogVerbosity, Status } from "./constants"; +import * as http2 from 'http2'; +import { getErrorMessage } from "./error"; +import * as zlib from 'zlib'; +import { promisify } from "util"; +import { StreamDecoder } from "./stream-decoder"; +import { CallEventTracker } from "./transport"; +import * as logging from './logging'; + +const unzip = promisify(zlib.unzip); +const inflate = promisify(zlib.inflate); + +const TRACER_NAME = 'server_call'; + +function trace(text: string) { + logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text); +} + +export interface ServerMetadataListener { + (metadata: Metadata, next: (metadata: Metadata) => void): void; +} + +export interface ServerMessageListener { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (message: any, next: (message: any) => void): void; +} + +export interface ServerHalfCloseListener { + (next: () => void): void; +} + +export interface ServerCancelListener { + (): void; +} + +export interface FullServerListener { + onReceiveMetadata: ServerMetadataListener; + onReceiveMessage: ServerMessageListener; + onReceiveHalfClose: ServerHalfCloseListener; + onCancel: ServerCancelListener; +} + +export type ServerListener = Partial; + +export class ServerListenerBuilder { + private metadata: ServerMetadataListener | undefined = undefined; + private message: ServerMessageListener | undefined = undefined; + private halfClose: ServerHalfCloseListener | undefined = undefined; + private cancel: ServerCancelListener | undefined = undefined; + + withOnReceiveMetadata(onReceiveMetadata: ServerMetadataListener): this { + this.metadata = onReceiveMetadata; + return this; + } + + withOnReceiveMessage(onReceiveMessage: ServerMessageListener): this { + this.message = onReceiveMessage; + return this; + } + + withOnReceiveHalfClose(onReceiveHalfClose: ServerHalfCloseListener): this { + this.halfClose = onReceiveHalfClose; + return this; + } + + withOnCancel(onCancel: ServerCancelListener): this { + this.cancel = onCancel; + return this; + } + + build(): ServerListener { + return { + onReceiveMetadata: this.metadata, + onReceiveMessage: this.message, + onReceiveHalfClose: this.halfClose, + onCancel: this.cancel + }; + } +} + +export interface InterceptingServerListener { + onReceiveMetadata(metadata: Metadata): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onReceiveMessage(message: any): void; + onReceiveHalfClose(): void; + onCancel(): void; +} + +export function isInterceptingServerListener(listener: ServerListener | InterceptingServerListener): listener is InterceptingServerListener { + return listener.onReceiveMetadata !== undefined && listener.onReceiveMetadata.length === 1; +} + +class InterceptingServerListenerImpl implements InterceptingServerListener { + /** + * Once the call is cancelled, ignore all other events. + */ + private cancelled: boolean = false; + private processingMetadata: boolean = false; + private hasPendingMessage: boolean = false; + private pendingMessage: any = null; + private processingMessage: boolean = false; + private hasPendingHalfClose: boolean = false; + + constructor(private listener: FullServerListener, private nextListener: InterceptingServerListener) {} + + private processPendingMessage() { + if (this.hasPendingMessage) { + this.nextListener.onReceiveMessage(this.pendingMessage); + this.pendingMessage = null; + this.hasPendingMessage = false; + } + } + + private processPendingHalfClose() { + if (this.hasPendingHalfClose) { + this.nextListener.onReceiveHalfClose(); + this.hasPendingHalfClose = false; + } + } + + onReceiveMetadata(metadata: Metadata): void { + if (this.cancelled) { + return; + } + this.processingMetadata = true; + this.listener.onReceiveMetadata(metadata, interceptedMetadata => { + this.processingMetadata = false; + if (this.cancelled) { + return; + } + this.nextListener.onReceiveMetadata(interceptedMetadata); + this.processPendingMessage(); + this.processPendingHalfClose(); + }); + } + onReceiveMessage(message: any): void { + if (this.cancelled) { + return; + } + this.processingMessage = true; + this.listener.onReceiveMessage(message, msg => { + this.processingMessage = false; + if (this.cancelled) { + return; + } + if (this.processingMetadata) { + this.pendingMessage = msg; + this.hasPendingMessage = true; + } else { + this.nextListener.onReceiveMessage(msg); + this.processPendingHalfClose(); + } + }); + } + onReceiveHalfClose(): void { + if (this.cancelled) { + return; + } + this.listener.onReceiveHalfClose(() => { + if (this.cancelled) { + return; + } + if (this.processingMetadata || this.processingMessage) { + this.hasPendingHalfClose = true; + } else { + this.nextListener.onReceiveHalfClose(); + } + }); + } + onCancel(): void { + this.cancelled = true; + this.listener.onCancel(); + this.nextListener.onCancel(); + } + +} + +export interface StartResponder { + (next: (listener?: ServerListener) => void): void; +} + +export interface MetadataResponder { + (metadata: Metadata, next: (metadata: Metadata) => void): void; +} + +export interface MessageResponder { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (message: any, next: (message: any) => void): void; +} + +export interface StatusResponder { + (status: PartialStatusObject, next: (status: PartialStatusObject) => void): void; +} + +export interface FullResponder { + start: StartResponder; + sendMetadata: MetadataResponder; + sendMessage: MessageResponder; + sendStatus: StatusResponder; +} + +export type Responder = Partial; + +export class ResponderBuilder { + private start: StartResponder | undefined = undefined; + private metadata: MetadataResponder | undefined = undefined; + private message: MessageResponder | undefined = undefined; + private status: StatusResponder | undefined = undefined; + + withStart(start: StartResponder): this { + this.start = start; + return this; + } + + withSendMetadata(sendMetadata: MetadataResponder): this { + this.metadata = sendMetadata; + return this; + } + + withSendMessage(sendMessage: MessageResponder): this { + this.message = sendMessage; + return this; + } + + withSendStatus(sendStatus: StatusResponder): this { + this.status = sendStatus; + return this; + } + + build(): Responder { + return { + start: this.start, + sendMetadata: this.metadata, + sendMessage: this.message, + sendStatus: this.status + }; + } +} + +const defaultServerListener: FullServerListener = { + onReceiveMetadata: (metadata, next) => { + next(metadata); + }, + onReceiveMessage: (message, next) => { + next(message); + }, + onReceiveHalfClose: next => { + next(); + }, + onCancel: () => {} +}; + +const defaultResponder: FullResponder = { + start: (next) => { + next(); + }, + sendMetadata: (metadata, next) => { + next(metadata); + }, + sendMessage: (message, next) => { + next(message); + }, + sendStatus: (status, next) => { + next(status); + } +}; + +export interface ServerInterceptingCallInterface { + /** + * Register the listener to handle inbound events. + */ + start(listener: InterceptingServerListener): void; + /** + * Send response metadata. + */ + sendMetadata(metadata: Metadata): void; + /** + * Send a response message. + */ + sendMessage(message: any, callback: () => void): void; + /** + * End the call by sending this status. + */ + sendStatus(status: PartialStatusObject): void; + /** + * Start a single read, eventually triggering either listener.onReceiveMessage or listener.onReceiveHalfClose. + */ + startRead(): void; + /** + * Return the peer address of the client making the request, if known, or "unknown" otherwise + */ + getPeer(): string; + /** + * Return the call deadline set by the client. The value is Infinity if there is no deadline. + */ + getDeadline(): Deadline; +} + +export class ServerInterceptingCall implements ServerInterceptingCallInterface { + private responder: FullResponder; + private processingMetadata: boolean = false; + private processingMessage: boolean = false; + private pendingMessage: any = null; + private pendingMessageCallback: (() => void) | null = null; + private pendingStatus: PartialStatusObject | null = null; + constructor(private nextCall: ServerInterceptingCallInterface, responder?: Responder) { + this.responder = {...defaultResponder, ...responder}; + } + + private processPendingMessage() { + if (this.pendingMessageCallback) { + this.nextCall.sendMessage(this.pendingMessage, this.pendingMessageCallback); + this.pendingMessage = null; + this.pendingMessageCallback = null; + } + } + + private processPendingStatus() { + if (this.pendingStatus) { + this.nextCall.sendStatus(this.pendingStatus); + this.pendingStatus = null; + } + } + + start(listener: InterceptingServerListener): void { + this.responder.start(interceptedListener => { + const fullInterceptedListener: FullServerListener = {...defaultServerListener, ...interceptedListener}; + const finalInterceptingListener = new InterceptingServerListenerImpl(fullInterceptedListener, listener); + this.nextCall.start(finalInterceptingListener); + }); + } + sendMetadata(metadata: Metadata): void { + this.processingMetadata = true; + this.responder.sendMetadata(metadata, interceptedMetadata => { + this.processingMetadata = false; + this.nextCall.sendMetadata(interceptedMetadata); + this.processPendingMessage(); + this.processPendingStatus(); + }); + } + sendMessage(message: any, callback: () => void): void { + this.processingMessage = true; + this.responder.sendMessage(message, interceptedMessage => { + this.processingMessage = false; + if (this.processingMetadata) { + this.pendingMessage = interceptedMessage; + this.pendingMessageCallback = callback; + } else { + this.nextCall.sendMessage(interceptedMessage, callback); + } + }); + } + sendStatus(status: PartialStatusObject): void { + this.responder.sendStatus(status, interceptedStatus => { + if (this.processingMetadata || this.processingMessage) { + this.pendingStatus = interceptedStatus; + } else { + this.nextCall.sendStatus(interceptedStatus); + } + }); + } + startRead(): void { + this.nextCall.startRead(); + } + getPeer(): string { + return this.nextCall.getPeer(); + } + getDeadline(): Deadline { + return this.nextCall.getDeadline(); + } +} + +export interface ServerInterceptor { + (methodDescriptor: ServerMethodDefinition, call: ServerInterceptingCallInterface): ServerInterceptingCall; +} + +interface DeadlineUnitIndexSignature { + [name: string]: number; +} + +const GRPC_ACCEPT_ENCODING_HEADER = 'grpc-accept-encoding'; +const GRPC_ENCODING_HEADER = 'grpc-encoding'; +const GRPC_MESSAGE_HEADER = 'grpc-message'; +const GRPC_STATUS_HEADER = 'grpc-status'; +const GRPC_TIMEOUT_HEADER = 'grpc-timeout'; +const DEADLINE_REGEX = /(\d{1,8})\s*([HMSmun])/; +const deadlineUnitsToMs: DeadlineUnitIndexSignature = { + H: 3600000, + M: 60000, + S: 1000, + m: 1, + u: 0.001, + n: 0.000001, +}; + +const defaultCompressionHeaders = { + // TODO(cjihrig): Remove these encoding headers from the default response + // once compression is integrated. + [GRPC_ACCEPT_ENCODING_HEADER]: 'identity,deflate,gzip', + [GRPC_ENCODING_HEADER]: 'identity', +}; +const defaultResponseHeaders = { + [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, + [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', +}; +const defaultResponseOptions = { + waitForTrailers: true, +} as http2.ServerStreamResponseOptions; + +type ReadQueueEntryType = 'COMPRESSED' | 'READABLE' | 'HALF_CLOSE'; + +interface ReadQueueEntry { + type: ReadQueueEntryType; + compressedMessage: Buffer | null; + parsedMessage: any; +} + +export class BaseServerInterceptingCall implements ServerInterceptingCallInterface { + private listener: InterceptingServerListener | null = null; + private metadata: Metadata; + private deadlineTimer: NodeJS.Timeout | null = null; + private deadline: Deadline = Infinity; + private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; + private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; + private cancelled = false; + private metadataSent = false; + private wantTrailers = false; + private cancelNotified = false; + private incomingEncoding: string = 'identity'; + private decoder = new StreamDecoder(); + private readQueue: ReadQueueEntry[] = []; + private isReadPending = false; + private receivedHalfClose = false; + private streamEnded = false; + + constructor( + private readonly stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders, + private readonly callEventTracker: CallEventTracker | null, + private readonly handler: Handler, + options: ChannelOptions + ) { + this.stream.once('error', (err: ServerErrorResponse) => { + /* We need an error handler to avoid uncaught error event exceptions, but + * there is nothing we can reasonably do here. Any error event should + * have a corresponding close event, which handles emitting the cancelled + * event. And the stream is now in a bad state, so we can't reasonably + * expect to be able to send an error over it. */ + }); + + this.stream.once('close', () => { + trace( + 'Request to method ' + + this.handler?.path + + ' stream closed with rstCode ' + + this.stream.rstCode + ); + + if (this.callEventTracker && !this.streamEnded) { + this.streamEnded = true; + this.callEventTracker.onStreamEnd(false); + this.callEventTracker.onCallEnd({ + code: Status.CANCELLED, + details: 'Stream closed before sending status', + metadata: null + }); + } + + this.notifyOnCancel(); + }); + + this.stream.on('data', (data: Buffer) => { + this.handleDataFrame(data); + }); + this.stream.pause(); + + this.stream.on('end', () => { + this.handleEndEvent(); + }); + + if ('grpc.max_send_message_length' in options) { + this.maxSendMessageSize = options['grpc.max_send_message_length']!; + } + if ('grpc.max_receive_message_length' in options) { + this.maxReceiveMessageSize = options['grpc.max_receive_message_length']!; + } + + const metadata = Metadata.fromHttp2Headers(headers); + + if (logging.isTracerEnabled(TRACER_NAME)) { + trace( + 'Request to ' + + this.handler.path + + ' received headers ' + + JSON.stringify(metadata.toJSON()) + ); + } + + const timeoutHeader = metadata.get(GRPC_TIMEOUT_HEADER); + + if (timeoutHeader.length > 0) { + this.handleTimeoutHeader(timeoutHeader[0] as string); + } + + const encodingHeader = metadata.get(GRPC_ENCODING_HEADER); + + if (encodingHeader.length > 0) { + this.incomingEncoding = encodingHeader[0] as string; + } + + // Remove several headers that should not be propagated to the application + metadata.remove(GRPC_TIMEOUT_HEADER); + metadata.remove(GRPC_ENCODING_HEADER); + metadata.remove(GRPC_ACCEPT_ENCODING_HEADER); + metadata.remove(http2.constants.HTTP2_HEADER_ACCEPT_ENCODING); + metadata.remove(http2.constants.HTTP2_HEADER_TE); + metadata.remove(http2.constants.HTTP2_HEADER_CONTENT_TYPE); + this.metadata = metadata; + } + + private handleTimeoutHeader(timeoutHeader: string) { + const match = timeoutHeader.toString().match(DEADLINE_REGEX); + + if (match === null) { + const status: PartialStatusObject = { + code: Status.INTERNAL, + details: `Invalid ${GRPC_TIMEOUT_HEADER} value "${timeoutHeader}"`, + metadata: null + }; + // Wait for the constructor to complete before sending the error. + process.nextTick(() => { + this.sendStatus(status); + }); + return; + } + + const timeout = (+match[1] * deadlineUnitsToMs[match[2]]) | 0; + + const now = new Date(); + this.deadline = now.setMilliseconds(now.getMilliseconds() + timeout); + this.deadlineTimer = setTimeout(() => { + const status: PartialStatusObject = { + code: Status.DEADLINE_EXCEEDED, + details: 'Deadline exceeded', + metadata: null + }; + this.sendStatus(status); + }, timeout); + + } + + private checkCancelled(): boolean { + /* In some cases the stream can become destroyed before the close event + * fires. That creates a race condition that this check works around */ + if (!this.cancelled && (this.stream.destroyed || this.stream.closed)) { + this.notifyOnCancel(); + this.cancelled = true; + } + return this.cancelled; + } + private notifyOnCancel() { + if (this.cancelNotified) { + return; + } + this.cancelNotified = true; + this.cancelled = true; + process.nextTick(() => { + this.listener?.onCancel(); + }); + if (this.deadlineTimer) { + clearTimeout(this.deadlineTimer); + } + // Flush incoming data frames + this.stream.resume(); + } + + /** + * A server handler can start sending messages without explicitly sending + * metadata. In that case, we need to send headers before sending any + * messages. This function does that if necessary. + */ + private maybeSendMetadata() { + if (!this.metadataSent) { + this.sendMetadata(new Metadata()); + } + } + + /** + * Serialize a message to a length-delimited byte string. + * @param value + * @returns + */ + private serializeMessage(value: any) { + const messageBuffer = this.handler.serialize(value); + const byteLength = messageBuffer.byteLength; + const output = Buffer.allocUnsafe(byteLength + 5); + /* Note: response compression is currently not supported, so this + * compressed bit is always 0. */ + output.writeUInt8(0, 0); + output.writeUInt32BE(byteLength, 1); + messageBuffer.copy(output, 5); + return output; + } + + private decompressMessage( + message: Buffer, + encoding: string + ): Buffer | Promise { + switch (encoding) { + case 'deflate': + return inflate(message.subarray(5)); + case 'gzip': + return unzip(message.subarray(5)); + case 'identity': + return message.subarray(5); + default: + return Promise.reject({ + code: Status.UNIMPLEMENTED, + details: `Received message compressed with unsupported encoding "${encoding}"`, + }); + } + } + + private async decompressAndMaybePush(queueEntry: ReadQueueEntry) { + if (queueEntry.type !== 'COMPRESSED') { + throw new Error(`Invalid queue entry type: ${queueEntry.type}`); + } + + const compressed = queueEntry.compressedMessage!.readUInt8(0) === 1; + const compressedMessageEncoding = compressed ? this.incomingEncoding : 'identity'; + const decompressedMessage = await this.decompressMessage(queueEntry.compressedMessage!, compressedMessageEncoding); + try { + queueEntry.parsedMessage = this.handler.deserialize(decompressedMessage); + } catch (err) { + this.sendStatus({ + code: Status.INTERNAL, + details: `Error deserializing request: ${(err as Error).message}` + }); + return; + } + queueEntry.type = 'READABLE'; + this.maybePushNextMessage(); + } + + private maybePushNextMessage() { + if (this.listener && this.isReadPending && this.readQueue.length > 0 && this.readQueue[0].type !== 'COMPRESSED') { + this.isReadPending = false; + const nextQueueEntry = this.readQueue.shift()!; + if (nextQueueEntry.type === 'READABLE') { + this.listener.onReceiveMessage(nextQueueEntry.parsedMessage); + } else { + // nextQueueEntry.type === 'HALF_CLOSE' + this.listener.onReceiveHalfClose(); + } + } + } + + private handleDataFrame(data: Buffer) { + if (this.checkCancelled()) { + return; + } + trace('Request to ' + this.handler.path + ' received data frame of size ' + data.length); + const rawMessages = this.decoder.write(data); + + for (const messageBytes of rawMessages) { + this.stream.pause(); + if (this.maxReceiveMessageSize !== -1 && messageBytes.length - 5 > this.maxReceiveMessageSize) { + this.sendStatus({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message larger than max (${messageBytes.length - 5} vs. ${this.maxReceiveMessageSize})`, + metadata: null + }); + return; + } + const queueEntry: ReadQueueEntry = { + type: 'COMPRESSED', + compressedMessage: messageBytes, + parsedMessage: null + }; + this.readQueue.push(queueEntry); + this.decompressAndMaybePush(queueEntry); + this.callEventTracker?.addMessageReceived(); + } + } + private handleEndEvent() { + this.readQueue.push({ + type: 'HALF_CLOSE', + compressedMessage: null, + parsedMessage: null + }); + this.receivedHalfClose = true; + this.maybePushNextMessage(); + } + start(listener: InterceptingServerListener): void { + trace('Request to ' + this.handler.path + ' start called'); + if (this.checkCancelled()) { + return; + } + this.listener = listener; + listener.onReceiveMetadata(this.metadata); + } + sendMetadata(metadata: Metadata): void { + if (this.checkCancelled()) { + return; + } + + if (this.metadataSent) { + return; + } + + this.metadataSent = true; + const custom = metadata ? metadata.toHttp2Headers() : null; + const headers = { + ...defaultResponseHeaders, + ...defaultCompressionHeaders, + ...custom, + }; + this.stream.respond(headers, defaultResponseOptions); + } + sendMessage(message: any, callback: () => void): void { + if (this.checkCancelled()) { + return; + } + let response: Buffer; + try { + response = this.serializeMessage(message); + } catch (e) { + this.sendStatus({ + code: Status.INTERNAL, + details: `Error serializing response: ${getErrorMessage(e)}`, + metadata: null + }); + return; + } + + if ( + this.maxSendMessageSize !== -1 && + response.length - 5 > this.maxSendMessageSize + ) { + this.sendStatus({ + code: Status.RESOURCE_EXHAUSTED, + details: `Sent message larger than max (${response.length} vs. ${this.maxSendMessageSize})`, + metadata: null + }); + return; + } + this.maybeSendMetadata(); + trace('Request to ' + this.handler.path + ' sent data frame of size ' + response.length); + this.stream.write(response, error => { + if (error) { + this.sendStatus({ + code: Status.INTERNAL, + details: `Error writing message: ${getErrorMessage(error)}`, + metadata: null + }); + return; + } + this.callEventTracker?.addMessageSent(); + callback(); + }); + } + sendStatus(status: PartialStatusObject): void { + if (this.checkCancelled()) { + return; + } + this.notifyOnCancel(); + + trace( + 'Request to method ' + + this.handler?.path + + ' ended with status code: ' + + Status[status.code] + + ' details: ' + + status.details + ); + + if (this.stream.headersSent) { + if (!this.wantTrailers) { + this.wantTrailers = true; + this.stream.once('wantTrailers', () => { + if (this.callEventTracker && !this.streamEnded) { + this.streamEnded = true; + this.callEventTracker.onStreamEnd(true); + this.callEventTracker.onCallEnd(status); + } + const trailersToSend = { + [GRPC_STATUS_HEADER]: status.code, + [GRPC_MESSAGE_HEADER]: encodeURI(status.details), + ...status.metadata?.toHttp2Headers(), + }; + + this.stream.sendTrailers(trailersToSend); + }); + this.stream.end(); + } + } else { + if (this.callEventTracker && !this.streamEnded) { + this.streamEnded = true; + this.callEventTracker.onStreamEnd(true); + this.callEventTracker.onCallEnd(status); + } + // Trailers-only response + const trailersToSend = { + [GRPC_STATUS_HEADER]: status.code, + [GRPC_MESSAGE_HEADER]: encodeURI(status.details), + ...defaultResponseHeaders, + ...status.metadata?.toHttp2Headers(), + }; + this.stream.respond(trailersToSend, { endStream: true }); + } + } + startRead(): void { + trace('Request to ' + this.handler.path + ' startRead called'); + if (this.checkCancelled()) { + return; + } + this.isReadPending = true; + if (this.readQueue.length === 0) { + if (!this.receivedHalfClose) { + this.stream.resume(); + } + } else { + this.maybePushNextMessage(); + } + } + getPeer(): string { + const socket = this.stream.session?.socket; + if (socket?.remoteAddress) { + if (socket.remotePort) { + return `${socket.remoteAddress}:${socket.remotePort}`; + } else { + return socket.remoteAddress; + } + } else { + return 'unknown'; + } + } + getDeadline(): Deadline { + return this.deadline; + } +} + +export function getServerInterceptingCall( + interceptors: ServerInterceptor[], + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders, + callEventTracker: CallEventTracker | null, + handler: Handler, + options: ChannelOptions +) { + + const methodDefinition: ServerMethodDefinition = { + path: handler.path, + requestStream: handler.type === 'clientStream' || handler.type === 'bidi', + responseStream: handler.type === 'serverStream' || handler.type === 'bidi', + requestDeserialize: handler.deserialize, + responseSerialize: handler.serialize + } + const baseCall = new BaseServerInterceptingCall(stream, headers, callEventTracker, handler, options); + return interceptors.reduce((call: ServerInterceptingCallInterface, interceptor: ServerInterceptor) => { + return interceptor(methodDefinition, call); + }, baseCall); +} diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index adc7f299f..31851b832 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -28,20 +28,18 @@ import { HandleCall, Handler, HandlerType, - Http2ServerCallStream, sendUnaryData, ServerDuplexStream, ServerDuplexStreamImpl, ServerReadableStream, - ServerReadableStreamImpl, ServerStreamingHandler, ServerUnaryCall, - ServerUnaryCallImpl, ServerWritableStream, ServerWritableStreamImpl, UnaryHandler, ServerErrorResponse, ServerStatusResponse, + serverErrorToStatus, } from './server-call'; import { ServerCredentials } from './server-credentials'; import { ChannelOptions } from './channel-options'; @@ -72,6 +70,9 @@ import { unregisterChannelzRef, } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; +import { ServerInterceptingCallInterface, ServerInterceptor, getServerInterceptingCall } from './server-interceptors'; +import { PartialStatusObject } from './call-interface'; +import { CallEventTracker } from './transport'; const UNLIMITED_CONNECTION_AGE_MS = ~(1 << 31); const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); @@ -109,7 +110,7 @@ function deprecate(message: string) { function getUnimplementedStatusResponse( methodName: string -): Partial { +): PartialStatusObject { return { code: Status.UNIMPLEMENTED, details: `The server does not implement the method ${methodName}`, @@ -219,6 +220,10 @@ interface Http2ServerInfo { sessions: Set; } +export interface ServerOptions extends ChannelOptions { + interceptors?: ServerInterceptor[] +} + export class Server { private boundPorts: Map= new Map(); private http2Servers: Map = new Map(); @@ -234,7 +239,7 @@ export class Server { */ private started = false; private shutdown = false; - private options: ChannelOptions; + private options: ServerOptions; private serverAddressString = 'null'; // Channelz Info @@ -251,13 +256,15 @@ export class Server { private readonly keepaliveTimeMs: number; private readonly keepaliveTimeoutMs: number; + private readonly interceptors: ServerInterceptor[]; + /** * Options that will be used to construct all Http2Server instances for this * Server. */ private commonServerOptions: http2.ServerOptions; - constructor(options?: ChannelOptions) { + constructor(options?: ServerOptions) { this.options = options ?? {}; if (this.options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; @@ -296,6 +303,7 @@ export class Server { maxConcurrentStreams: this.options['grpc.max_concurrent_streams'], }; } + this.interceptors = this.options.interceptors ?? []; this.trace('Server constructed'); } @@ -1072,23 +1080,24 @@ export class Server { return handler; } - private _respondWithError>( - err: T, + private _respondWithError( + err: PartialStatusObject, stream: http2.ServerHttp2Stream, channelzSessionInfo: ChannelzSessionInfo | null = null ) { - const call = new Http2ServerCallStream(stream, null!, this.options); - - if (err.code === undefined) { - err.code = Status.INTERNAL; - } + const trailersToSend = { + 'grpc-status': err.code ?? Status.INTERNAL, + 'grpc-message': err.details, + [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, + [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', + ...err.metadata?.toHttp2Headers() + }; + stream.respond(trailersToSend, {endStream: true}); if (this.channelzEnabled) { this.callTracker.addCallFailed(); channelzSessionInfo?.streamTracker.addCallFailed(); } - - call.sendError(err); } private _channelzHandler( @@ -1120,39 +1129,44 @@ export class Server { return; } - const call = new Http2ServerCallStream(stream, handler, this.options); - - call.once('callEnd', (code: Status) => { - if (code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }); - - if (channelzSessionInfo) { - call.once('streamEnd', (success: boolean) => { - if (success) { - channelzSessionInfo.streamTracker.addCallSucceeded(); + let callEventTracker: CallEventTracker = { + addMessageSent: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesSent += 1; + channelzSessionInfo.lastMessageSentTimestamp = new Date(); + } + }, + addMessageReceived: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesReceived += 1; + channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); + } + }, + onCallEnd: status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); } else { - channelzSessionInfo.streamTracker.addCallFailed(); + this.callTracker.addCallFailed(); } - }); - call.on('sendMessage', () => { - channelzSessionInfo.messagesSent += 1; - channelzSessionInfo.lastMessageSentTimestamp = new Date(); - }); - call.on('receiveMessage', () => { - channelzSessionInfo.messagesReceived += 1; - channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); - }); + }, + onStreamEnd: success => { + if (channelzSessionInfo) { + if (success) { + channelzSessionInfo.streamTracker.addCallSucceeded(); + } else { + channelzSessionInfo.streamTracker.addCallFailed(); + } + } + } } - if (!this._runHandlerForCall(call, handler, headers)) { + const call = getServerInterceptingCall(this.interceptors, stream, headers, callEventTracker, handler, this.options); + + if (!this._runHandlerForCall(call, handler)) { this.callTracker.addCallFailed(); channelzSessionInfo?.streamTracker.addCallFailed(); - call.sendError({ + call.sendStatus({ code: Status.INTERNAL, details: `Unknown handler type: ${handler.type}`, }); @@ -1179,9 +1193,10 @@ export class Server { return; } - const call = new Http2ServerCallStream(stream, handler, this.options); - if (!this._runHandlerForCall(call, handler, headers)) { - call.sendError({ + const call = getServerInterceptingCall(this.interceptors, stream, headers, null, handler, this.options); + + if (!this._runHandlerForCall(call, handler)) { + call.sendStatus({ code: Status.INTERNAL, details: `Unknown handler type: ${handler.type}`, }); @@ -1189,38 +1204,27 @@ export class Server { } private _runHandlerForCall( - call: Http2ServerCallStream, - handler: Handler, - headers: http2.IncomingHttpHeaders + call: ServerInterceptingCallInterface, + handler: Handler ): boolean { - const metadata = call.receiveMetadata(headers); - const encoding = - (metadata.get('grpc-encoding')[0] as string | undefined) ?? 'identity'; - metadata.remove('grpc-encoding'); const { type } = handler; if (type === 'unary') { - handleUnary(call, handler as UntypedUnaryHandler, metadata, encoding); + handleUnary(call, handler as UntypedUnaryHandler); } else if (type === 'clientStream') { handleClientStreaming( call, - handler as UntypedClientStreamingHandler, - metadata, - encoding + handler as UntypedClientStreamingHandler ); } else if (type === 'serverStream') { handleServerStreaming( call, - handler as UntypedServerStreamingHandler, - metadata, - encoding + handler as UntypedServerStreamingHandler ); } else if (type === 'bidi') { handleBidiStreaming( call, - handler as UntypedBidiStreamingHandler, - metadata, - encoding + handler as UntypedBidiStreamingHandler ); } else { return false; @@ -1365,52 +1369,83 @@ export class Server { } async function handleUnary( - call: Http2ServerCallStream, - handler: UnaryHandler, - metadata: Metadata, - encoding: string + call: ServerInterceptingCallInterface, + handler: UnaryHandler ): Promise { - try { - const request = await call.receiveUnaryMessage(encoding); + let stream: ServerUnaryCall; - if (request === undefined || call.cancelled) { + function respond( + err: ServerErrorResponse | ServerStatusResponse | null, + value?: ResponseType | null, + trailer?: Metadata, + flags?: number + ) { + if (err) { + call.sendStatus(serverErrorToStatus(err, trailer)); return; } + call.sendMessage(value, () => { + call.sendStatus({ + code: Status.OK, + details: 'OK', + metadata: trailer ?? null + }); + }); + } - const emitter = new ServerUnaryCallImpl( - call, - metadata, - request - ); - - handler.func( - emitter, - ( - err: ServerErrorResponse | ServerStatusResponse | null, - value?: ResponseType | null, - trailer?: Metadata, - flags?: number - ) => { - call.sendUnaryMessage(err, value, trailer, flags); + let requestMetadata: Metadata; + let requestMessage: RequestType | null = null; + call.start({ + onReceiveMetadata(metadata) { + requestMetadata = metadata; + call.startRead(); + }, + onReceiveMessage(message) { + if (requestMessage) { + call.sendStatus({ + code: Status.UNIMPLEMENTED, + details: `Received a second request message for server streaming method ${handler.path}`, + metadata: null + }); + return; } - ); - } catch (err) { - call.sendError(err as ServerErrorResponse); - } + requestMessage = message; + call.startRead(); + }, + onReceiveHalfClose() { + if (!requestMessage) { + call.sendStatus({ + code: Status.UNIMPLEMENTED, + details: `Received no request message for server streaming method ${handler.path}`, + metadata: null + }); + return; + } + stream = new ServerWritableStreamImpl(handler.path, call, requestMetadata, requestMessage); + try { + handler.func(stream, respond); + } catch (err) { + call.sendStatus({ + code: Status.UNKNOWN, + details: `Server method handler threw error ${(err as Error).message}`, + metadata: null + }); + } + }, + onCancel() { + if (stream) { + stream.cancelled = true; + stream.emit('cancelled', 'cancelled'); + } + }, + }); } function handleClientStreaming( - call: Http2ServerCallStream, - handler: ClientStreamingHandler, - metadata: Metadata, - encoding: string + call: ServerInterceptingCallInterface, + handler: ClientStreamingHandler ): void { - const stream = new ServerReadableStreamImpl( - call, - metadata, - handler.deserialize, - encoding - ); + let stream: ServerReadableStream; function respond( err: ServerErrorResponse | ServerStatusResponse | null, @@ -1418,61 +1453,134 @@ function handleClientStreaming( trailer?: Metadata, flags?: number ) { - stream.destroy(); - call.sendUnaryMessage(err, value, trailer, flags); - } - - if (call.cancelled) { - return; - } - - stream.on('error', respond); - handler.func(stream, respond); -} - -async function handleServerStreaming( - call: Http2ServerCallStream, - handler: ServerStreamingHandler, - metadata: Metadata, - encoding: string -): Promise { - try { - const request = await call.receiveUnaryMessage(encoding); - - if (request === undefined || call.cancelled) { + if (err) { + call.sendStatus(serverErrorToStatus(err, trailer)); return; } + call.sendMessage(value, () => { + call.sendStatus({ + code: Status.OK, + details: 'OK', + metadata: trailer ?? null + }); + }); + } - const stream = new ServerWritableStreamImpl( - call, - metadata, - handler.serialize, - request - ); + call.start({ + onReceiveMetadata(metadata) { + stream = new ServerDuplexStreamImpl(handler.path, call, metadata); + try { + handler.func(stream, respond); + } catch (err) { + call.sendStatus({ + code: Status.UNKNOWN, + details: `Server method handler threw error ${(err as Error).message}`, + metadata: null + }); + } + }, + onReceiveMessage(message) { + stream.push(message); + }, + onReceiveHalfClose() { + stream.push(null); + }, + onCancel() { + if (stream) { + stream.cancelled = true; + stream.emit('cancelled', 'cancelled'); + stream.destroy(); + } + }, + }); +} - handler.func(stream); - } catch (err) { - call.sendError(err as ServerErrorResponse); - } +function handleServerStreaming( + call: ServerInterceptingCallInterface, + handler: ServerStreamingHandler +): void { + let stream: ServerWritableStream; + + let requestMetadata: Metadata; + let requestMessage: RequestType | null = null; + call.start({ + onReceiveMetadata(metadata) { + requestMetadata = metadata; + call.startRead(); + }, + onReceiveMessage(message) { + if (requestMessage) { + call.sendStatus({ + code: Status.UNIMPLEMENTED, + details: `Received a second request message for server streaming method ${handler.path}`, + metadata: null + }); + return; + } + requestMessage = message; + call.startRead(); + }, + onReceiveHalfClose() { + if (!requestMessage) { + call.sendStatus({ + code: Status.UNIMPLEMENTED, + details: `Received no request message for server streaming method ${handler.path}`, + metadata: null + }); + return; + } + stream = new ServerWritableStreamImpl(handler.path, call, requestMetadata, requestMessage); + try { + handler.func(stream); + } catch (err) { + call.sendStatus({ + code: Status.UNKNOWN, + details: `Server method handler threw error ${(err as Error).message}`, + metadata: null + }); + } + }, + onCancel() { + if (stream) { + stream.cancelled = true; + stream.emit('cancelled', 'cancelled'); + stream.destroy(); + } + }, + }); } function handleBidiStreaming( - call: Http2ServerCallStream, - handler: BidiStreamingHandler, - metadata: Metadata, - encoding: string + call: ServerInterceptingCallInterface, + handler: BidiStreamingHandler ): void { - const stream = new ServerDuplexStreamImpl( - call, - metadata, - handler.serialize, - handler.deserialize, - encoding - ); - - if (call.cancelled) { - return; - } - - handler.func(stream); + let stream: ServerDuplexStream; + + call.start({ + onReceiveMetadata(metadata) { + stream = new ServerDuplexStreamImpl(handler.path, call, metadata); + try { + handler.func(stream); + } catch (err) { + call.sendStatus({ + code: Status.UNKNOWN, + details: `Server method handler threw error ${(err as Error).message}`, + metadata: null + }); + } + }, + onReceiveMessage(message) { + stream.push(message); + }, + onReceiveHalfClose() { + stream.push(null); + }, + onCancel() { + if (stream) { + stream.cancelled = true; + stream.emit('cancelled', 'cancelled'); + stream.destroy(); + } + }, + }); } diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 39ca69383..b21581375 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -23,7 +23,7 @@ import { PeerCertificate, TLSSocket, } from 'tls'; -import { StatusObject } from './call-interface'; +import { PartialStatusObject } from './call-interface'; import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; import { @@ -72,7 +72,7 @@ const KEEPALIVE_TIMEOUT_MS = 20000; export interface CallEventTracker { addMessageSent(): void; addMessageReceived(): void; - onCallEnd(status: StatusObject): void; + onCallEnd(status: PartialStatusObject): void; onStreamEnd(success: boolean): void; } diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index 20352fb2f..f6aeed103 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -72,7 +72,7 @@ const serviceImpl = { export class TestServer { private server: grpc.Server; public port: number | null = null; - constructor(public useTls: boolean, options?: grpc.ChannelOptions) { + constructor(public useTls: boolean, options?: grpc.ServerOptions) { this.server = new grpc.Server(options); this.server.addService(echoService.service, serviceImpl); } @@ -92,7 +92,6 @@ export class TestServer { return; } this.port = port; - this.server.start(); resolve(); }); }); @@ -130,6 +129,10 @@ export class TestClient { this.client.echo({}, callback); } + sendRequestWithMetadata(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError) => void) { + this.client.echo({}, metadata, callback); + } + getChannelState() { return this.client.getChannel().getConnectivityState(false); } diff --git a/packages/grpc-js/test/test-channelz.ts b/packages/grpc-js/test/test-channelz.ts index e740c4329..5c3dc3d7d 100644 --- a/packages/grpc-js/test/test-channelz.ts +++ b/packages/grpc-js/test/test-channelz.ts @@ -495,12 +495,12 @@ describe('Channelz', () => { assert.strictEqual( +serverSocketResult.socket.data .streams_succeeded, - 0 + 1 ); assert.strictEqual( +serverSocketResult.socket.data .streams_failed, - 1 + 0 ); assert.strictEqual( +serverSocketResult.socket.data diff --git a/packages/grpc-js/test/test-server-deadlines.ts b/packages/grpc-js/test/test-server-deadlines.ts index 2a966e664..43e39808a 100644 --- a/packages/grpc-js/test/test-server-deadlines.ts +++ b/packages/grpc-js/test/test-server-deadlines.ts @@ -110,8 +110,8 @@ describe('Server deadlines', () => { {}, (error: any, response: any) => { assert(error); - assert.strictEqual(error.code, grpc.status.OUT_OF_RANGE); - assert.strictEqual(error.details, 'Invalid deadline'); + assert.strictEqual(error.code, grpc.status.INTERNAL); + assert.match(error.details, /^Invalid grpc-timeout value/); done(); } ); diff --git a/packages/grpc-js/test/test-server-interceptors.ts b/packages/grpc-js/test/test-server-interceptors.ts new file mode 100644 index 000000000..b6b5b55f2 --- /dev/null +++ b/packages/grpc-js/test/test-server-interceptors.ts @@ -0,0 +1,296 @@ +/* + * Copyright 2024 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 * as assert from 'assert'; +import * as path from 'path'; +import * as grpc from '../src'; +import { TestClient, loadProtoFile } from './common'; + +const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto'); +const echoService = loadProtoFile(protoFile) + .EchoService as grpc.ServiceClientConstructor; + +const AUTH_HEADER_KEY = 'auth'; +const AUTH_HEADER_ALLOWED_VALUE = 'allowed'; +const testAuthInterceptor: grpc.ServerInterceptor = (methodDescriptor, call) => { + return new grpc.ServerInterceptingCall(call, { + start: next => { + const authListener: grpc.ServerListener = { + onReceiveMetadata: (metadata, mdNext) => { + if (metadata.get(AUTH_HEADER_KEY)?.[0] !== AUTH_HEADER_ALLOWED_VALUE) { + call.sendStatus({ + code: grpc.status.UNAUTHENTICATED, + details: 'Auth metadata not correct' + }); + } else { + mdNext(metadata); + } + } + }; + next(authListener); + } + }); +}; + +let eventCounts = { + receiveMetadata: 0, + receiveMessage: 0, + receiveHalfClose: 0, + sendMetadata: 0, + sendMessage: 0, + sendStatus: 0 +}; + +function resetEventCounts() { + eventCounts = { + receiveMetadata: 0, + receiveMessage: 0, + receiveHalfClose: 0, + sendMetadata: 0, + sendMessage: 0, + sendStatus: 0 + }; +} + +/** + * Test interceptor to verify that interceptors see each expected event by + * counting each kind of event. + * @param methodDescription + * @param call + */ +const testLoggingInterceptor: grpc.ServerInterceptor = (methodDescription, call) => { + return new grpc.ServerInterceptingCall(call, { + start: next => { + next({ + onReceiveMetadata: (metadata, mdNext) => { + eventCounts.receiveMetadata += 1; + mdNext(metadata); + }, + onReceiveMessage: (message, messageNext) => { + eventCounts.receiveMessage += 1; + messageNext(message); + }, + onReceiveHalfClose: hcNext => { + eventCounts.receiveHalfClose += 1; + hcNext(); + } + }); + }, + sendMetadata: (metadata, mdNext) => { + eventCounts.sendMetadata += 1; + mdNext(metadata); + }, + sendMessage: (message, messageNext) => { + eventCounts.sendMessage += 1; + messageNext(message); + }, + sendStatus: (status, statusNext) => { + eventCounts.sendStatus += 1; + statusNext(status); + } + }); +}; + +const testHeaderInjectionInterceptor: grpc.ServerInterceptor = (methodDescriptor, call) => { + return new grpc.ServerInterceptingCall(call, { + start: next => { + const authListener: grpc.ServerListener = { + onReceiveMetadata: (metadata, mdNext) => { + metadata.set('injected-header', 'present'); + mdNext(metadata); + } + }; + next(authListener); + } + }); +}; + +describe('Server interceptors', () => { + describe('Auth-type interceptor', () => { + let server: grpc.Server; + let client: TestClient; + /* Tests that an interceptor can entirely prevent the handler from being + * invoked, based on the contents of the metadata. */ + before(done => { + server = new grpc.Server({interceptors: [testAuthInterceptor]}); + server.addService(echoService.service, { + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + // A test will fail if a request makes it to the handler without the correct auth header + assert.strictEqual(call.metadata.get(AUTH_HEADER_KEY)?.[0], AUTH_HEADER_ALLOWED_VALUE); + callback(null, call.request); + }, + }); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + }); + }); + after(done => { + client.close(); + server.tryShutdown(done); + }); + it('Should accept a request with the expected header', done => { + const requestMetadata = new grpc.Metadata(); + requestMetadata.set(AUTH_HEADER_KEY, AUTH_HEADER_ALLOWED_VALUE); + client.sendRequestWithMetadata(requestMetadata, done); + }); + it('Should reject a request without the expected header', done => { + const requestMetadata = new grpc.Metadata(); + requestMetadata.set(AUTH_HEADER_KEY, 'not allowed'); + client.sendRequestWithMetadata(requestMetadata, error => { + assert.strictEqual(error?.code, grpc.status.UNAUTHENTICATED); + done(); + }); + }); + }); + describe('Logging-type interceptor', () => { + let server: grpc.Server; + let client: TestClient; + before(done => { + server = new grpc.Server({interceptors: [testLoggingInterceptor]}); + server.addService(echoService.service, { + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + call.sendMetadata(new grpc.Metadata()); + callback(null, call.request); + }, + }); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + }); + }); + after(done => { + client.close(); + server.tryShutdown(done); + }); + beforeEach(() => { + resetEventCounts(); + }); + it('Should see every event once', done => { + client.sendRequest(error => { + assert.ifError(error); + assert.deepStrictEqual(eventCounts, { + receiveMetadata: 1, + receiveMessage: 1, + receiveHalfClose: 1, + sendMetadata: 1, + sendMessage: 1, + sendStatus: 1 + }); + done(); + }); + }); + }); + describe('Header injection interceptor', () => { + let server: grpc.Server; + let client: TestClient; + before(done => { + server = new grpc.Server({interceptors: [testHeaderInjectionInterceptor]}); + server.addService(echoService.service, { + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + assert.strictEqual(call.metadata.get('injected-header')?.[0], 'present'); + callback(null, call.request); + }, + }); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + }); + }); + after(done => { + client.close(); + server.tryShutdown(done); + }); + it('Should inject the header for the handler to see', done => { + client.sendRequest(done); + }); + }); + describe('Multiple interceptors', () => { + let server: grpc.Server; + let client: TestClient; + before(done => { + server = new grpc.Server({interceptors: [testAuthInterceptor, testLoggingInterceptor, testHeaderInjectionInterceptor]}); + server.addService(echoService.service, { + echo: ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData + ) => { + assert.strictEqual(call.metadata.get(AUTH_HEADER_KEY)?.[0], AUTH_HEADER_ALLOWED_VALUE); + assert.strictEqual(call.metadata.get('injected-header')?.[0], 'present'); + call.sendMetadata(new grpc.Metadata()); + callback(null, call.request); + }, + }); + server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + }); + }); + after(done => { + client.close(); + server.tryShutdown(done); + }); + beforeEach(() => { + resetEventCounts(); + }); + it('Should not log requests rejected by auth', done => { + const requestMetadata = new grpc.Metadata(); + requestMetadata.set(AUTH_HEADER_KEY, 'not allowed'); + client.sendRequestWithMetadata(requestMetadata, error => { + assert.strictEqual(error?.code, grpc.status.UNAUTHENTICATED); + assert.deepStrictEqual(eventCounts, { + receiveMetadata: 0, + receiveMessage: 0, + receiveHalfClose: 0, + sendMetadata: 0, + sendMessage: 0, + sendStatus: 0 + }); + done(); + }); + }); + it('Should log requests accepted by auth', done => { + const requestMetadata = new grpc.Metadata(); + requestMetadata.set(AUTH_HEADER_KEY, AUTH_HEADER_ALLOWED_VALUE); + client.sendRequestWithMetadata(requestMetadata, error => { + assert.ifError(error); + assert.deepStrictEqual(eventCounts, { + receiveMetadata: 1, + receiveMessage: 1, + receiveHalfClose: 1, + sendMetadata: 1, + sendMessage: 1, + sendStatus: 1 + }); + done(); + }); + }); + }); +}); From 24c258ad5807f1a12882788d9add6174c3c9f616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Cruz?= Date: Tue, 30 Jan 2024 14:53:34 +0000 Subject: [PATCH 658/694] grpc-health-check: Move `typescript` as a dev dependency --- packages/grpc-health-check/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grpc-health-check/package.json b/packages/grpc-health-check/package.json index a7fe1c3fd..ecdaea579 100644 --- a/packages/grpc-health-check/package.json +++ b/packages/grpc-health-check/package.json @@ -21,8 +21,7 @@ "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs proto/ -O test/generated --grpcLib=@grpc/grpc-js health/v1/health.proto" }, "dependencies": { - "@grpc/proto-loader": "^0.7.10", - "typescript": "^5.2.2" + "@grpc/proto-loader": "^0.7.10" }, "files": [ "LICENSE", @@ -35,6 +34,7 @@ "types": "build/src/health.d.ts", "license": "Apache-2.0", "devDependencies": { - "@grpc/grpc-js": "file:../grpc-js" + "@grpc/grpc-js": "file:../grpc-js", + "typescript": "^5.2.2" } } From 7c9a5e71479292cf3dd766e796316bacfb89daee Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 31 Jan 2024 10:41:01 -0800 Subject: [PATCH 659/694] Make extra trailer behavior consistent with old code --- packages/grpc-js/src/server-call.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 8fb1de2aa..edc38e983 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -56,14 +56,11 @@ export type ServerDuplexStream = ServerSurfaceCall & ObjectReadable & ObjectWritable & { end: (metadata?: Metadata) => void }; -export function serverErrorToStatus(error: ServerErrorResponse | ServerStatusResponse, extraTrailers?: Metadata | undefined): PartialStatusObject { +export function serverErrorToStatus(error: ServerErrorResponse | ServerStatusResponse, overrideTrailers?: Metadata | undefined): PartialStatusObject { const status: PartialStatusObject = { code: Status.UNKNOWN, details: 'message' in error ? error.message : 'Unknown Error', - metadata: - 'metadata' in error && error.metadata !== undefined - ? error.metadata - : null, + metadata: overrideTrailers ?? error.metadata ?? null }; if ( @@ -77,14 +74,6 @@ export function serverErrorToStatus(error: ServerErrorResponse | ServerStatusRes status.details = error.details!; } } - - if (extraTrailers) { - if (status.metadata) { - status.metadata.merge(extraTrailers); - } else { - status.metadata = extraTrailers; - } - } return status; } From 322b165c855468ef6d1a606f7e7083cf0ef04a56 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 1 Feb 2024 13:25:38 -0800 Subject: [PATCH 660/694] grpc-js-xds: De-experimentalize tested features and update feature list --- packages/grpc-js-xds/README.md | 5 ++++- packages/grpc-js-xds/gulpfile.ts | 5 ++--- packages/grpc-js-xds/src/environment.ts | 7 +++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index c1db440cf..2e07675a6 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -30,5 +30,8 @@ const client = new MyServiceClient('xds:///example.com:123'); - [Client Status Discovery Service](https://github.com/grpc/proposal/blob/master/A40-csds-support.md) - [Outlier Detection](https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md) - [xDS Retry Support](https://github.com/grpc/proposal/blob/master/A44-xds-retry.md) - - [xDS Aggregate and Logical DNS Clusters](https://github.com/grpc/proposal/blob/master/A37-xds-aggregate-and-logical-dns-clusters.md)' + - [xDS Aggregate and Logical DNS Clusters](https://github.com/grpc/proposal/blob/master/A37-xds-aggregate-and-logical-dns-clusters.md) - [xDS Federation](https://github.com/grpc/proposal/blob/master/A47-xds-federation.md) (Currently experimental, enabled by environment variable `GRPC_EXPERIMENTAL_XDS_FEDERATION`) + - [xDS Custom Load Balancer Configuration](https://github.com/grpc/proposal/blob/master/A52-xds-custom-lb-policies.md) (Custom load balancer registration not currently supported) + - [xDS Ring Hash LB Policy](https://github.com/grpc/proposal/blob/master/A42-xds-ring-hash-lb-policy.md) + - [`pick_first` via xDS](https://github.com/grpc/proposal/blob/master/A62-pick-first.md#pick_first-via-xds-1) (Currently experimental, enabled by environment variable `GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG`) diff --git a/packages/grpc-js-xds/gulpfile.ts b/packages/grpc-js-xds/gulpfile.ts index becf109a6..47ca71324 100644 --- a/packages/grpc-js-xds/gulpfile.ts +++ b/packages/grpc-js-xds/gulpfile.ts @@ -62,10 +62,9 @@ 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'; + if (Number(process.versions.node.split('.')[0]) <= 14) { + process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'false'; } return gulp.src(`${outDir}/test/**/*.js`) .pipe(mocha({reporter: 'mocha-jenkins-reporter', diff --git a/packages/grpc-js-xds/src/environment.ts b/packages/grpc-js-xds/src/environment.ts index 02f14dabc..e32d788a6 100644 --- a/packages/grpc-js-xds/src/environment.ts +++ b/packages/grpc-js-xds/src/environment.ts @@ -15,10 +15,13 @@ * */ +/* Switches to enable or disable experimental features. If the default is + * 'true', the feature is enabled by default, if the default is 'false' the + * feature is disabled by default. */ export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true'; export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true'; export const EXPERIMENTAL_RETRY = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY ?? 'true') === 'true'; 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_CUSTOM_LB_CONFIG = (process.env.GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG ?? 'true') === 'true'; +export const EXPERIMENTAL_RING_HASH = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH ?? 'true') === 'true'; export const EXPERIMENTAL_PICK_FIRST = (process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG ?? 'false') === 'true'; From b1c45a819f117d38717a37e3e91be0ddefa8484c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 1 Feb 2024 13:41:28 -0800 Subject: [PATCH 661/694] grpc-js/grpc-js-xds: Bump version to 1.10.0 --- packages/grpc-js-xds/README.md | 2 +- packages/grpc-js-xds/package.json | 4 ++-- packages/grpc-js/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/grpc-js-xds/README.md b/packages/grpc-js-xds/README.md index c1db440cf..476dc077e 100644 --- a/packages/grpc-js-xds/README.md +++ b/packages/grpc-js-xds/README.md @@ -1,6 +1,6 @@ # @grpc/grpc-js xDS plugin -This package provides support for the `xds://` URL scheme to the `@grpc/grpc-js` library. The latest version of this package is compatible with `@grpc/grpc-js` version 1.9.x. +This package provides support for the `xds://` URL scheme to the `@grpc/grpc-js` library. The latest version of this package is compatible with `@grpc/grpc-js` version 1.10.x. ## Installation diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index c2f324b99..1286a16a8 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.9.2", + "version": "1.10.0", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -50,7 +50,7 @@ "xxhash-wasm": "^1.0.2" }, "peerDependencies": { - "@grpc/grpc-js": "~1.9.0" + "@grpc/grpc-js": "~1.10.0" }, "engines": { "node": ">=10.10.0" diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 8d8f4fd90..701df6723 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.9.14", + "version": "1.10.0", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From 429a66d1cbb06d8decfe9c5f08c07a951c3c895b Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 14 Feb 2024 11:05:26 -0800 Subject: [PATCH 662/694] grpc-js: round_robin: always have children reconnect immediately --- packages/grpc-js-xds/test/backend.ts | 7 ++--- packages/grpc-js-xds/test/test-core.ts | 28 +++++++++++++++++++ packages/grpc-js/package.json | 2 +- .../grpc-js/src/load-balancer-pick-first.ts | 4 +++ .../grpc-js/src/load-balancer-round-robin.ts | 9 ++++++ 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-xds/test/backend.ts b/packages/grpc-js-xds/test/backend.ts index 01474284b..59c23ad7d 100644 --- a/packages/grpc-js-xds/test/backend.ts +++ b/packages/grpc-js-xds/test/backend.ts @@ -15,7 +15,7 @@ * */ -import { loadPackageDefinition, sendUnaryData, Server, ServerCredentials, ServerUnaryCall, UntypedServiceImplementation } from "@grpc/grpc-js"; +import { loadPackageDefinition, sendUnaryData, Server, ServerCredentials, ServerOptions, ServerUnaryCall, UntypedServiceImplementation } from "@grpc/grpc-js"; import { loadSync } from "@grpc/proto-loader"; import { ProtoGrpcType } from "./generated/echo"; import { EchoRequest__Output } from "./generated/grpc/testing/EchoRequest"; @@ -43,7 +43,7 @@ export class Backend { private receivedCallCount = 0; private callListeners: (() => void)[] = []; private port: number | null = null; - constructor() { + constructor(private serverOptions?: ServerOptions) { } Echo(call: ServerUnaryCall, callback: sendUnaryData) { // call.request.params is currently ignored @@ -76,13 +76,12 @@ export class Backend { if (this.server) { throw new Error("Backend already running"); } - this.server = new Server(); + this.server = new Server(this.serverOptions); this.server.addService(loadedProtos.grpc.testing.EchoTestService.service, this as unknown as UntypedServiceImplementation); const boundPort = this.port ?? 0; this.server.bindAsync(`localhost:${boundPort}`, ServerCredentials.createInsecure(), (error, port) => { if (!error) { this.port = port; - this.server!.start(); } callback(error, port); }) diff --git a/packages/grpc-js-xds/test/test-core.ts b/packages/grpc-js-xds/test/test-core.ts index f48ab6c11..5d71ff8b8 100644 --- a/packages/grpc-js-xds/test/test-core.ts +++ b/packages/grpc-js-xds/test/test-core.ts @@ -91,4 +91,32 @@ describe('core xDS functionality', () => { }); }, reason => done(reason)); }); + it('should handle connections aging out', function(done) { + this.timeout(5000); + const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend({'grpc.max_connection_age_ms': 1000})], locality:{region: 'region1'}}]); + 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(error => { + assert.ifError(error); + // Make another call after the max_connection_age_ms expires + setTimeout(() => { + client.sendOneCall(error => { + done(error); + }) + }, 1100); + }); + }, reason => done(reason)); + + }) }); diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 701df6723..d1cb3d561 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.0", + "version": "1.10.1", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index c9224de6b..02796fea0 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -605,6 +605,10 @@ export class LeafLoadBalancer { return this.endpoint; } + exitIdle() { + this.pickFirstBalancer.exitIdle(); + } + destroy() { this.pickFirstBalancer.destroy(); } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 5ed26c9a5..7c38569e5 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -161,6 +161,15 @@ export class RoundRobinLoadBalancer implements LoadBalancer { } else { this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); } + /* round_robin should keep all children connected, this is how we do that. + * We can't do this more efficiently in the individual child's updateState + * callback because that doesn't have a reference to which child the state + * change is associated with. */ + for (const child of this.children) { + if (child.getConnectivityState() === ConnectivityState.IDLE) { + child.exitIdle(); + } + } } private updateState(newState: ConnectivityState, picker: Picker) { From 6c2bc599e55dc5b374603c2e5527b0165064b693 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 27 Feb 2024 12:51:38 -0800 Subject: [PATCH 663/694] grpc-js: Run code formatter, fix one lint error --- packages/grpc-js/src/backoff-timeout.ts | 4 +- packages/grpc-js/src/index.ts | 11 +- packages/grpc-js/src/internal-channel.ts | 8 +- .../grpc-js/src/load-balancer-pick-first.ts | 23 +- .../grpc-js/src/load-balancer-round-robin.ts | 4 +- packages/grpc-js/src/load-balancing-call.ts | 4 +- packages/grpc-js/src/logging.ts | 13 +- packages/grpc-js/src/resolver-dns.ts | 9 +- .../grpc-js/src/resolving-load-balancer.ts | 7 +- packages/grpc-js/src/server-call.ts | 15 +- packages/grpc-js/src/server-interceptors.ts | 186 +++++++---- packages/grpc-js/src/server.ts | 305 +++++++++++------- packages/grpc-js/src/service-config.ts | 15 +- packages/grpc-js/src/transport.ts | 7 +- packages/grpc-js/test/common.ts | 5 +- packages/grpc-js/test/test-confg-parsing.ts | 89 ++--- packages/grpc-js/test/test-pick-first.ts | 73 +++-- .../grpc-js/test/test-server-interceptors.ts | 135 +++++--- packages/grpc-js/test/test-server.ts | 139 +++++--- 19 files changed, 698 insertions(+), 354 deletions(-) diff --git a/packages/grpc-js/src/backoff-timeout.ts b/packages/grpc-js/src/backoff-timeout.ts index 78318d1e8..10d347e79 100644 --- a/packages/grpc-js/src/backoff-timeout.ts +++ b/packages/grpc-js/src/backoff-timeout.ts @@ -106,7 +106,9 @@ export class BackoffTimeout { private runTimer(delay: number) { this.endTime = this.startTime; - this.endTime.setMilliseconds(this.endTime.getMilliseconds() + this.nextDelay); + this.endTime.setMilliseconds( + this.endTime.getMilliseconds() + this.nextDelay + ); clearTimeout(this.timerId); this.timerId = setTimeout(() => { this.callback(); diff --git a/packages/grpc-js/src/index.ts b/packages/grpc-js/src/index.ts index b37f61103..c766a3718 100644 --- a/packages/grpc-js/src/index.ts +++ b/packages/grpc-js/src/index.ts @@ -183,7 +183,7 @@ export { ServiceDefinition, UntypedHandleCall, UntypedServiceImplementation, - VerifyOptions + VerifyOptions, }; /**** Server ****/ @@ -263,7 +263,12 @@ export { getChannelzServiceDefinition, getChannelzHandlers } from './channelz'; export { addAdminServicesToServer } from './admin'; -export { ServiceConfig, LoadBalancingConfig, MethodConfig, RetryPolicy } from './service-config'; +export { + ServiceConfig, + LoadBalancingConfig, + MethodConfig, + RetryPolicy, +} from './service-config'; export { ServerListener, @@ -274,7 +279,7 @@ export { ResponderBuilder, ServerInterceptingCallInterface, ServerInterceptingCall, - ServerInterceptor + ServerInterceptor, } from './server-interceptors'; import * as experimental from './experimental'; diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index be140522b..823c935af 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -583,7 +583,8 @@ export class InternalChannel { return; } const now = new Date(); - const timeSinceLastActivity = now.valueOf() - this.lastActivityTimestamp.valueOf(); + const timeSinceLastActivity = + now.valueOf() - this.lastActivityTimestamp.valueOf(); if (timeSinceLastActivity >= this.idleTimeoutMs) { this.trace( 'Idle timer triggered after ' + @@ -603,7 +604,10 @@ export class InternalChannel { } private maybeStartIdleTimer() { - if (this.connectivityState !== ConnectivityState.SHUTDOWN && !this.idleTimer) { + if ( + this.connectivityState !== ConnectivityState.SHUTDOWN && + !this.idleTimer + ) { this.startIdleTimeout(this.idleTimeoutMs); } } diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 02796fea0..29bbfbf07 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -198,7 +198,12 @@ export class PickFirstLoadBalancer implements LoadBalancer { keepaliveTime, errorMessage ) => { - this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage); + this.onSubchannelStateUpdate( + subchannel, + previousState, + newState, + errorMessage + ); }; private pickedSubchannelHealthListener: HealthListener = () => @@ -275,7 +280,9 @@ export class PickFirstLoadBalancer implements LoadBalancer { if (this.stickyTransientFailureMode) { this.updateState( ConnectivityState.TRANSIENT_FAILURE, - new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`}) + new UnavailablePicker({ + details: `No connection established. Last error: ${this.lastError}`, + }) ); } else { this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this)); @@ -441,7 +448,12 @@ export class PickFirstLoadBalancer implements LoadBalancer { private resetSubchannelList() { for (const child of this.children) { - if (!(this.currentPick && child.subchannel.realSubchannelEquals(this.currentPick))) { + if ( + !( + this.currentPick && + child.subchannel.realSubchannelEquals(this.currentPick) + ) + ) { /* The connectivity state listener is the same whether the subchannel * is in the list of children or it is the currentPick, so if it is in * both, removing it here would cause problems. In particular, that @@ -523,7 +535,10 @@ export class PickFirstLoadBalancer implements LoadBalancer { } exitIdle() { - if (this.currentState === ConnectivityState.IDLE && this.latestAddressList) { + if ( + this.currentState === ConnectivityState.IDLE && + this.latestAddressList + ) { this.connectToAddressList(this.latestAddressList); } } diff --git a/packages/grpc-js/src/load-balancer-round-robin.ts b/packages/grpc-js/src/load-balancer-round-robin.ts index 7c38569e5..7e70c554f 100644 --- a/packages/grpc-js/src/load-balancer-round-robin.ts +++ b/packages/grpc-js/src/load-balancer-round-robin.ts @@ -156,7 +156,9 @@ export class RoundRobinLoadBalancer implements LoadBalancer { ) { this.updateState( ConnectivityState.TRANSIENT_FAILURE, - new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`}) + new UnavailablePicker({ + details: `No connection established. Last error: ${this.lastError}`, + }) ); } else { this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 87ef02497..25a36553a 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -145,7 +145,9 @@ export class LoadBalancingCall implements Call { * metadata generation finished, we shouldn't do anything with * it. */ if (this.ended) { - this.trace('Credentials metadata generation finished after call ended'); + this.trace( + 'Credentials metadata generation finished after call ended' + ); return; } finalMetadata.merge(credsMetadata); diff --git a/packages/grpc-js/src/logging.ts b/packages/grpc-js/src/logging.ts index e1b396fff..2279d3b65 100644 --- a/packages/grpc-js/src/logging.ts +++ b/packages/grpc-js/src/logging.ts @@ -112,7 +112,18 @@ export function trace( text: string ): void { if (isTracerEnabled(tracer)) { - log(severity, new Date().toISOString() + ' | v' + clientVersion + ' ' + pid + ' | ' + tracer + ' | ' + text); + log( + severity, + new Date().toISOString() + + ' | v' + + clientVersion + + ' ' + + pid + + ' | ' + + tracer + + ' | ' + + text + ); } } diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 978f1442a..6652839b0 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -335,9 +335,14 @@ class DnsResolver implements Resolver { if (this.pendingLookupPromise === null) { if (this.isNextResolutionTimerRunning || this.backoff.isRunning()) { if (this.isNextResolutionTimerRunning) { - trace('resolution update delayed by "min time between resolutions" rate limit'); + trace( + 'resolution update delayed by "min time between resolutions" rate limit' + ); } else { - trace('resolution update delayed by backoff timer until ' + this.backoff.getEndTime().toISOString()); + trace( + 'resolution update delayed by backoff timer until ' + + this.backoff.getEndTime().toISOString() + ); } this.continueResolving = true; } else { diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index a8de2019a..82c4ff436 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -223,8 +223,11 @@ export class ResolvingLoadBalancer implements LoadBalancer { * In that case, the backoff timer callback will call * updateResolution */ if (this.backoffTimeout.isRunning()) { - trace('requestReresolution delayed by backoff timer until ' + this.backoffTimeout.getEndTime().toISOString()); - this.continueResolving = true; + trace( + 'requestReresolution delayed by backoff timer until ' + + this.backoffTimeout.getEndTime().toISOString() + ); + this.continueResolving = true; } else { this.updateResolution(); } diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index edc38e983..95393fba9 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -18,9 +18,7 @@ import { EventEmitter } from 'events'; import { Duplex, Readable, Writable } from 'stream'; -import { - Status, -} from './constants'; +import { Status } from './constants'; import { Deserialize, Serialize } from './make-client'; import { Metadata } from './metadata'; import { ObjectReadable, ObjectWritable } from './object-stream'; @@ -56,11 +54,14 @@ export type ServerDuplexStream = ServerSurfaceCall & ObjectReadable & ObjectWritable & { end: (metadata?: Metadata) => void }; -export function serverErrorToStatus(error: ServerErrorResponse | ServerStatusResponse, overrideTrailers?: Metadata | undefined): PartialStatusObject { +export function serverErrorToStatus( + error: ServerErrorResponse | ServerStatusResponse, + overrideTrailers?: Metadata | undefined +): PartialStatusObject { const status: PartialStatusObject = { code: Status.UNKNOWN, details: 'message' in error ? error.message : 'Unknown Error', - metadata: overrideTrailers ?? error.metadata ?? null + metadata: overrideTrailers ?? error.metadata ?? null, }; if ( @@ -154,7 +155,7 @@ export class ServerWritableStreamImpl private trailingMetadata: Metadata; private pendingStatus: PartialStatusObject = { code: Status.OK, - details: 'OK' + details: 'OK', }; constructor( @@ -224,7 +225,7 @@ export class ServerDuplexStreamImpl private trailingMetadata: Metadata; private pendingStatus: PartialStatusObject = { code: Status.OK, - details: 'OK' + details: 'OK', }; constructor( diff --git a/packages/grpc-js/src/server-interceptors.ts b/packages/grpc-js/src/server-interceptors.ts index c03f3028c..60c5c8865 100644 --- a/packages/grpc-js/src/server-interceptors.ts +++ b/packages/grpc-js/src/server-interceptors.ts @@ -15,19 +15,24 @@ * */ -import { PartialStatusObject} from "./call-interface"; -import { ServerMethodDefinition } from "./make-client"; -import { Metadata } from "./metadata"; -import { ChannelOptions } from "./channel-options"; -import { Handler, ServerErrorResponse } from "./server-call"; -import { Deadline } from "./deadline"; -import { DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, DEFAULT_MAX_SEND_MESSAGE_LENGTH, LogVerbosity, Status } from "./constants"; +import { PartialStatusObject } from './call-interface'; +import { ServerMethodDefinition } from './make-client'; +import { Metadata } from './metadata'; +import { ChannelOptions } from './channel-options'; +import { Handler, ServerErrorResponse } from './server-call'; +import { Deadline } from './deadline'; +import { + DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, + DEFAULT_MAX_SEND_MESSAGE_LENGTH, + LogVerbosity, + Status, +} from './constants'; import * as http2 from 'http2'; -import { getErrorMessage } from "./error"; +import { getErrorMessage } from './error'; import * as zlib from 'zlib'; -import { promisify } from "util"; -import { StreamDecoder } from "./stream-decoder"; -import { CallEventTracker } from "./transport"; +import { promisify } from 'util'; +import { StreamDecoder } from './stream-decoder'; +import { CallEventTracker } from './transport'; import * as logging from './logging'; const unzip = promisify(zlib.unzip); @@ -96,7 +101,7 @@ export class ServerListenerBuilder { onReceiveMetadata: this.metadata, onReceiveMessage: this.message, onReceiveHalfClose: this.halfClose, - onCancel: this.cancel + onCancel: this.cancel, }; } } @@ -109,22 +114,30 @@ export interface InterceptingServerListener { onCancel(): void; } -export function isInterceptingServerListener(listener: ServerListener | InterceptingServerListener): listener is InterceptingServerListener { - return listener.onReceiveMetadata !== undefined && listener.onReceiveMetadata.length === 1; +export function isInterceptingServerListener( + listener: ServerListener | InterceptingServerListener +): listener is InterceptingServerListener { + return ( + listener.onReceiveMetadata !== undefined && + listener.onReceiveMetadata.length === 1 + ); } class InterceptingServerListenerImpl implements InterceptingServerListener { /** * Once the call is cancelled, ignore all other events. */ - private cancelled: boolean = false; - private processingMetadata: boolean = false; - private hasPendingMessage: boolean = false; + private cancelled = false; + private processingMetadata = false; + private hasPendingMessage = false; private pendingMessage: any = null; - private processingMessage: boolean = false; - private hasPendingHalfClose: boolean = false; + private processingMessage = false; + private hasPendingHalfClose = false; - constructor(private listener: FullServerListener, private nextListener: InterceptingServerListener) {} + constructor( + private listener: FullServerListener, + private nextListener: InterceptingServerListener + ) {} private processPendingMessage() { if (this.hasPendingMessage) { @@ -195,7 +208,6 @@ class InterceptingServerListenerImpl implements InterceptingServerListener { this.listener.onCancel(); this.nextListener.onCancel(); } - } export interface StartResponder { @@ -212,7 +224,10 @@ export interface MessageResponder { } export interface StatusResponder { - (status: PartialStatusObject, next: (status: PartialStatusObject) => void): void; + ( + status: PartialStatusObject, + next: (status: PartialStatusObject) => void + ): void; } export interface FullResponder { @@ -255,7 +270,7 @@ export class ResponderBuilder { start: this.start, sendMetadata: this.metadata, sendMessage: this.message, - sendStatus: this.status + sendStatus: this.status, }; } } @@ -270,11 +285,11 @@ const defaultServerListener: FullServerListener = { onReceiveHalfClose: next => { next(); }, - onCancel: () => {} + onCancel: () => {}, }; const defaultResponder: FullResponder = { - start: (next) => { + start: next => { next(); }, sendMetadata: (metadata, next) => { @@ -285,7 +300,7 @@ const defaultResponder: FullResponder = { }, sendStatus: (status, next) => { next(status); - } + }, }; export interface ServerInterceptingCallInterface { @@ -321,18 +336,24 @@ export interface ServerInterceptingCallInterface { export class ServerInterceptingCall implements ServerInterceptingCallInterface { private responder: FullResponder; - private processingMetadata: boolean = false; - private processingMessage: boolean = false; + private processingMetadata = false; + private processingMessage = false; private pendingMessage: any = null; private pendingMessageCallback: (() => void) | null = null; private pendingStatus: PartialStatusObject | null = null; - constructor(private nextCall: ServerInterceptingCallInterface, responder?: Responder) { - this.responder = {...defaultResponder, ...responder}; + constructor( + private nextCall: ServerInterceptingCallInterface, + responder?: Responder + ) { + this.responder = { ...defaultResponder, ...responder }; } private processPendingMessage() { if (this.pendingMessageCallback) { - this.nextCall.sendMessage(this.pendingMessage, this.pendingMessageCallback); + this.nextCall.sendMessage( + this.pendingMessage, + this.pendingMessageCallback + ); this.pendingMessage = null; this.pendingMessageCallback = null; } @@ -347,8 +368,14 @@ export class ServerInterceptingCall implements ServerInterceptingCallInterface { start(listener: InterceptingServerListener): void { this.responder.start(interceptedListener => { - const fullInterceptedListener: FullServerListener = {...defaultServerListener, ...interceptedListener}; - const finalInterceptingListener = new InterceptingServerListenerImpl(fullInterceptedListener, listener); + const fullInterceptedListener: FullServerListener = { + ...defaultServerListener, + ...interceptedListener, + }; + const finalInterceptingListener = new InterceptingServerListenerImpl( + fullInterceptedListener, + listener + ); this.nextCall.start(finalInterceptingListener); }); } @@ -394,7 +421,10 @@ export class ServerInterceptingCall implements ServerInterceptingCallInterface { } export interface ServerInterceptor { - (methodDescriptor: ServerMethodDefinition, call: ServerInterceptingCallInterface): ServerInterceptingCall; + ( + methodDescriptor: ServerMethodDefinition, + call: ServerInterceptingCallInterface + ): ServerInterceptingCall; } interface DeadlineUnitIndexSignature { @@ -438,7 +468,9 @@ interface ReadQueueEntry { parsedMessage: any; } -export class BaseServerInterceptingCall implements ServerInterceptingCallInterface { +export class BaseServerInterceptingCall + implements ServerInterceptingCallInterface +{ private listener: InterceptingServerListener | null = null; private metadata: Metadata; private deadlineTimer: NodeJS.Timeout | null = null; @@ -449,7 +481,7 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa private metadataSent = false; private wantTrailers = false; private cancelNotified = false; - private incomingEncoding: string = 'identity'; + private incomingEncoding = 'identity'; private decoder = new StreamDecoder(); private readQueue: ReadQueueEntry[] = []; private isReadPending = false; @@ -485,7 +517,7 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa this.callEventTracker.onCallEnd({ code: Status.CANCELLED, details: 'Stream closed before sending status', - metadata: null + metadata: null, }); } @@ -548,7 +580,7 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa const status: PartialStatusObject = { code: Status.INTERNAL, details: `Invalid ${GRPC_TIMEOUT_HEADER} value "${timeoutHeader}"`, - metadata: null + metadata: null, }; // Wait for the constructor to complete before sending the error. process.nextTick(() => { @@ -565,11 +597,10 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa const status: PartialStatusObject = { code: Status.DEADLINE_EXCEEDED, details: 'Deadline exceeded', - metadata: null + metadata: null, }; this.sendStatus(status); }, timeout); - } private checkCancelled(): boolean { @@ -650,14 +681,19 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa } const compressed = queueEntry.compressedMessage!.readUInt8(0) === 1; - const compressedMessageEncoding = compressed ? this.incomingEncoding : 'identity'; - const decompressedMessage = await this.decompressMessage(queueEntry.compressedMessage!, compressedMessageEncoding); + const compressedMessageEncoding = compressed + ? this.incomingEncoding + : 'identity'; + const decompressedMessage = await this.decompressMessage( + queueEntry.compressedMessage!, + compressedMessageEncoding + ); try { queueEntry.parsedMessage = this.handler.deserialize(decompressedMessage); } catch (err) { this.sendStatus({ code: Status.INTERNAL, - details: `Error deserializing request: ${(err as Error).message}` + details: `Error deserializing request: ${(err as Error).message}`, }); return; } @@ -666,7 +702,12 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa } private maybePushNextMessage() { - if (this.listener && this.isReadPending && this.readQueue.length > 0 && this.readQueue[0].type !== 'COMPRESSED') { + if ( + this.listener && + this.isReadPending && + this.readQueue.length > 0 && + this.readQueue[0].type !== 'COMPRESSED' + ) { this.isReadPending = false; const nextQueueEntry = this.readQueue.shift()!; if (nextQueueEntry.type === 'READABLE') { @@ -682,23 +723,33 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa if (this.checkCancelled()) { return; } - trace('Request to ' + this.handler.path + ' received data frame of size ' + data.length); + trace( + 'Request to ' + + this.handler.path + + ' received data frame of size ' + + data.length + ); const rawMessages = this.decoder.write(data); for (const messageBytes of rawMessages) { this.stream.pause(); - if (this.maxReceiveMessageSize !== -1 && messageBytes.length - 5 > this.maxReceiveMessageSize) { + if ( + this.maxReceiveMessageSize !== -1 && + messageBytes.length - 5 > this.maxReceiveMessageSize + ) { this.sendStatus({ code: Status.RESOURCE_EXHAUSTED, - details: `Received message larger than max (${messageBytes.length - 5} vs. ${this.maxReceiveMessageSize})`, - metadata: null + details: `Received message larger than max (${ + messageBytes.length - 5 + } vs. ${this.maxReceiveMessageSize})`, + metadata: null, }); return; } const queueEntry: ReadQueueEntry = { type: 'COMPRESSED', compressedMessage: messageBytes, - parsedMessage: null + parsedMessage: null, }; this.readQueue.push(queueEntry); this.decompressAndMaybePush(queueEntry); @@ -709,7 +760,7 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa this.readQueue.push({ type: 'HALF_CLOSE', compressedMessage: null, - parsedMessage: null + parsedMessage: null, }); this.receivedHalfClose = true; this.maybePushNextMessage(); @@ -751,7 +802,7 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa this.sendStatus({ code: Status.INTERNAL, details: `Error serializing response: ${getErrorMessage(e)}`, - metadata: null + metadata: null, }); return; } @@ -763,18 +814,23 @@ export class BaseServerInterceptingCall implements ServerInterceptingCallInterfa this.sendStatus({ code: Status.RESOURCE_EXHAUSTED, details: `Sent message larger than max (${response.length} vs. ${this.maxSendMessageSize})`, - metadata: null + metadata: null, }); return; } this.maybeSendMetadata(); - trace('Request to ' + this.handler.path + ' sent data frame of size ' + response.length); + trace( + 'Request to ' + + this.handler.path + + ' sent data frame of size ' + + response.length + ); this.stream.write(response, error => { if (error) { this.sendStatus({ code: Status.INTERNAL, details: `Error writing message: ${getErrorMessage(error)}`, - metadata: null + metadata: null, }); return; } @@ -871,16 +927,24 @@ export function getServerInterceptingCall( handler: Handler, options: ChannelOptions ) { - const methodDefinition: ServerMethodDefinition = { path: handler.path, requestStream: handler.type === 'clientStream' || handler.type === 'bidi', responseStream: handler.type === 'serverStream' || handler.type === 'bidi', requestDeserialize: handler.deserialize, - responseSerialize: handler.serialize - } - const baseCall = new BaseServerInterceptingCall(stream, headers, callEventTracker, handler, options); - return interceptors.reduce((call: ServerInterceptingCallInterface, interceptor: ServerInterceptor) => { - return interceptor(methodDefinition, call); - }, baseCall); + responseSerialize: handler.serialize, + }; + const baseCall = new BaseServerInterceptingCall( + stream, + headers, + callEventTracker, + handler, + options + ); + return interceptors.reduce( + (call: ServerInterceptingCallInterface, interceptor: ServerInterceptor) => { + return interceptor(methodDefinition, call); + }, + baseCall + ); } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 31851b832..46bd22ead 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -55,7 +55,13 @@ import { subchannelAddressToString, stringToSubchannelAddress, } from './subchannel-address'; -import { GrpcUri, combineHostPort, parseUri, splitHostPort, uriToString } from './uri-parser'; +import { + GrpcUri, + combineHostPort, + parseUri, + splitHostPort, + uriToString, +} from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, @@ -70,7 +76,11 @@ import { unregisterChannelzRef, } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; -import { ServerInterceptingCallInterface, ServerInterceptor, getServerInterceptingCall } from './server-interceptors'; +import { + ServerInterceptingCallInterface, + ServerInterceptor, + getServerInterceptingCall, +} from './server-interceptors'; import { PartialStatusObject } from './call-interface'; import { CallEventTracker } from './transport'; @@ -103,9 +113,15 @@ function noop(): void {} * @returns */ function deprecate(message: string) { - return function (target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext Return>) { + return function ( + target: (this: This, ...args: Args) => Return, + context: ClassMethodDecoratorContext< + This, + (this: This, ...args: Args) => Return + > + ) { return util.deprecate(target, message); - } + }; } function getUnimplementedStatusResponse( @@ -209,7 +225,7 @@ interface BoundPort { * that expands to multiple addresses will result in multiple listening * servers. */ - listeningServers: Set + listeningServers: Set; } /** @@ -221,11 +237,11 @@ interface Http2ServerInfo { } export interface ServerOptions extends ChannelOptions { - interceptors?: ServerInterceptor[] + interceptors?: ServerInterceptor[]; } export class Server { - private boundPorts: Map= new Map(); + private boundPorts: Map = new Map(); private http2Servers: Map = new Map(); private handlers: Map = new Map< @@ -526,10 +542,11 @@ export class Server { return http2Server; } - private bindOneAddress(address: SubchannelAddress, boundPortObject: BoundPort): Promise { - this.trace( - 'Attempting to bind ' + subchannelAddressToString(address) - ); + private bindOneAddress( + address: SubchannelAddress, + boundPortObject: BoundPort + ): Promise { + this.trace('Attempting to bind ' + subchannelAddressToString(address)); const http2Server = this.createHttp2Server(boundPortObject.credentials); return new Promise((resolve, reject) => { const onError = (err: Error) => { @@ -541,7 +558,7 @@ export class Server { ); resolve({ port: 'port' in address ? address.port : 1, - error: err.message + error: err.message, }); }; @@ -561,13 +578,15 @@ export class Server { }; } - const channelzRef = this.registerListenerToChannelz(boundSubchannelAddress); + const channelzRef = this.registerListenerToChannelz( + boundSubchannelAddress + ); if (this.channelzEnabled) { this.listenerChildrenTracker.refChild(channelzRef); } this.http2Servers.set(http2Server, { channelzRef: channelzRef, - sessions: new Set() + sessions: new Set(), }); boundPortObject.listeningServers.add(http2Server); this.trace( @@ -575,62 +594,86 @@ export class Server { subchannelAddressToString(boundSubchannelAddress) ); resolve({ - port: 'port' in boundSubchannelAddress - ? boundSubchannelAddress.port - : 1 + port: + 'port' in boundSubchannelAddress ? boundSubchannelAddress.port : 1, }); http2Server.removeListener('error', onError); }); }); } - private async bindManyPorts(addressList: SubchannelAddress[], boundPortObject: BoundPort): Promise { + private async bindManyPorts( + addressList: SubchannelAddress[], + boundPortObject: BoundPort + ): Promise { if (addressList.length === 0) { return { count: 0, port: 0, - errors: [] + errors: [], }; } if (isTcpSubchannelAddress(addressList[0]) && addressList[0].port === 0) { /* If binding to port 0, first try to bind the first address, then bind * the rest of the address list to the specific port that it binds. */ - const firstAddressResult = await this.bindOneAddress(addressList[0], boundPortObject); + const firstAddressResult = await this.bindOneAddress( + addressList[0], + boundPortObject + ); if (firstAddressResult.error) { /* If the first address fails to bind, try the same operation starting * from the second item in the list. */ - const restAddressResult = await this.bindManyPorts(addressList.slice(1), boundPortObject); + const restAddressResult = await this.bindManyPorts( + addressList.slice(1), + boundPortObject + ); return { ...restAddressResult, - errors: [firstAddressResult.error, ...restAddressResult.errors] + errors: [firstAddressResult.error, ...restAddressResult.errors], }; } else { - const restAddresses = addressList.slice(1).map(address => isTcpSubchannelAddress(address) ? {host: address.host, port: firstAddressResult.port} : address) - const restAddressResult = await Promise.all(restAddresses.map(address => this.bindOneAddress(address, boundPortObject))); + const restAddresses = addressList + .slice(1) + .map(address => + isTcpSubchannelAddress(address) + ? { host: address.host, port: firstAddressResult.port } + : address + ); + const restAddressResult = await Promise.all( + restAddresses.map(address => + this.bindOneAddress(address, boundPortObject) + ) + ); const allResults = [firstAddressResult, ...restAddressResult]; return { count: allResults.filter(result => result.error === undefined).length, port: firstAddressResult.port, - errors: allResults.filter(result => result.error).map(result => result.error!) + errors: allResults + .filter(result => result.error) + .map(result => result.error!), }; } } else { - const allResults = await Promise.all(addressList.map(address => this.bindOneAddress(address, boundPortObject))); + const allResults = await Promise.all( + addressList.map(address => + this.bindOneAddress(address, boundPortObject) + ) + ); return { count: allResults.filter(result => result.error === undefined).length, port: allResults[0].port, - errors: allResults.filter(result => result.error).map(result => result.error!) + errors: allResults + .filter(result => result.error) + .map(result => result.error!), }; } } - private async bindAddressList(addressList: SubchannelAddress[], boundPortObject: BoundPort): Promise { - let bindResult: BindResult; - try { - bindResult = await this.bindManyPorts(addressList, boundPortObject); - } catch (error) { - throw error; - } + private async bindAddressList( + addressList: SubchannelAddress[], + boundPortObject: BoundPort + ): Promise { + const bindResult = await this.bindManyPorts(addressList, boundPortObject); if (bindResult.count > 0) { if (bindResult.count < addressList.length) { logging.log( @@ -642,7 +685,9 @@ export class Server { } else { const errorString = `No address added out of total ${addressList.length} resolved`; logging.log(LogVerbosity.ERROR, errorString); - throw new Error(`${errorString} errors: [${bindResult.errors.join(',')}]`); + throw new Error( + `${errorString} errors: [${bindResult.errors.join(',')}]` + ); } } @@ -660,9 +705,7 @@ export class Server { ...endpointList.map(endpoint => endpoint.addresses) ); if (addressList.length === 0) { - reject( - new Error(`No addresses resolved for port ${port}`) - ); + reject(new Error(`No addresses resolved for port ${port}`)); return; } resolve(addressList); @@ -676,7 +719,10 @@ export class Server { }); } - private async bindPort(port: GrpcUri, boundPortObject: BoundPort): Promise { + private async bindPort( + port: GrpcUri, + boundPortObject: BoundPort + ): Promise { const addressList = await this.resolvePort(port); if (boundPortObject.cancelled) { this.completeUnbind(boundPortObject); @@ -691,7 +737,6 @@ export class Server { } private normalizePort(port: string): GrpcUri { - const initialPortUri = parseUri(port); if (initialPortUri === null) { throw new Error(`Could not parse port "${port}"`); @@ -736,14 +781,20 @@ export class Server { let boundPortObject = this.boundPorts.get(uriToString(portUri)); if (boundPortObject) { if (!creds._equals(boundPortObject.credentials)) { - deferredCallback(new Error(`${port} already bound with incompatible credentials`), 0); + deferredCallback( + new Error(`${port} already bound with incompatible credentials`), + 0 + ); return; } /* If that operation has previously been cancelled by an unbind call, * uncancel it. */ boundPortObject.cancelled = false; if (boundPortObject.completionPromise) { - boundPortObject.completionPromise.then(portNum => callback(null, portNum), error => callback(error as Error, 0)); + boundPortObject.completionPromise.then( + portNum => callback(null, portNum), + error => callback(error as Error, 0) + ); } else { deferredCallback(null, boundPortObject.portNumber); } @@ -756,7 +807,7 @@ export class Server { cancelled: false, portNumber: 0, credentials: creds, - listeningServers: new Set() + listeningServers: new Set(), }; const splitPort = splitHostPort(portUri.path); const completionPromise = this.bindPort(portUri, boundPortObject); @@ -765,34 +816,42 @@ export class Server { * bind operation completes and we have a specific port number. Otherwise, * populate it immediately. */ if (splitPort?.port === 0) { - completionPromise.then(portNum => { - const finalUri: GrpcUri = { - scheme: portUri.scheme, - authority: portUri.authority, - path: combineHostPort({host: splitPort.host, port: portNum}) - }; - boundPortObject!.mapKey = uriToString(finalUri); - boundPortObject!.completionPromise = null; - boundPortObject!.portNumber = portNum; - this.boundPorts.set(boundPortObject!.mapKey, boundPortObject!); - callback(null, portNum); - }, error => { - callback(error, 0); - }) + completionPromise.then( + portNum => { + const finalUri: GrpcUri = { + scheme: portUri.scheme, + authority: portUri.authority, + path: combineHostPort({ host: splitPort.host, port: portNum }), + }; + boundPortObject!.mapKey = uriToString(finalUri); + boundPortObject!.completionPromise = null; + boundPortObject!.portNumber = portNum; + this.boundPorts.set(boundPortObject!.mapKey, boundPortObject!); + callback(null, portNum); + }, + error => { + callback(error, 0); + } + ); } else { this.boundPorts.set(boundPortObject.mapKey, boundPortObject); - completionPromise.then(portNum => { - boundPortObject!.completionPromise = null; - boundPortObject!.portNumber = portNum; - callback(null, portNum); - }, error => { - callback(error, 0); - }); + completionPromise.then( + portNum => { + boundPortObject!.completionPromise = null; + boundPortObject!.portNumber = portNum; + callback(null, portNum); + }, + error => { + callback(error, 0); + } + ); } } private closeServer(server: AnyHttp2Server, callback?: () => void) { - this.trace('Closing server with address ' + JSON.stringify(server.address())); + this.trace( + 'Closing server with address ' + JSON.stringify(server.address()) + ); const serverInfo = this.http2Servers.get(server); server.close(() => { if (this.channelzEnabled && serverInfo) { @@ -802,10 +861,12 @@ export class Server { this.http2Servers.delete(server); callback?.(); }); - } - private closeSession(session: http2.ServerHttp2Session, callback?: () => void) { + private closeSession( + session: http2.ServerHttp2Session, + callback?: () => void + ) { this.trace('Closing session initiated by ' + session.socket?.remoteAddress); const sessionInfo = this.sessions.get(session); const closeCallback = () => { @@ -854,7 +915,12 @@ export class Server { } const boundPortObject = this.boundPorts.get(uriToString(portUri)); if (boundPortObject) { - this.trace('unbinding ' + boundPortObject.mapKey + ' originally bound as ' + uriToString(boundPortObject.originalUri)); + this.trace( + 'unbinding ' + + boundPortObject.mapKey + + ' originally bound as ' + + uriToString(boundPortObject.originalUri) + ); /* If the bind operation is pending, the cancelled flag will trigger * the unbind operation later. */ if (boundPortObject.completionPromise) { @@ -964,13 +1030,13 @@ export class Server { /** * @deprecated No longer needed as of version 1.10.x */ - @deprecate('Calling start() is no longer necessary. It can be safely omitted.') + @deprecate( + 'Calling start() is no longer necessary. It can be safely omitted.' + ) start(): void { if ( this.http2Servers.size === 0 || - [...this.http2Servers.keys()].every( - server => !server.listening - ) + [...this.http2Servers.keys()].every(server => !server.listening) ) { throw new Error('server must be bound in order to start'); } @@ -1090,9 +1156,9 @@ export class Server { 'grpc-message': err.details, [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto', - ...err.metadata?.toHttp2Headers() + ...err.metadata?.toHttp2Headers(), }; - stream.respond(trailersToSend, {endStream: true}); + stream.respond(trailersToSend, { endStream: true }); if (this.channelzEnabled) { this.callTracker.addCallFailed(); @@ -1129,7 +1195,7 @@ export class Server { return; } - let callEventTracker: CallEventTracker = { + const callEventTracker: CallEventTracker = { addMessageSent: () => { if (channelzSessionInfo) { channelzSessionInfo.messagesSent += 1; @@ -1157,10 +1223,17 @@ export class Server { channelzSessionInfo.streamTracker.addCallFailed(); } } - } - } + }, + }; - const call = getServerInterceptingCall(this.interceptors, stream, headers, callEventTracker, handler, this.options); + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + callEventTracker, + handler, + this.options + ); if (!this._runHandlerForCall(call, handler)) { this.callTracker.addCallFailed(); @@ -1193,7 +1266,14 @@ export class Server { return; } - const call = getServerInterceptingCall(this.interceptors, stream, headers, null, handler, this.options); + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + null, + handler, + this.options + ); if (!this._runHandlerForCall(call, handler)) { call.sendStatus({ @@ -1207,25 +1287,15 @@ export class Server { call: ServerInterceptingCallInterface, handler: Handler ): boolean { - const { type } = handler; if (type === 'unary') { handleUnary(call, handler as UntypedUnaryHandler); } else if (type === 'clientStream') { - handleClientStreaming( - call, - handler as UntypedClientStreamingHandler - ); + handleClientStreaming(call, handler as UntypedClientStreamingHandler); } else if (type === 'serverStream') { - handleServerStreaming( - call, - handler as UntypedServerStreamingHandler - ); + handleServerStreaming(call, handler as UntypedServerStreamingHandler); } else if (type === 'bidi') { - handleBidiStreaming( - call, - handler as UntypedBidiStreamingHandler - ); + handleBidiStreaming(call, handler as UntypedBidiStreamingHandler); } else { return false; } @@ -1257,7 +1327,6 @@ export class Server { http2Server.on('stream', handler.bind(this)); http2Server.on('session', session => { - const channelzRef = registerChannelzSocket( session.socket.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session), @@ -1388,7 +1457,7 @@ async function handleUnary( call.sendStatus({ code: Status.OK, details: 'OK', - metadata: trailer ?? null + metadata: trailer ?? null, }); }); } @@ -1405,7 +1474,7 @@ async function handleUnary( call.sendStatus({ code: Status.UNIMPLEMENTED, details: `Received a second request message for server streaming method ${handler.path}`, - metadata: null + metadata: null, }); return; } @@ -1417,18 +1486,25 @@ async function handleUnary( call.sendStatus({ code: Status.UNIMPLEMENTED, details: `Received no request message for server streaming method ${handler.path}`, - metadata: null + metadata: null, }); return; } - stream = new ServerWritableStreamImpl(handler.path, call, requestMetadata, requestMessage); + stream = new ServerWritableStreamImpl( + handler.path, + call, + requestMetadata, + requestMessage + ); try { handler.func(stream, respond); } catch (err) { call.sendStatus({ code: Status.UNKNOWN, - details: `Server method handler threw error ${(err as Error).message}`, - metadata: null + details: `Server method handler threw error ${ + (err as Error).message + }`, + metadata: null, }); } }, @@ -1461,7 +1537,7 @@ function handleClientStreaming( call.sendStatus({ code: Status.OK, details: 'OK', - metadata: trailer ?? null + metadata: trailer ?? null, }); }); } @@ -1474,8 +1550,10 @@ function handleClientStreaming( } catch (err) { call.sendStatus({ code: Status.UNKNOWN, - details: `Server method handler threw error ${(err as Error).message}`, - metadata: null + details: `Server method handler threw error ${ + (err as Error).message + }`, + metadata: null, }); } }, @@ -1513,7 +1591,7 @@ function handleServerStreaming( call.sendStatus({ code: Status.UNIMPLEMENTED, details: `Received a second request message for server streaming method ${handler.path}`, - metadata: null + metadata: null, }); return; } @@ -1525,18 +1603,25 @@ function handleServerStreaming( call.sendStatus({ code: Status.UNIMPLEMENTED, details: `Received no request message for server streaming method ${handler.path}`, - metadata: null + metadata: null, }); return; } - stream = new ServerWritableStreamImpl(handler.path, call, requestMetadata, requestMessage); + stream = new ServerWritableStreamImpl( + handler.path, + call, + requestMetadata, + requestMessage + ); try { handler.func(stream); } catch (err) { call.sendStatus({ code: Status.UNKNOWN, - details: `Server method handler threw error ${(err as Error).message}`, - metadata: null + details: `Server method handler threw error ${ + (err as Error).message + }`, + metadata: null, }); } }, @@ -1564,8 +1649,10 @@ function handleBidiStreaming( } catch (err) { call.sendStatus({ code: Status.UNKNOWN, - details: `Server method handler threw error ${(err as Error).message}`, - metadata: null + details: `Server method handler threw error ${ + (err as Error).message + }`, + metadata: null, }); } }, diff --git a/packages/grpc-js/src/service-config.ts b/packages/grpc-js/src/service-config.ts index 5c2ca0d06..b0d0d5576 100644 --- a/packages/grpc-js/src/service-config.ts +++ b/packages/grpc-js/src/service-config.ts @@ -356,17 +356,23 @@ export function validateRetryThrottling(obj: any): RetryThrottling { function validateLoadBalancingConfig(obj: any): LoadBalancingConfig { if (!(typeof obj === 'object' && obj !== null)) { - throw new Error(`Invalid loadBalancingConfig: unexpected type ${typeof obj}`); + throw new Error( + `Invalid loadBalancingConfig: unexpected type ${typeof obj}` + ); } const keys = Object.keys(obj); if (keys.length > 1) { - throw new Error(`Invalid loadBalancingConfig: unexpected multiple keys ${keys}`); + throw new Error( + `Invalid loadBalancingConfig: unexpected multiple keys ${keys}` + ); } if (keys.length === 0) { - throw new Error('Invalid loadBalancingConfig: load balancing policy name required'); + throw new Error( + 'Invalid loadBalancingConfig: load balancing policy name required' + ); } return { - [keys[0]]: obj[keys[0]] + [keys[0]]: obj[keys[0]], }; } @@ -385,7 +391,6 @@ export function validateServiceConfig(obj: any): ServiceConfig { if ('loadBalancingConfig' in obj) { if (Array.isArray(obj.loadBalancingConfig)) { for (const config of obj.loadBalancingConfig) { - result.loadBalancingConfig.push(validateLoadBalancingConfig(config)); } } else { diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index b21581375..c4941b068 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -205,7 +205,12 @@ class Http2Transport implements Transport { ) { tooManyPings = true; } - this.trace('connection closed by GOAWAY with code ' + errorCode + ' and data ' + opaqueData?.toString()); + this.trace( + 'connection closed by GOAWAY with code ' + + errorCode + + ' and data ' + + opaqueData?.toString() + ); this.reportDisconnectToOwner(tooManyPings); } ); diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index f6aeed103..88aa129aa 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -129,7 +129,10 @@ export class TestClient { this.client.echo({}, callback); } - sendRequestWithMetadata(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError) => void) { + sendRequestWithMetadata( + metadata: grpc.Metadata, + callback: (error?: grpc.ServiceError) => void + ) { this.client.echo({}, metadata, callback); } diff --git a/packages/grpc-js/test/test-confg-parsing.ts b/packages/grpc-js/test/test-confg-parsing.ts index 569b83f5e..b5b9832a7 100644 --- a/packages/grpc-js/test/test-confg-parsing.ts +++ b/packages/grpc-js/test/test-confg-parsing.ts @@ -28,7 +28,7 @@ import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig; */ interface TestCase { name: string; - input: object, + input: object; output?: object; error?: RegExp; } @@ -40,52 +40,52 @@ interface TestCase { * Note: some tests have an expected output that is different from the output, * but all non-error tests additionally verify that parsing the output again * produces the same output. */ -const allTestCases: {[lbPolicyName: string]: TestCase[]} = { +const allTestCases: { [lbPolicyName: string]: TestCase[] } = { pick_first: [ { name: 'no fields set', input: {}, output: { - shuffleAddressList: false - } + shuffleAddressList: false, + }, }, { name: 'shuffleAddressList set', input: { - shuffleAddressList: true - } - } + shuffleAddressList: true, + }, + }, ], round_robin: [ { name: 'no fields set', - input: {} - } + input: {}, + }, ], outlier_detection: [ { name: 'only required fields set', input: { - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }, output: { interval: { seconds: 10, - nanos: 0 + nanos: 0, }, base_ejection_time: { seconds: 30, - nanos: 0 + nanos: 0, }, max_ejection_time: { seconds: 300, - nanos: 0 + nanos: 0, }, max_ejection_percent: 10, success_rate_ejection: undefined, failure_percentage_ejection: undefined, - child_policy: [{round_robin: {}}] - } + child_policy: [{ round_robin: {} }], + }, }, { name: 'all optional fields undefined', @@ -96,53 +96,53 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { max_ejection_percent: undefined, success_rate_ejection: undefined, failure_percentage_ejection: undefined, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }, output: { interval: { seconds: 10, - nanos: 0 + nanos: 0, }, base_ejection_time: { seconds: 30, - nanos: 0 + nanos: 0, }, max_ejection_time: { seconds: 300, - nanos: 0 + nanos: 0, }, max_ejection_percent: 10, success_rate_ejection: undefined, failure_percentage_ejection: undefined, - child_policy: [{round_robin: {}}] - } + child_policy: [{ round_robin: {} }], + }, }, { name: 'empty ejection configs', input: { success_rate_ejection: {}, failure_percentage_ejection: {}, - child_policy: [{round_robin: {}}] + child_policy: [{ round_robin: {} }], }, output: { interval: { seconds: 10, - nanos: 0 + nanos: 0, }, base_ejection_time: { seconds: 30, - nanos: 0 + nanos: 0, }, max_ejection_time: { seconds: 300, - nanos: 0 + nanos: 0, }, max_ejection_percent: 10, success_rate_ejection: { stdev_factor: 1900, enforcement_percentage: 100, minimum_hosts: 5, - request_volume: 100 + request_volume: 100, }, failure_percentage_ejection: { threshold: 85, @@ -150,30 +150,30 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { minimum_hosts: 5, request_volume: 50, }, - child_policy: [{round_robin: {}}] - } + child_policy: [{ round_robin: {} }], + }, }, { name: 'all fields populated', input: { interval: { seconds: 20, - nanos: 0 + nanos: 0, }, base_ejection_time: { seconds: 40, - nanos: 0 + nanos: 0, }, max_ejection_time: { seconds: 400, - nanos: 0 + nanos: 0, }, max_ejection_percent: 20, success_rate_ejection: { stdev_factor: 1800, enforcement_percentage: 90, minimum_hosts: 4, - request_volume: 200 + request_volume: 200, }, failure_percentage_ejection: { threshold: 95, @@ -181,29 +181,34 @@ const allTestCases: {[lbPolicyName: string]: TestCase[]} = { minimum_hosts: 4, request_volume: 60, }, - child_policy: [{round_robin: {}}] - - } - } - ] -} + child_policy: [{ round_robin: {} }], + }, + }, + ], +}; describe('Load balancing policy config parsing', () => { for (const [lbPolicyName, testCases] of Object.entries(allTestCases)) { describe(lbPolicyName, () => { for (const testCase of testCases) { it(testCase.name, () => { - const lbConfigInput = {[lbPolicyName]: testCase.input}; + const lbConfigInput = { [lbPolicyName]: testCase.input }; if (testCase.error) { assert.throws(() => { parseLoadBalancingConfig(lbConfigInput); }, testCase.error); } else { const expectedOutput = testCase.output ?? testCase.input; - const parsedJson = parseLoadBalancingConfig(lbConfigInput).toJsonObject(); - assert.deepStrictEqual(parsedJson, {[lbPolicyName]: expectedOutput}); + const parsedJson = + parseLoadBalancingConfig(lbConfigInput).toJsonObject(); + assert.deepStrictEqual(parsedJson, { + [lbPolicyName]: expectedOutput, + }); // Test idempotency - assert.deepStrictEqual(parseLoadBalancingConfig(parsedJson).toJsonObject(), parsedJson); + assert.deepStrictEqual( + parseLoadBalancingConfig(parsedJson).toJsonObject(), + parsedJson + ); } }); } diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index df7a3c741..4c2c319e1 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -561,28 +561,43 @@ describe('pick_first load balancing policy', () => { }, updateState: updateStateCallBackForExpectedStateSequence( [ConnectivityState.CONNECTING, ConnectivityState.TRANSIENT_FAILURE], - err => setImmediate(() => { - assert.strictEqual(reresolutionRequestCount, targetReresolutionRequestCount); - done(err); - }) + err => + setImmediate(() => { + assert.strictEqual( + reresolutionRequestCount, + targetReresolutionRequestCount + ); + done(err); + }) ), requestReresolution: () => { reresolutionRequestCount += 1; - } + }, } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); - pickFirst.updateAddressList([{ addresses: [{ host: 'localhost', port: 1 }]}], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE); process.nextTick(() => { - pickFirst.updateAddressList([{ addresses: [{ host: 'localhost', port: 2 }]}], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 2 }] }], + config + ); process.nextTick(() => { subchannels[1].transitionToState(ConnectivityState.TRANSIENT_FAILURE); process.nextTick(() => { - pickFirst.updateAddressList([{ addresses: [{ host: 'localhost', port: 3 }]}], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 3 }] }], + config + ); process.nextTick(() => { - subchannels[2].transitionToState(ConnectivityState.TRANSIENT_FAILURE); + subchannels[2].transitionToState( + ConnectivityState.TRANSIENT_FAILURE + ); }); }); }); @@ -606,22 +621,35 @@ describe('pick_first load balancing policy', () => { }, updateState: updateStateCallBackForExpectedStateSequence( [ConnectivityState.TRANSIENT_FAILURE], - err => setImmediate(() => { - assert.strictEqual(reresolutionRequestCount, targetReresolutionRequestCount); - done(err); - }) + err => + setImmediate(() => { + assert.strictEqual( + reresolutionRequestCount, + targetReresolutionRequestCount + ); + done(err); + }) ), requestReresolution: () => { reresolutionRequestCount += 1; - } + }, } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); - pickFirst.updateAddressList([{ addresses: [{ host: 'localhost', port: 1 }]}], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { - pickFirst.updateAddressList([{ addresses: [{ host: 'localhost', port: 2 }]}], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 2 }] }], + config + ); process.nextTick(() => { - pickFirst.updateAddressList([{ addresses: [{ host: 'localhost', port: 2 }]}], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 2 }] }], + config + ); }); }); }); @@ -639,13 +667,20 @@ describe('pick_first load balancing policy', () => { return subchannel; }, updateState: updateStateCallBackForExpectedStateSequence( - [ConnectivityState.READY, ConnectivityState.IDLE, ConnectivityState.READY], + [ + ConnectivityState.READY, + ConnectivityState.IDLE, + ConnectivityState.READY, + ], done ), } ); const pickFirst = new PickFirstLoadBalancer(channelControlHelper, {}); - pickFirst.updateAddressList([{ addresses: [{ host: 'localhost', port: 1 }]}], config); + pickFirst.updateAddressList( + [{ addresses: [{ host: 'localhost', port: 1 }] }], + config + ); process.nextTick(() => { subchannels[0].transitionToState(ConnectivityState.IDLE); process.nextTick(() => { diff --git a/packages/grpc-js/test/test-server-interceptors.ts b/packages/grpc-js/test/test-server-interceptors.ts index b6b5b55f2..5e93d32d2 100644 --- a/packages/grpc-js/test/test-server-interceptors.ts +++ b/packages/grpc-js/test/test-server-interceptors.ts @@ -26,23 +26,28 @@ const echoService = loadProtoFile(protoFile) const AUTH_HEADER_KEY = 'auth'; const AUTH_HEADER_ALLOWED_VALUE = 'allowed'; -const testAuthInterceptor: grpc.ServerInterceptor = (methodDescriptor, call) => { +const testAuthInterceptor: grpc.ServerInterceptor = ( + methodDescriptor, + call +) => { return new grpc.ServerInterceptingCall(call, { start: next => { const authListener: grpc.ServerListener = { onReceiveMetadata: (metadata, mdNext) => { - if (metadata.get(AUTH_HEADER_KEY)?.[0] !== AUTH_HEADER_ALLOWED_VALUE) { + if ( + metadata.get(AUTH_HEADER_KEY)?.[0] !== AUTH_HEADER_ALLOWED_VALUE + ) { call.sendStatus({ code: grpc.status.UNAUTHENTICATED, - details: 'Auth metadata not correct' + details: 'Auth metadata not correct', }); } else { mdNext(metadata); } - } + }, }; next(authListener); - } + }, }); }; @@ -52,7 +57,7 @@ let eventCounts = { receiveHalfClose: 0, sendMetadata: 0, sendMessage: 0, - sendStatus: 0 + sendStatus: 0, }; function resetEventCounts() { @@ -62,7 +67,7 @@ function resetEventCounts() { receiveHalfClose: 0, sendMetadata: 0, sendMessage: 0, - sendStatus: 0 + sendStatus: 0, }; } @@ -72,7 +77,10 @@ function resetEventCounts() { * @param methodDescription * @param call */ -const testLoggingInterceptor: grpc.ServerInterceptor = (methodDescription, call) => { +const testLoggingInterceptor: grpc.ServerInterceptor = ( + methodDescription, + call +) => { return new grpc.ServerInterceptingCall(call, { start: next => { next({ @@ -87,7 +95,7 @@ const testLoggingInterceptor: grpc.ServerInterceptor = (methodDescription, call) onReceiveHalfClose: hcNext => { eventCounts.receiveHalfClose += 1; hcNext(); - } + }, }); }, sendMetadata: (metadata, mdNext) => { @@ -101,21 +109,24 @@ const testLoggingInterceptor: grpc.ServerInterceptor = (methodDescription, call) sendStatus: (status, statusNext) => { eventCounts.sendStatus += 1; statusNext(status); - } + }, }); }; -const testHeaderInjectionInterceptor: grpc.ServerInterceptor = (methodDescriptor, call) => { +const testHeaderInjectionInterceptor: grpc.ServerInterceptor = ( + methodDescriptor, + call +) => { return new grpc.ServerInterceptingCall(call, { start: next => { const authListener: grpc.ServerListener = { onReceiveMetadata: (metadata, mdNext) => { metadata.set('injected-header', 'present'); mdNext(metadata); - } + }, }; next(authListener); - } + }, }); }; @@ -126,22 +137,29 @@ describe('Server interceptors', () => { /* Tests that an interceptor can entirely prevent the handler from being * invoked, based on the contents of the metadata. */ before(done => { - server = new grpc.Server({interceptors: [testAuthInterceptor]}); + server = new grpc.Server({ interceptors: [testAuthInterceptor] }); server.addService(echoService.service, { echo: ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ) => { // A test will fail if a request makes it to the handler without the correct auth header - assert.strictEqual(call.metadata.get(AUTH_HEADER_KEY)?.[0], AUTH_HEADER_ALLOWED_VALUE); + assert.strictEqual( + call.metadata.get(AUTH_HEADER_KEY)?.[0], + AUTH_HEADER_ALLOWED_VALUE + ); callback(null, call.request); }, }); - server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - assert.ifError(error); - client = new TestClient(port, false); - done(); - }); + server.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + } + ); }); after(done => { client.close(); @@ -165,7 +183,7 @@ describe('Server interceptors', () => { let server: grpc.Server; let client: TestClient; before(done => { - server = new grpc.Server({interceptors: [testLoggingInterceptor]}); + server = new grpc.Server({ interceptors: [testLoggingInterceptor] }); server.addService(echoService.service, { echo: ( call: grpc.ServerUnaryCall, @@ -175,11 +193,15 @@ describe('Server interceptors', () => { callback(null, call.request); }, }); - server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - assert.ifError(error); - client = new TestClient(port, false); - done(); - }); + server.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + } + ); }); after(done => { client.close(); @@ -197,7 +219,7 @@ describe('Server interceptors', () => { receiveHalfClose: 1, sendMetadata: 1, sendMessage: 1, - sendStatus: 1 + sendStatus: 1, }); done(); }); @@ -207,21 +229,30 @@ describe('Server interceptors', () => { let server: grpc.Server; let client: TestClient; before(done => { - server = new grpc.Server({interceptors: [testHeaderInjectionInterceptor]}); + server = new grpc.Server({ + interceptors: [testHeaderInjectionInterceptor], + }); server.addService(echoService.service, { echo: ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ) => { - assert.strictEqual(call.metadata.get('injected-header')?.[0], 'present'); + assert.strictEqual( + call.metadata.get('injected-header')?.[0], + 'present' + ); callback(null, call.request); }, }); - server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - assert.ifError(error); - client = new TestClient(port, false); - done(); - }); + server.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + } + ); }); after(done => { client.close(); @@ -235,23 +266,39 @@ describe('Server interceptors', () => { let server: grpc.Server; let client: TestClient; before(done => { - server = new grpc.Server({interceptors: [testAuthInterceptor, testLoggingInterceptor, testHeaderInjectionInterceptor]}); + server = new grpc.Server({ + interceptors: [ + testAuthInterceptor, + testLoggingInterceptor, + testHeaderInjectionInterceptor, + ], + }); server.addService(echoService.service, { echo: ( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ) => { - assert.strictEqual(call.metadata.get(AUTH_HEADER_KEY)?.[0], AUTH_HEADER_ALLOWED_VALUE); - assert.strictEqual(call.metadata.get('injected-header')?.[0], 'present'); + assert.strictEqual( + call.metadata.get(AUTH_HEADER_KEY)?.[0], + AUTH_HEADER_ALLOWED_VALUE + ); + assert.strictEqual( + call.metadata.get('injected-header')?.[0], + 'present' + ); call.sendMetadata(new grpc.Metadata()); callback(null, call.request); }, }); - server.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => { - assert.ifError(error); - client = new TestClient(port, false); - done(); - }); + server.bindAsync( + 'localhost:0', + grpc.ServerCredentials.createInsecure(), + (error, port) => { + assert.ifError(error); + client = new TestClient(port, false); + done(); + } + ); }); after(done => { client.close(); @@ -271,7 +318,7 @@ describe('Server interceptors', () => { receiveHalfClose: 0, sendMetadata: 0, sendMessage: 0, - sendStatus: 0 + sendStatus: 0, }); done(); }); @@ -287,7 +334,7 @@ describe('Server interceptors', () => { receiveHalfClose: 1, sendMetadata: 1, sendMessage: 1, - sendStatus: 1 + sendStatus: 1, }); done(); }); diff --git a/packages/grpc-js/test/test-server.ts b/packages/grpc-js/test/test-server.ts index 48b305ef4..dbcdad469 100644 --- a/packages/grpc-js/test/test-server.ts +++ b/packages/grpc-js/test/test-server.ts @@ -149,14 +149,22 @@ describe('Server', () => { }); it('succeeds when called with an already bound port', done => { - server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { - assert.ifError(err); - server.bindAsync(`localhost:${port}`, ServerCredentials.createInsecure(), (err2, port2) => { - assert.ifError(err2); - assert.strictEqual(port, port2); - done(); - }); - }); + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + server.bindAsync( + `localhost:${port}`, + ServerCredentials.createInsecure(), + (err2, port2) => { + assert.ifError(err2); + assert.strictEqual(port, port2); + done(); + } + ); + } + ); }); it('fails when called on a bound port with different credentials', done => { @@ -165,15 +173,19 @@ describe('Server', () => { [{ private_key: key, cert_chain: cert }], true ); - server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { - assert.ifError(err); - server.bindAsync(`localhost:${port}`, secureCreds, (err2, port2) => { - assert(err2 !== null); - assert.match(err2.message, /credentials/); - done(); - }) - }); - }) + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + server.bindAsync(`localhost:${port}`, secureCreds, (err2, port2) => { + assert(err2 !== null); + assert.match(err2.message, /credentials/); + done(); + }); + } + ); + }); }); describe('unbind', () => { @@ -188,42 +200,73 @@ describe('Server', () => { assert.throws(() => { server.unbind('localhost:0'); }, /port 0/); - server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { - assert.ifError(err); - assert.notStrictEqual(port, 0); - assert.throws(() => { - server.unbind('localhost:0'); - }, /port 0/); - done(); - }) + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + assert.ifError(err); + assert.notStrictEqual(port, 0); + assert.throws(() => { + server.unbind('localhost:0'); + }, /port 0/); + done(); + } + ); }); it('successfully unbinds a bound ephemeral port', done => { - server.bindAsync('localhost:0', ServerCredentials.createInsecure(), (err, port) => { - client = new grpc.Client(`localhost:${port}`, grpc.credentials.createInsecure()); - client.makeUnaryRequest('/math.Math/Div', x => x, x => x, Buffer.from('abc'), (callError1, result) => { - assert(callError1); - // UNIMPLEMENTED means that the request reached the call handling code - assert.strictEqual(callError1.code, grpc.status.UNIMPLEMENTED); - server.unbind(`localhost:${port}`); - const deadline = new Date(); - deadline.setSeconds(deadline.getSeconds() + 1); - client!.makeUnaryRequest('/math.Math/Div', x => x, x => x, Buffer.from('abc'), {deadline: deadline}, (callError2, result) => { - assert(callError2); - // DEADLINE_EXCEEDED means that the server is unreachable - assert(callError2.code === grpc.status.DEADLINE_EXCEEDED || callError2.code === grpc.status.UNAVAILABLE); - done(); - }); - }); - }) + server.bindAsync( + 'localhost:0', + ServerCredentials.createInsecure(), + (err, port) => { + client = new grpc.Client( + `localhost:${port}`, + grpc.credentials.createInsecure() + ); + client.makeUnaryRequest( + '/math.Math/Div', + x => x, + x => x, + Buffer.from('abc'), + (callError1, result) => { + assert(callError1); + // UNIMPLEMENTED means that the request reached the call handling code + assert.strictEqual(callError1.code, grpc.status.UNIMPLEMENTED); + server.unbind(`localhost:${port}`); + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 1); + client!.makeUnaryRequest( + '/math.Math/Div', + x => x, + x => x, + Buffer.from('abc'), + { deadline: deadline }, + (callError2, result) => { + assert(callError2); + // DEADLINE_EXCEEDED means that the server is unreachable + assert( + callError2.code === grpc.status.DEADLINE_EXCEEDED || + callError2.code === grpc.status.UNAVAILABLE + ); + done(); + } + ); + } + ); + } + ); }); it('cancels a bindAsync in progress', done => { - server.bindAsync('localhost:50051', ServerCredentials.createInsecure(), (err, port) => { - assert(err); - assert.match(err.message, /cancelled by unbind/); - done(); - }); + server.bindAsync( + 'localhost:50051', + ServerCredentials.createInsecure(), + (err, port) => { + assert(err); + assert.match(err.message, /cancelled by unbind/); + done(); + } + ); server.unbind('localhost:50051'); }); }); @@ -282,7 +325,7 @@ describe('Server', () => { call.on('data', () => { server.drain(`localhost:${portNumber!}`, 100); }); - call.write({value: 'abc'}); + call.write({ value: 'abc' }); }); }); From e0b900dd6903922b66bbfd2e17f00450d12f66b6 Mon Sep 17 00:00:00 2001 From: AVVS Date: Tue, 27 Feb 2024 12:27:25 -0800 Subject: [PATCH 664/694] feat: channelz improvements, idle timeout implementation --- packages/grpc-js/.eslintrc | 1 + packages/grpc-js/gulpfile.ts | 33 +- packages/grpc-js/package.json | 37 +- packages/grpc-js/src/channel.ts | 2 +- packages/grpc-js/src/channelz.ts | 468 ++++++------ .../src/load-balancer-child-handler.ts | 2 +- packages/grpc-js/src/load-balancer.ts | 2 +- packages/grpc-js/src/server-call.ts | 18 +- packages/grpc-js/src/server.ts | 678 ++++++++++++------ packages/grpc-js/src/subchannel-interface.ts | 2 +- packages/grpc-js/src/subchannel.ts | 38 +- packages/grpc-js/src/transport.ts | 16 +- packages/grpc-js/test/common.ts | 4 +- 13 files changed, 784 insertions(+), 517 deletions(-) diff --git a/packages/grpc-js/.eslintrc b/packages/grpc-js/.eslintrc index 2f6bfd62d..9a72b31de 100644 --- a/packages/grpc-js/.eslintrc +++ b/packages/grpc-js/.eslintrc @@ -50,6 +50,7 @@ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/ban-types": "off", "@typescript-eslint/camelcase": "off", + "@typescript-eslint/no-explicit-any": "off", "node/no-missing-import": "off", "node/no-empty-function": "off", "node/no-unsupported-features/es-syntax": "off", diff --git a/packages/grpc-js/gulpfile.ts b/packages/grpc-js/gulpfile.ts index d85900364..e4e9071ff 100644 --- a/packages/grpc-js/gulpfile.ts +++ b/packages/grpc-js/gulpfile.ts @@ -35,14 +35,17 @@ const pkgPath = path.resolve(jsCoreDir, 'package.json'); const supportedVersionRange = require(pkgPath).engines.node; const versionNotSupported = () => { console.log(`Skipping grpc-js task for Node ${process.version}`); - return () => { return Promise.resolve(); }; + return () => { + return Promise.resolve(); + }; }; const identity = (value: any): any => value; -const checkTask = semver.satisfies(process.version, supportedVersionRange) ? - identity : versionNotSupported; +const checkTask = semver.satisfies(process.version, supportedVersionRange) + ? identity + : versionNotSupported; const execNpmVerb = (verb: string, ...args: string[]) => - execa('npm', [verb, ...args], {cwd: jsCoreDir, stdio: 'inherit'}); + execa('npm', [verb, ...args], { cwd: jsCoreDir, stdio: 'inherit' }); const execNpmCommand = execNpmVerb.bind(null, 'run'); const install = checkTask(() => execNpmVerb('install', '--unsafe-perm')); @@ -64,22 +67,20 @@ const cleanAll = gulp.parallel(clean); */ const compile = checkTask(() => execNpmCommand('compile')); -const copyTestFixtures = checkTask(() => ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`)); +const copyTestFixtures = checkTask(() => + ncpP(`${jsCoreDir}/test/fixtures`, `${outDir}/test/fixtures`) +); const runTests = checkTask(() => { process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION = 'true'; - return gulp.src(`${outDir}/test/**/*.js`) - .pipe(mocha({reporter: 'mocha-jenkins-reporter', - require: ['ts-node/register']})); + return gulp.src(`${outDir}/test/**/*.js`).pipe( + mocha({ + reporter: 'mocha-jenkins-reporter', + require: ['ts-node/register'], + }) + ); }); const test = gulp.series(install, copyTestFixtures, runTests); -export { - install, - lint, - clean, - cleanAll, - compile, - test -} +export { install, lint, clean, cleanAll, compile, test }; diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index d1cb3d561..34d8b558b 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -6,7 +6,7 @@ "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", "main": "build/src/index.js", "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=12.10.0" }, "keywords": [], "author": { @@ -15,17 +15,18 @@ "types": "build/src/index.d.ts", "license": "Apache-2.0", "devDependencies": { - "@types/gulp": "^4.0.6", - "@types/gulp-mocha": "0.0.32", - "@types/lodash": "^4.14.186", - "@types/mocha": "^5.2.6", - "@types/ncp": "^2.0.1", - "@types/pify": "^3.0.2", - "@types/semver": "^7.3.9", - "@typescript-eslint/eslint-plugin": "^5.59.11", - "@typescript-eslint/parser": "^5.59.11", - "@typescript-eslint/typescript-estree": "^5.59.11", - "clang-format": "^1.0.55", + "@types/gulp": "^4.0.17", + "@types/gulp-mocha": "0.0.37", + "@types/lodash": "^4.14.202", + "@types/mocha": "^10.0.6", + "@types/ncp": "^2.0.8", + "@types/pify": "^5.0.4", + "@types/semver": "^7.5.8", + "@types/node": ">=20.11.20", + "@typescript-eslint/eslint-plugin": "^7.1.0", + "@typescript-eslint/parser": "^7.1.0", + "@typescript-eslint/typescript-estree": "^7.1.0", + "clang-format": "^1.8.0", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-node": "^11.1.0", @@ -33,16 +34,16 @@ "execa": "^2.0.3", "gulp": "^4.0.2", "gulp-mocha": "^6.0.0", - "lodash": "^4.17.4", + "lodash": "^4.17.21", "madge": "^5.0.1", "mocha-jenkins-reporter": "^0.4.1", "ncp": "^2.0.0", "pify": "^4.0.1", "prettier": "^2.8.8", "rimraf": "^3.0.2", - "semver": "^7.3.5", - "ts-node": "^10.9.1", - "typescript": "^5.1.3" + "semver": "^7.6.0", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" }, "contributors": [ { @@ -65,8 +66,8 @@ "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto" }, "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" + "@grpc/proto-loader": "^0.7.10", + "@js-sdsl/ordered-map": "^4.4.2" }, "files": [ "src/**/*.ts", diff --git a/packages/grpc-js/src/channel.ts b/packages/grpc-js/src/channel.ts index 7ce5a15f7..514920c8f 100644 --- a/packages/grpc-js/src/channel.ts +++ b/packages/grpc-js/src/channel.ts @@ -20,7 +20,7 @@ import { ChannelOptions } from './channel-options'; import { ServerSurfaceCall } from './server-call'; import { ConnectivityState } from './connectivity-state'; -import { ChannelRef } from './channelz'; +import type { ChannelRef } from './channelz'; import { Call } from './call-interface'; import { InternalChannel } from './internal-channel'; import { Deadline } from './deadline'; diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 1e2627a97..6d70b7543 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -16,6 +16,7 @@ */ import { isIPv4, isIPv6 } from 'net'; +import { OrderedMap, type OrderedMapIterator } from '@js-sdsl/ordered-map'; import { ConnectivityState } from './connectivity-state'; import { Status } from './constants'; import { Timestamp } from './generated/google/protobuf/Timestamp'; @@ -66,24 +67,25 @@ export type TraceSeverity = | 'CT_ERROR'; export interface ChannelRef { - kind: 'channel'; + kind: EntityTypes.channel; id: number; name: string; } export interface SubchannelRef { - kind: 'subchannel'; + kind: EntityTypes.subchannel; id: number; name: string; } export interface ServerRef { - kind: 'server'; + kind: EntityTypes.server; id: number; + name: string; } export interface SocketRef { - kind: 'socket'; + kind: EntityTypes.socket; id: number; name: string; } @@ -131,6 +133,21 @@ interface TraceEvent { */ const TARGET_RETAINED_TRACES = 32; +export class ChannelzTraceStub { + readonly events: TraceEvent[] = []; + readonly creationTimestamp: Date = new Date(); + readonly eventsLogged = 0; + + addTrace(): void {} + getTraceMessage(): ChannelTrace { + return { + creation_timestamp: dateToProtoTimestamp(this.creationTimestamp), + num_events_logged: this.eventsLogged, + events: [], + }; + } +} + export class ChannelzTrace { events: TraceEvent[] = []; creationTimestamp: Date; @@ -182,105 +199,64 @@ export class ChannelzTrace { } export class ChannelzChildrenTracker { - private channelChildren: Map = - new Map(); - private subchannelChildren: Map< + private channelChildren = new OrderedMap< + number, + { ref: ChannelRef; count: number } + >(); + private subchannelChildren = new OrderedMap< number, { ref: SubchannelRef; count: number } - > = new Map(); - private socketChildren: Map = - new Map(); + >(); + private socketChildren = new OrderedMap< + number, + { ref: SocketRef; count: number } + >(); + private trackerMap = { + [EntityTypes.channel]: this.channelChildren, + [EntityTypes.subchannel]: this.subchannelChildren, + [EntityTypes.socket]: this.socketChildren, + } as const; refChild(child: ChannelRef | SubchannelRef | SocketRef) { - switch (child.kind) { - case 'channel': { - const trackedChild = this.channelChildren.get(child.id) ?? { - ref: child, - count: 0, - }; - trackedChild.count += 1; - this.channelChildren.set(child.id, trackedChild); - break; - } - case 'subchannel': { - const trackedChild = this.subchannelChildren.get(child.id) ?? { - ref: child, - count: 0, - }; - trackedChild.count += 1; - this.subchannelChildren.set(child.id, trackedChild); - break; - } - case 'socket': { - const trackedChild = this.socketChildren.get(child.id) ?? { - ref: child, - count: 0, - }; - trackedChild.count += 1; - this.socketChildren.set(child.id, trackedChild); - break; - } + const tracker = this.trackerMap[child.kind]; + const trackedChild = tracker.getElementByKey(child.id); + + if (trackedChild === undefined) { + tracker.setElement(child.id, { + // @ts-expect-error union issues + ref: child, + count: 1, + }); + } else { + trackedChild.count += 1; } } unrefChild(child: ChannelRef | SubchannelRef | SocketRef) { - switch (child.kind) { - case 'channel': { - const trackedChild = this.channelChildren.get(child.id); - if (trackedChild !== undefined) { - trackedChild.count -= 1; - if (trackedChild.count === 0) { - this.channelChildren.delete(child.id); - } else { - this.channelChildren.set(child.id, trackedChild); - } - } - break; - } - case 'subchannel': { - const trackedChild = this.subchannelChildren.get(child.id); - if (trackedChild !== undefined) { - trackedChild.count -= 1; - if (trackedChild.count === 0) { - this.subchannelChildren.delete(child.id); - } else { - this.subchannelChildren.set(child.id, trackedChild); - } - } - break; - } - case 'socket': { - const trackedChild = this.socketChildren.get(child.id); - if (trackedChild !== undefined) { - trackedChild.count -= 1; - if (trackedChild.count === 0) { - this.socketChildren.delete(child.id); - } else { - this.socketChildren.set(child.id, trackedChild); - } - } - break; + const tracker = this.trackerMap[child.kind]; + const trackedChild = tracker.getElementByKey(child.id); + if (trackedChild !== undefined) { + trackedChild.count -= 1; + if (trackedChild.count === 0) { + tracker.eraseElementByKey(child.id); } } } getChildLists(): ChannelzChildren { - const channels: ChannelRef[] = []; - for (const { ref } of this.channelChildren.values()) { - channels.push(ref); - } - const subchannels: SubchannelRef[] = []; - for (const { ref } of this.subchannelChildren.values()) { - subchannels.push(ref); - } - const sockets: SocketRef[] = []; - for (const { ref } of this.socketChildren.values()) { - sockets.push(ref); - } - return { channels, subchannels, sockets }; + return { + channels: this.channelChildren, + subchannels: this.subchannelChildren, + sockets: this.socketChildren, + }; } } +export class ChannelzChildrenTrackerStub extends ChannelzChildrenTracker { + override refChild(): void {} + override unrefChild(): void {} +} + export class ChannelzCallTracker { callsStarted = 0; callsSucceeded = 0; @@ -299,17 +275,23 @@ export class ChannelzCallTracker { } } +export class ChannelzCallTrackerStub extends ChannelzCallTracker { + override addCallStarted() {} + override addCallSucceeded() {} + override addCallFailed() {} +} + export interface ChannelzChildren { - channels: ChannelRef[]; - subchannels: SubchannelRef[]; - sockets: SocketRef[]; + channels: OrderedMap; + subchannels: OrderedMap; + sockets: OrderedMap; } export interface ChannelInfo { target: string; state: ConnectivityState; - trace: ChannelzTrace; - callTracker: ChannelzCallTracker; + trace: ChannelzTrace | ChannelzTraceStub; + callTracker: ChannelzCallTracker | ChannelzCallTrackerStub; children: ChannelzChildren; } @@ -348,105 +330,102 @@ export interface SocketInfo { remoteFlowControlWindow: number | null; } -interface ChannelEntry { +type ChannelEntry = { ref: ChannelRef; getInfo(): ChannelInfo; -} +}; -interface SubchannelEntry { +type SubchannelEntry = { ref: SubchannelRef; getInfo(): SubchannelInfo; -} +}; -interface ServerEntry { +type ServerEntry = { ref: ServerRef; getInfo(): ServerInfo; -} +}; -interface SocketEntry { +type SocketEntry = { ref: SocketRef; getInfo(): SocketInfo; -} - -let nextId = 1; - -function getNextId(): number { - return nextId++; -} - -const channels: (ChannelEntry | undefined)[] = []; -const subchannels: (SubchannelEntry | undefined)[] = []; -const servers: (ServerEntry | undefined)[] = []; -const sockets: (SocketEntry | undefined)[] = []; - -export function registerChannelzChannel( - name: string, - getInfo: () => ChannelInfo, - channelzEnabled: boolean -): ChannelRef { - const id = getNextId(); - const ref: ChannelRef = { id, name, kind: 'channel' }; - if (channelzEnabled) { - channels[id] = { ref, getInfo }; - } - return ref; -} - -export function registerChannelzSubchannel( - name: string, - getInfo: () => SubchannelInfo, - channelzEnabled: boolean -): SubchannelRef { - const id = getNextId(); - const ref: SubchannelRef = { id, name, kind: 'subchannel' }; - if (channelzEnabled) { - subchannels[id] = { ref, getInfo }; +}; + +export const enum EntityTypes { + channel = 'channel', + subchannel = 'subchannel', + server = 'server', + socket = 'socket', +} + +const entityMaps = { + [EntityTypes.channel]: new OrderedMap(), + [EntityTypes.subchannel]: new OrderedMap(), + [EntityTypes.server]: new OrderedMap(), + [EntityTypes.socket]: new OrderedMap(), +} as const; + +export type RefByType = T extends EntityTypes.channel + ? ChannelRef + : T extends EntityTypes.server + ? ServerRef + : T extends EntityTypes.socket + ? SocketRef + : T extends EntityTypes.subchannel + ? SubchannelRef + : never; + +export type EntryByType = T extends EntityTypes.channel + ? ChannelEntry + : T extends EntityTypes.server + ? ServerEntry + : T extends EntityTypes.socket + ? SocketEntry + : T extends EntityTypes.subchannel + ? SubchannelEntry + : never; + +export type InfoByType = T extends EntityTypes.channel + ? ChannelInfo + : T extends EntityTypes.subchannel + ? SubchannelInfo + : T extends EntityTypes.server + ? ServerInfo + : T extends EntityTypes.socket + ? SocketInfo + : never; + +const generateRegisterFn = (kind: R) => { + let nextId = 1; + function getNextId(): number { + return nextId++; } - return ref; -} -export function registerChannelzServer( - getInfo: () => ServerInfo, - channelzEnabled: boolean -): ServerRef { - const id = getNextId(); - const ref: ServerRef = { id, kind: 'server' }; - if (channelzEnabled) { - servers[id] = { ref, getInfo }; - } - return ref; -} - -export function registerChannelzSocket( - name: string, - getInfo: () => SocketInfo, - channelzEnabled: boolean -): SocketRef { - const id = getNextId(); - const ref: SocketRef = { id, name, kind: 'socket' }; - if (channelzEnabled) { - sockets[id] = { ref, getInfo }; - } - return ref; -} + return ( + name: string, + getInfo: () => InfoByType, + channelzEnabled: boolean + ): RefByType => { + const id = getNextId(); + const ref = { id, name, kind } as RefByType; + if (channelzEnabled) { + // @ts-expect-error typing issues + entityMaps[kind].setElement(id, { ref, getInfo }); + } + return ref; + }; +}; + +export const registerChannelzChannel = generateRegisterFn(EntityTypes.channel); +export const registerChannelzSubchannel = generateRegisterFn( + EntityTypes.subchannel +); +export const registerChannelzServer = generateRegisterFn(EntityTypes.server); +export const registerChannelzSocket = generateRegisterFn(EntityTypes.socket); export function unregisterChannelzRef( ref: ChannelRef | SubchannelRef | ServerRef | SocketRef ) { - switch (ref.kind) { - case 'channel': - delete channels[ref.id]; - return; - case 'subchannel': - delete subchannels[ref.id]; - return; - case 'server': - delete servers[ref.id]; - return; - case 'socket': - delete sockets[ref.id]; - return; - } + entityMaps[ref.kind].eraseElementByKey(ref.id); } /** @@ -556,6 +535,17 @@ function dateToProtoTimestamp(date?: Date | null): Timestamp | null { function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { const resolvedInfo = channelEntry.getInfo(); + const channelRef: ChannelRefMessage[] = []; + const subchannelRef: SubchannelRefMessage[] = []; + + resolvedInfo.children.channels.forEach(el => { + channelRef.push(channelRefToMessage(el[1].ref)); + }); + + resolvedInfo.children.subchannels.forEach(el => { + subchannelRef.push(subchannelRefToMessage(el[1].ref)); + }); + return { ref: channelRefToMessage(channelEntry.ref), data: { @@ -569,12 +559,8 @@ function getChannelMessage(channelEntry: ChannelEntry): ChannelMessage { ), trace: resolvedInfo.trace.getTraceMessage(), }, - channel_ref: resolvedInfo.children.channels.map(ref => - channelRefToMessage(ref) - ), - subchannel_ref: resolvedInfo.children.subchannels.map(ref => - subchannelRefToMessage(ref) - ), + channel_ref: channelRef, + subchannel_ref: subchannelRef, }; } @@ -582,8 +568,9 @@ function GetChannel( call: ServerUnaryCall, callback: sendUnaryData ): void { - const channelId = Number.parseInt(call.request.channel_id); - const channelEntry = channels[channelId]; + const channelId = parseInt(call.request.channel_id, 10); + const channelEntry = + entityMaps[EntityTypes.channel].getElementByKey(channelId); if (channelEntry === undefined) { callback({ code: Status.NOT_FOUND, @@ -598,27 +585,34 @@ function GetTopChannels( call: ServerUnaryCall, callback: sendUnaryData ): void { - const maxResults = Number.parseInt(call.request.max_results); + const maxResults = parseInt(call.request.max_results, 10) || 100; const resultList: ChannelMessage[] = []; - let i = Number.parseInt(call.request.start_channel_id); - for (; i < channels.length; i++) { - const channelEntry = channels[i]; - if (channelEntry === undefined) { - continue; - } - resultList.push(getChannelMessage(channelEntry)); - if (resultList.length >= maxResults) { - break; - } + const startId = parseInt(call.request.start_channel_id, 10); + const channelEntries = entityMaps[EntityTypes.channel]; + + let i: OrderedMapIterator; + for ( + i = channelEntries.lowerBound(startId); + !i.equals(channelEntries.end()) && resultList.length < maxResults; + i = i.next() + ) { + resultList.push(getChannelMessage(i.pointer[1])); } + callback(null, { channel: resultList, - end: i >= servers.length, + end: i.equals(channelEntries.end()), }); } function getServerMessage(serverEntry: ServerEntry): ServerMessage { const resolvedInfo = serverEntry.getInfo(); + const listenSocket: SocketRefMessage[] = []; + + resolvedInfo.listenerChildren.sockets.forEach(el => { + listenSocket.push(socketRefToMessage(el[1].ref)); + }); + return { ref: serverRefToMessage(serverEntry.ref), data: { @@ -630,9 +624,7 @@ function getServerMessage(serverEntry: ServerEntry): ServerMessage { ), trace: resolvedInfo.trace.getTraceMessage(), }, - listen_socket: resolvedInfo.listenerChildren.sockets.map(ref => - socketRefToMessage(ref) - ), + listen_socket: listenSocket, }; } @@ -640,8 +632,9 @@ function GetServer( call: ServerUnaryCall, callback: sendUnaryData ): void { - const serverId = Number.parseInt(call.request.server_id); - const serverEntry = servers[serverId]; + const serverId = parseInt(call.request.server_id, 10); + const serverEntries = entityMaps[EntityTypes.server]; + const serverEntry = serverEntries.getElementByKey(serverId); if (serverEntry === undefined) { callback({ code: Status.NOT_FOUND, @@ -656,22 +649,23 @@ function GetServers( call: ServerUnaryCall, callback: sendUnaryData ): void { - const maxResults = Number.parseInt(call.request.max_results); + const maxResults = parseInt(call.request.max_results, 10) || 100; + const startId = parseInt(call.request.start_server_id, 10); + const serverEntries = entityMaps[EntityTypes.server]; const resultList: ServerMessage[] = []; - let i = Number.parseInt(call.request.start_server_id); - for (; i < servers.length; i++) { - const serverEntry = servers[i]; - if (serverEntry === undefined) { - continue; - } - resultList.push(getServerMessage(serverEntry)); - if (resultList.length >= maxResults) { - break; - } + + let i: OrderedMapIterator; + for ( + i = serverEntries.lowerBound(startId); + !i.equals(serverEntries.end()) && resultList.length < maxResults; + i = i.next() + ) { + resultList.push(getServerMessage(i.pointer[1])); } + callback(null, { server: resultList, - end: i >= servers.length, + end: i.equals(serverEntries.end()), }); } @@ -679,8 +673,9 @@ function GetSubchannel( call: ServerUnaryCall, callback: sendUnaryData ): void { - const subchannelId = Number.parseInt(call.request.subchannel_id); - const subchannelEntry = subchannels[subchannelId]; + const subchannelId = parseInt(call.request.subchannel_id, 10); + const subchannelEntry = + entityMaps[EntityTypes.subchannel].getElementByKey(subchannelId); if (subchannelEntry === undefined) { callback({ code: Status.NOT_FOUND, @@ -689,6 +684,12 @@ function GetSubchannel( return; } const resolvedInfo = subchannelEntry.getInfo(); + const listenSocket: SocketRefMessage[] = []; + + resolvedInfo.children.sockets.forEach(el => { + listenSocket.push(socketRefToMessage(el[1].ref)); + }); + const subchannelMessage: SubchannelMessage = { ref: subchannelRefToMessage(subchannelEntry.ref), data: { @@ -702,9 +703,7 @@ function GetSubchannel( ), trace: resolvedInfo.trace.getTraceMessage(), }, - socket_ref: resolvedInfo.children.sockets.map(ref => - socketRefToMessage(ref) - ), + socket_ref: listenSocket, }; callback(null, { subchannel: subchannelMessage }); } @@ -735,8 +734,8 @@ function GetSocket( call: ServerUnaryCall, callback: sendUnaryData ): void { - const socketId = Number.parseInt(call.request.socket_id); - const socketEntry = sockets[socketId]; + const socketId = parseInt(call.request.socket_id, 10); + const socketEntry = entityMaps[EntityTypes.socket].getElementByKey(socketId); if (socketEntry === undefined) { callback({ code: Status.NOT_FOUND, @@ -809,8 +808,9 @@ function GetServerSockets( >, callback: sendUnaryData ): void { - const serverId = Number.parseInt(call.request.server_id); - const serverEntry = servers[serverId]; + const serverId = parseInt(call.request.server_id, 10); + const serverEntry = entityMaps[EntityTypes.server].getElementByKey(serverId); + if (serverEntry === undefined) { callback({ code: Status.NOT_FOUND, @@ -818,28 +818,28 @@ function GetServerSockets( }); return; } - const startId = Number.parseInt(call.request.start_socket_id); - const maxResults = Number.parseInt(call.request.max_results); + + const startId = parseInt(call.request.start_socket_id, 10); + const maxResults = parseInt(call.request.max_results, 10) || 100; const resolvedInfo = serverEntry.getInfo(); // If we wanted to include listener sockets in the result, this line would // instead say // const allSockets = resolvedInfo.listenerChildren.sockets.concat(resolvedInfo.sessionChildren.sockets).sort((ref1, ref2) => ref1.id - ref2.id); - const allSockets = resolvedInfo.sessionChildren.sockets.sort( - (ref1, ref2) => ref1.id - ref2.id - ); + const allSockets = resolvedInfo.sessionChildren.sockets; const resultList: SocketRefMessage[] = []; - let i = 0; - for (; i < allSockets.length; i++) { - if (allSockets[i].id >= startId) { - resultList.push(socketRefToMessage(allSockets[i])); - if (resultList.length >= maxResults) { - break; - } - } + + let i: OrderedMapIterator; + for ( + i = allSockets.lowerBound(startId); + !i.equals(allSockets.end()) && resultList.length < maxResults; + i = i.next() + ) { + resultList.push(socketRefToMessage(i.pointer[1].ref)); } + callback(null, { socket_ref: resultList, - end: i >= allSockets.length, + end: i.equals(allSockets.end()), }); } diff --git a/packages/grpc-js/src/load-balancer-child-handler.ts b/packages/grpc-js/src/load-balancer-child-handler.ts index a29d6c92b..352ea7b81 100644 --- a/packages/grpc-js/src/load-balancer-child-handler.ts +++ b/packages/grpc-js/src/load-balancer-child-handler.ts @@ -25,7 +25,7 @@ import { Endpoint, SubchannelAddress } from './subchannel-address'; import { ChannelOptions } from './channel-options'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; -import { ChannelRef, SubchannelRef } from './channelz'; +import type { ChannelRef, SubchannelRef } from './channelz'; import { SubchannelInterface } from './subchannel-interface'; const TYPE_NAME = 'child_load_balancer_helper'; diff --git a/packages/grpc-js/src/load-balancer.ts b/packages/grpc-js/src/load-balancer.ts index f8071317a..fb353a59a 100644 --- a/packages/grpc-js/src/load-balancer.ts +++ b/packages/grpc-js/src/load-balancer.ts @@ -19,7 +19,7 @@ import { ChannelOptions } from './channel-options'; import { Endpoint, SubchannelAddress } from './subchannel-address'; import { ConnectivityState } from './connectivity-state'; import { Picker } from './picker'; -import { ChannelRef, SubchannelRef } from './channelz'; +import type { ChannelRef, SubchannelRef } from './channelz'; import { SubchannelInterface } from './subchannel-interface'; import { LoadBalancingConfig } from './service-config'; import { log } from './logging'; diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 95393fba9..d5aababfa 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -19,12 +19,12 @@ import { EventEmitter } from 'events'; import { Duplex, Readable, Writable } from 'stream'; import { Status } from './constants'; -import { Deserialize, Serialize } from './make-client'; +import type { Deserialize, Serialize } from './make-client'; import { Metadata } from './metadata'; -import { ObjectReadable, ObjectWritable } from './object-stream'; -import { StatusObject, PartialStatusObject } from './call-interface'; -import { Deadline } from './deadline'; -import { ServerInterceptingCallInterface } from './server-interceptors'; +import type { ObjectReadable, ObjectWritable } from './object-stream'; +import type { StatusObject, PartialStatusObject } from './call-interface'; +import type { Deadline } from './deadline'; +import type { ServerInterceptingCallInterface } from './server-interceptors'; export type ServerStatusResponse = Partial; @@ -330,7 +330,7 @@ export interface UnaryHandler { func: handleUnaryCall; serialize: Serialize; deserialize: Deserialize; - type: HandlerType; + type: 'unary'; path: string; } @@ -338,7 +338,7 @@ export interface ClientStreamingHandler { func: handleClientStreamingCall; serialize: Serialize; deserialize: Deserialize; - type: HandlerType; + type: 'clientStream'; path: string; } @@ -346,7 +346,7 @@ export interface ServerStreamingHandler { func: handleServerStreamingCall; serialize: Serialize; deserialize: Deserialize; - type: HandlerType; + type: 'serverStream'; path: string; } @@ -354,7 +354,7 @@ export interface BidiStreamingHandler { func: handleBidiStreamingCall; serialize: Serialize; deserialize: Deserialize; - type: HandlerType; + type: 'bidi'; path: string; } diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 46bd22ead..0a5bc0e9b 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -64,8 +64,11 @@ import { } from './uri-parser'; import { ChannelzCallTracker, + ChannelzCallTrackerStub, ChannelzChildrenTracker, + ChannelzChildrenTrackerStub, ChannelzTrace, + ChannelzTraceStub, registerChannelzServer, registerChannelzSocket, ServerInfo, @@ -87,6 +90,7 @@ import { CallEventTracker } from './transport'; const UNLIMITED_CONNECTION_AGE_MS = ~(1 << 31); const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); const KEEPALIVE_TIMEOUT_MS = 20000; +const MAX_CONNECTION_IDLE_MS = 30 * 60 * 1e3; // 30 min const { HTTP2_HEADER_PATH } = http2.constants; @@ -177,9 +181,10 @@ function getDefaultHandler(handlerType: HandlerType, methodName: string) { interface ChannelzSessionInfo { ref: SocketRef; - streamTracker: ChannelzCallTracker; + streamTracker: ChannelzCallTracker | ChannelzCallTrackerStub; messagesSent: number; messagesReceived: number; + keepAlivesSent: number; lastMessageSentTimestamp: Date | null; lastMessageReceivedTimestamp: Date | null; } @@ -243,6 +248,13 @@ export interface ServerOptions extends ChannelOptions { export class Server { private boundPorts: Map = new Map(); private http2Servers: Map = new Map(); + private sessionIdleTimeouts = new Map< + http2.Http2Session, + { + activeStreams: number; + timeout: NodeJS.Timeout | null; + } + >(); private handlers: Map = new Map< string, @@ -261,10 +273,14 @@ export class Server { // Channelz Info private readonly channelzEnabled: boolean = true; private channelzRef: ServerRef; - private channelzTrace = new ChannelzTrace(); - private callTracker = new ChannelzCallTracker(); - private listenerChildrenTracker = new ChannelzChildrenTracker(); - private sessionChildrenTracker = new ChannelzChildrenTracker(); + private channelzTrace: ChannelzTrace | ChannelzTraceStub; + private callTracker: ChannelzCallTracker | ChannelzCallTrackerStub; + private listenerChildrenTracker: + | ChannelzChildrenTracker + | ChannelzChildrenTrackerStub; + private sessionChildrenTracker: + | ChannelzChildrenTracker + | ChannelzChildrenTrackerStub; private readonly maxConnectionAgeMs: number; private readonly maxConnectionAgeGraceMs: number; @@ -272,6 +288,8 @@ export class Server { private readonly keepaliveTimeMs: number; private readonly keepaliveTimeoutMs: number; + private readonly sessionIdleTimeout: number; + private readonly interceptors: ServerInterceptor[]; /** @@ -284,14 +302,24 @@ export class Server { this.options = options ?? {}; if (this.options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; + this.channelzTrace = new ChannelzTraceStub(); + this.callTracker = new ChannelzCallTrackerStub(); + this.listenerChildrenTracker = new ChannelzChildrenTrackerStub(); + this.sessionChildrenTracker = new ChannelzChildrenTrackerStub(); + } else { + this.channelzTrace = new ChannelzTrace(); + this.callTracker = new ChannelzCallTracker(); + this.listenerChildrenTracker = new ChannelzChildrenTracker(); + this.sessionChildrenTracker = new ChannelzChildrenTracker(); } + this.channelzRef = registerChannelzServer( + 'server', () => this.getChannelzInfo(), this.channelzEnabled ); - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Server created'); - } + + this.channelzTrace.addTrace('CT_INFO', 'Server created'); this.maxConnectionAgeMs = this.options['grpc.max_connection_age_ms'] ?? UNLIMITED_CONNECTION_AGE_MS; this.maxConnectionAgeGraceMs = @@ -301,6 +329,9 @@ export class Server { this.options['grpc.keepalive_time_ms'] ?? KEEPALIVE_MAX_TIME_MS; this.keepaliveTimeoutMs = this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; + this.sessionIdleTimeout = + this.options['grpc.max_connection_idle'] ?? MAX_CONNECTION_IDLE_MS; + this.commonServerOptions = { maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, }; @@ -382,7 +413,7 @@ export class Server { streamsFailed: sessionInfo.streamTracker.callsFailed, messagesSent: sessionInfo.messagesSent, messagesReceived: sessionInfo.messagesReceived, - keepAlivesSent: 0, + keepAlivesSent: sessionInfo.keepAlivesSent, lastLocalStreamCreatedTimestamp: null, lastRemoteStreamCreatedTimestamp: sessionInfo.streamTracker.lastCallStartedTimestamp, @@ -581,9 +612,8 @@ export class Server { const channelzRef = this.registerListenerToChannelz( boundSubchannelAddress ); - if (this.channelzEnabled) { - this.listenerChildrenTracker.refChild(channelzRef); - } + this.listenerChildrenTracker.refChild(channelzRef); + this.http2Servers.set(http2Server, { channelzRef: channelzRef, sessions: new Set(), @@ -854,7 +884,7 @@ export class Server { ); const serverInfo = this.http2Servers.get(server); server.close(() => { - if (this.channelzEnabled && serverInfo) { + if (serverInfo) { this.listenerChildrenTracker.unrefChild(serverInfo.channelzRef); unregisterChannelzRef(serverInfo.channelzRef); } @@ -870,15 +900,15 @@ export class Server { this.trace('Closing session initiated by ' + session.socket?.remoteAddress); const sessionInfo = this.sessions.get(session); const closeCallback = () => { - if (this.channelzEnabled && sessionInfo) { + if (sessionInfo) { this.sessionChildrenTracker.unrefChild(sessionInfo.ref); unregisterChannelzRef(sessionInfo.ref); + this.sessions.delete(session); } - this.sessions.delete(session); callback?.(); }; if (session.closed) { - process.nextTick(closeCallback); + queueMicrotask(closeCallback); } else { session.close(closeCallback); } @@ -956,14 +986,13 @@ export class Server { const allSessions: Set = new Set(); for (const http2Server of boundPortObject.listeningServers) { const serverEntry = this.http2Servers.get(http2Server); - if (!serverEntry) { - continue; - } - for (const session of serverEntry.sessions) { - allSessions.add(session); - this.closeSession(session, () => { - allSessions.delete(session); - }); + if (serverEntry) { + for (const session of serverEntry.sessions) { + allSessions.add(session); + this.closeSession(session, () => { + allSessions.delete(session); + }); + } } } /* After the grace time ends, send another goaway to all remaining sessions @@ -995,9 +1024,7 @@ export class Server { session.destroy(http2.constants.NGHTTP2_CANCEL as any); }); this.sessions.clear(); - if (this.channelzEnabled) { - unregisterChannelzRef(this.channelzRef); - } + unregisterChannelzRef(this.channelzRef); this.shutdown = true; } @@ -1049,9 +1076,7 @@ export class Server { tryShutdown(callback: (error?: Error) => void): void { const wrappedCallback = (error?: Error) => { - if (this.channelzEnabled) { - unregisterChannelzRef(this.channelzRef); - } + unregisterChannelzRef(this.channelzRef); callback(error); }; let pendingChecks = 0; @@ -1065,24 +1090,26 @@ export class Server { } this.shutdown = true; - for (const server of this.http2Servers.keys()) { + for (const [serverKey, server] of this.http2Servers.entries()) { pendingChecks++; - const serverString = this.http2Servers.get(server)!.channelzRef.name; + const serverString = server.channelzRef.name; this.trace('Waiting for server ' + serverString + ' to close'); - this.closeServer(server, () => { + this.closeServer(serverKey, () => { this.trace('Server ' + serverString + ' finished closing'); maybeCallback(); }); + + for (const session of server.sessions.keys()) { + pendingChecks++; + const sessionString = session.socket?.remoteAddress; + this.trace('Waiting for session ' + sessionString + ' to close'); + this.closeSession(session, () => { + this.trace('Session ' + sessionString + ' finished closing'); + maybeCallback(); + }); + } } - for (const session of this.sessions.keys()) { - pendingChecks++; - const sessionString = session.socket?.remoteAddress; - this.trace('Waiting for session ' + sessionString + ' to close'); - this.closeSession(session, () => { - this.trace('Session ' + sessionString + ' finished closing'); - maybeCallback(); - }); - } + if (pendingChecks === 0) { wrappedCallback(); } @@ -1160,213 +1187,161 @@ export class Server { }; stream.respond(trailersToSend, { endStream: true }); - if (this.channelzEnabled) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); - } + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); } - private _channelzHandler( - stream: http2.ServerHttp2Stream, - headers: http2.IncomingHttpHeaders + private _sessionHandler( + http2Server: http2.Http2Server | http2.Http2SecureServer ) { - const channelzSessionInfo = this.sessions.get( - stream.session as http2.ServerHttp2Session - ); + return (session: http2.ServerHttp2Session) => { + this.http2Servers.get(http2Server)?.sessions.add(session); + + let connectionAgeTimer: NodeJS.Timeout | null = null; + let connectionAgeGraceTimer: NodeJS.Timeout | null = null; + let sessionClosedByServer = false; - this.callTracker.addCallStarted(); - channelzSessionInfo?.streamTracker.addCallStarted(); + const idleTimeoutObj = this.enableIdleTimeout(session); - if (!this._verifyContentType(stream, headers)) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); - return; - } + if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { + // Apply a random jitter within a +/-10% range + const jitterMagnitude = this.maxConnectionAgeMs / 10; + const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude; - const path = headers[HTTP2_HEADER_PATH] as string; + connectionAgeTimer = setTimeout(() => { + sessionClosedByServer = true; - const handler = this._retrieveHandler(path); - if (!handler) { - this._respondWithError( - getUnimplementedStatusResponse(path), - stream, - channelzSessionInfo - ); - return; - } + this.trace( + `Connection dropped by max connection age: ${session.socket?.remoteAddress}` + ); - const callEventTracker: CallEventTracker = { - addMessageSent: () => { - if (channelzSessionInfo) { - channelzSessionInfo.messagesSent += 1; - channelzSessionInfo.lastMessageSentTimestamp = new Date(); - } - }, - addMessageReceived: () => { - if (channelzSessionInfo) { - channelzSessionInfo.messagesReceived += 1; - channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); - } - }, - onCallEnd: status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }, - onStreamEnd: success => { - if (channelzSessionInfo) { - if (success) { - channelzSessionInfo.streamTracker.addCallSucceeded(); - } else { - channelzSessionInfo.streamTracker.addCallFailed(); + try { + session.goaway( + http2.constants.NGHTTP2_NO_ERROR, + ~(1 << 31), + Buffer.from('max_age') + ); + } catch (e) { + // The goaway can't be sent because the session is already closed + session.destroy(); + return; } - } - }, - }; + session.close(); - const call = getServerInterceptingCall( - this.interceptors, - stream, - headers, - callEventTracker, - handler, - this.options - ); + /* Allow a grace period after sending the GOAWAY before forcibly + * closing the connection. */ + if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { + connectionAgeGraceTimer = setTimeout(() => { + session.destroy(); + }, this.maxConnectionAgeGraceMs).unref?.(); + } + }, this.maxConnectionAgeMs + jitter).unref?.(); + } - if (!this._runHandlerForCall(call, handler)) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); + const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => { + const timeoutTImer = setTimeout(() => { + sessionClosedByServer = true; + session.close(); + }, this.keepaliveTimeoutMs).unref?.(); - call.sendStatus({ - code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}`, - }); - } - } + try { + session.ping( + (err: Error | null, duration: number, payload: Buffer) => { + clearTimeout(timeoutTImer); - private _streamHandler( - stream: http2.ServerHttp2Stream, - headers: http2.IncomingHttpHeaders - ) { - if (this._verifyContentType(stream, headers) !== true) { - return; - } + if (err) { + sessionClosedByServer = true; + this.trace( + `Connection dropped due to error of a ping frame ${err.message} return in ${duration}` + ); + session.close(); + } + } + ); + } catch (e) { + // The ping can't be sent because the session is already closed + session.destroy(); + } + }, this.keepaliveTimeMs).unref?.(); - const path = headers[HTTP2_HEADER_PATH] as string; + session.on('close', () => { + if (!sessionClosedByServer) { + this.trace( + `Connection dropped by client ${session.socket?.remoteAddress}` + ); + } - const handler = this._retrieveHandler(path); - if (!handler) { - this._respondWithError( - getUnimplementedStatusResponse(path), - stream, - null - ); - return; - } + if (connectionAgeTimer) { + clearTimeout(connectionAgeTimer); + } - const call = getServerInterceptingCall( - this.interceptors, - stream, - headers, - null, - handler, - this.options - ); + if (connectionAgeGraceTimer) { + clearTimeout(connectionAgeGraceTimer); + } - if (!this._runHandlerForCall(call, handler)) { - call.sendStatus({ - code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}`, - }); - } - } + if (keeapliveTimeTimer) { + clearTimeout(keeapliveTimeTimer); + } - private _runHandlerForCall( - call: ServerInterceptingCallInterface, - handler: Handler - ): boolean { - const { type } = handler; - if (type === 'unary') { - handleUnary(call, handler as UntypedUnaryHandler); - } else if (type === 'clientStream') { - handleClientStreaming(call, handler as UntypedClientStreamingHandler); - } else if (type === 'serverStream') { - handleServerStreaming(call, handler as UntypedServerStreamingHandler); - } else if (type === 'bidi') { - handleBidiStreaming(call, handler as UntypedBidiStreamingHandler); - } else { - return false; - } + clearTimeout(idleTimeoutObj.timeout); + this.sessionIdleTimeouts.delete(session); - return true; + this.http2Servers.get(http2Server)?.sessions.delete(session); + }); + }; } - private _setupHandlers( + private _channelzSessionHandler( http2Server: http2.Http2Server | http2.Http2SecureServer - ): void { - if (http2Server === null) { - return; - } - - const serverAddress = http2Server.address(); - let serverAddressString = 'null'; - if (serverAddress) { - if (typeof serverAddress === 'string') { - serverAddressString = serverAddress; - } else { - serverAddressString = serverAddress.address + ':' + serverAddress.port; - } - } - this.serverAddressString = serverAddressString; - - const handler = this.channelzEnabled - ? this._channelzHandler - : this._streamHandler; - - http2Server.on('stream', handler.bind(this)); - http2Server.on('session', session => { + ) { + return (session: http2.ServerHttp2Session) => { const channelzRef = registerChannelzSocket( - session.socket.remoteAddress ?? 'unknown', + session.socket?.remoteAddress ?? 'unknown', this.getChannelzSessionInfoGetter(session), this.channelzEnabled ); const channelzSessionInfo: ChannelzSessionInfo = { ref: channelzRef, - streamTracker: new ChannelzCallTracker(), + streamTracker: this.channelzEnabled + ? new ChannelzCallTracker() + : new ChannelzCallTrackerStub(), messagesSent: 0, messagesReceived: 0, + keepAlivesSent: 0, lastMessageSentTimestamp: null, lastMessageReceivedTimestamp: null, }; this.http2Servers.get(http2Server)?.sessions.add(session); this.sessions.set(session, channelzSessionInfo); - const clientAddress = session.socket.remoteAddress; - if (this.channelzEnabled) { - this.channelzTrace.addTrace( - 'CT_INFO', - 'Connection established by client ' + clientAddress - ); - this.sessionChildrenTracker.refChild(channelzRef); - } + const clientAddress = `${session.socket.remoteAddress}:${session.socket.remotePort}`; + + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection established by client ' + clientAddress + ); + this.trace('Connection established by client ' + clientAddress); + this.sessionChildrenTracker.refChild(channelzRef); + let connectionAgeTimer: NodeJS.Timeout | null = null; let connectionAgeGraceTimer: NodeJS.Timeout | null = null; let sessionClosedByServer = false; + + const idleTimeoutObj = this.enableIdleTimeout(session); + if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { // Apply a random jitter within a +/-10% range const jitterMagnitude = this.maxConnectionAgeMs / 10; const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude; + connectionAgeTimer = setTimeout(() => { sessionClosedByServer = true; - if (this.channelzEnabled) { - this.channelzTrace.addTrace( - 'CT_INFO', - 'Connection dropped by max connection age from ' + clientAddress - ); - } + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped by max connection age from ' + clientAddress + ); + try { session.goaway( http2.constants.NGHTTP2_NO_ERROR, @@ -1379,6 +1354,7 @@ export class Server { return; } session.close(); + /* Allow a grace period after sending the GOAWAY before forcibly * closing the connection. */ if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { @@ -1388,52 +1364,316 @@ export class Server { } }, this.maxConnectionAgeMs + jitter).unref?.(); } + const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => { const timeoutTImer = setTimeout(() => { sessionClosedByServer = true; - if (this.channelzEnabled) { - this.channelzTrace.addTrace( - 'CT_INFO', - 'Connection dropped by keepalive timeout from ' + clientAddress - ); - } + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped by keepalive timeout from ' + clientAddress + ); + session.close(); }, this.keepaliveTimeoutMs).unref?.(); try { session.ping( (err: Error | null, duration: number, payload: Buffer) => { clearTimeout(timeoutTImer); + + if (err) { + sessionClosedByServer = true; + this.channelzTrace.addTrace( + 'CT_INFO', + `Connection dropped due to error of a ping frame ${err.message} return in ${duration}` + ); + + session.close(); + } } ); + channelzSessionInfo.keepAlivesSent += 1; } catch (e) { // The ping can't be sent because the session is already closed session.destroy(); } }, this.keepaliveTimeMs).unref?.(); + session.on('close', () => { - if (this.channelzEnabled) { - if (!sessionClosedByServer) { - this.channelzTrace.addTrace( - 'CT_INFO', - 'Connection dropped by client ' + clientAddress - ); - } - this.sessionChildrenTracker.unrefChild(channelzRef); - unregisterChannelzRef(channelzRef); + if (!sessionClosedByServer) { + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped by client ' + clientAddress + ); } + this.trace( + `DROPPING ${channelzRef.name} - ${channelzRef.kind} - ${channelzRef.id}` + ); + this.sessionChildrenTracker.unrefChild(channelzRef); + unregisterChannelzRef(channelzRef); + if (connectionAgeTimer) { clearTimeout(connectionAgeTimer); } + if (connectionAgeGraceTimer) { clearTimeout(connectionAgeGraceTimer); } + if (keeapliveTimeTimer) { clearTimeout(keeapliveTimeTimer); } + + clearTimeout(idleTimeoutObj.timeout); + this.sessionIdleTimeouts.delete(session); + this.http2Servers.get(http2Server)?.sessions.delete(session); this.sessions.delete(session); }); - }); + }; + } + + private _channelzHandler(http2Server: http2.Http2Server) { + return ( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) => { + // for handling idle timeout + this.onStreamOpened(stream); + + const channelzSessionInfo = this.sessions.get( + stream.session as http2.ServerHttp2Session + ); + + this.callTracker.addCallStarted(); + channelzSessionInfo?.streamTracker.addCallStarted(); + + if (!this._verifyContentType(stream, headers)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); + return; + } + + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError( + getUnimplementedStatusResponse(path), + stream, + channelzSessionInfo + ); + return; + } + + const callEventTracker: CallEventTracker = { + addMessageSent: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesSent += 1; + channelzSessionInfo.lastMessageSentTimestamp = new Date(); + } + }, + addMessageReceived: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesReceived += 1; + channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); + } + }, + onCallEnd: status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }, + onStreamEnd: success => { + if (channelzSessionInfo) { + if (success) { + channelzSessionInfo.streamTracker.addCallSucceeded(); + } else { + channelzSessionInfo.streamTracker.addCallFailed(); + } + } + }, + }; + + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + callEventTracker, + handler, + this.options + ); + + if (!this._runHandlerForCall(call, handler)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); + + call.sendStatus({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}`, + }); + } + }; + } + + private _streamHandler(http2Server: http2.Http2Server) { + return ( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) => { + // for handling idle timeout + this.onStreamOpened(stream); + + if (this._verifyContentType(stream, headers) !== true) { + return; + } + + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError( + getUnimplementedStatusResponse(path), + stream, + null + ); + return; + } + + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + null, + handler, + this.options + ); + + if (!this._runHandlerForCall(call, handler)) { + call.sendStatus({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}`, + }); + } + }; + } + + private _runHandlerForCall( + call: ServerInterceptingCallInterface, + handler: + | UntypedUnaryHandler + | UntypedClientStreamingHandler + | UntypedServerStreamingHandler + | UntypedBidiStreamingHandler + ): boolean { + const { type } = handler; + if (type === 'unary') { + handleUnary(call, handler); + } else if (type === 'clientStream') { + handleClientStreaming(call, handler); + } else if (type === 'serverStream') { + handleServerStreaming(call, handler); + } else if (type === 'bidi') { + handleBidiStreaming(call, handler); + } else { + return false; + } + + return true; + } + + private _setupHandlers( + http2Server: http2.Http2Server | http2.Http2SecureServer + ): void { + if (http2Server === null) { + return; + } + + const serverAddress = http2Server.address(); + let serverAddressString = 'null'; + if (serverAddress) { + if (typeof serverAddress === 'string') { + serverAddressString = serverAddress; + } else { + serverAddressString = serverAddress.address + ':' + serverAddress.port; + } + } + this.serverAddressString = serverAddressString; + + const handler = this.channelzEnabled + ? this._channelzHandler(http2Server) + : this._streamHandler(http2Server); + + const sessionHandler = this.channelzEnabled + ? this._channelzSessionHandler(http2Server) + : this._sessionHandler(http2Server); + + http2Server.on('stream', handler); + http2Server.on('session', sessionHandler); + } + + private enableIdleTimeout(session: http2.ServerHttp2Session) { + const idleTimeoutObj = { + activeStreams: 0, + timeout: setTimeout( + this.onIdleTimeout, + this.sessionIdleTimeout, + this, + session + ).unref(), + }; + this.sessionIdleTimeouts.set(session, idleTimeoutObj); + + this.trace(`Enable idle timeout for ${session.socket?.remoteAddress}`); + + return idleTimeoutObj; + } + + private onIdleTimeout(ctx: Server, session: http2.ServerHttp2Session) { + ctx.trace(`Idle timeout for ${session.socket?.remoteAddress}`); + ctx.closeSession(session); + } + + private onStreamOpened(stream: http2.ServerHttp2Stream) { + const session = stream.session as http2.ServerHttp2Session; + this.trace(`Stream opened for ${session.socket?.remoteAddress}`); + const idleTimeoutObj = this.sessionIdleTimeouts.get(session); + if (idleTimeoutObj) { + idleTimeoutObj.activeStreams += 1; + if (idleTimeoutObj.timeout) { + clearTimeout(idleTimeoutObj.timeout); + idleTimeoutObj.timeout = null; + } + + this.trace( + `onStreamOpened: adding on stream close event for ${session.socket?.remoteAddress}` + ); + stream.once('close', () => this.onStreamClose(session)); + } else { + this.trace( + `onStreamOpened: missing stream for ${session.socket?.remoteAddress}` + ); + } + } + + private onStreamClose(session: http2.ServerHttp2Session) { + this.trace(`Stream closed for ${session.socket?.remoteAddress}`); + const idleTimeoutObj = this.sessionIdleTimeouts.get(session); + if (idleTimeoutObj) { + idleTimeoutObj.activeStreams -= 1; + if (idleTimeoutObj.activeStreams === 0) { + this.trace( + `onStreamClose: set idle timeout for ${this.sessionIdleTimeout}ms ${session.socket?.remoteAddress}` + ); + idleTimeoutObj.timeout = setTimeout( + this.onIdleTimeout, + this.sessionIdleTimeout, + this, + session + ).unref(); + } + } } } diff --git a/packages/grpc-js/src/subchannel-interface.ts b/packages/grpc-js/src/subchannel-interface.ts index c26669ba3..6c314189a 100644 --- a/packages/grpc-js/src/subchannel-interface.ts +++ b/packages/grpc-js/src/subchannel-interface.ts @@ -15,7 +15,7 @@ * */ -import { SubchannelRef } from './channelz'; +import type { SubchannelRef } from './channelz'; import { ConnectivityState } from './connectivity-state'; import { Subchannel } from './subchannel'; diff --git a/packages/grpc-js/src/subchannel.ts b/packages/grpc-js/src/subchannel.ts index 63e254cf3..95b600c4c 100644 --- a/packages/grpc-js/src/subchannel.ts +++ b/packages/grpc-js/src/subchannel.ts @@ -31,10 +31,13 @@ import { SubchannelRef, ChannelzTrace, ChannelzChildrenTracker, + ChannelzChildrenTrackerStub, SubchannelInfo, registerChannelzSubchannel, ChannelzCallTracker, + ChannelzCallTrackerStub, unregisterChannelzRef, + ChannelzTraceStub, } from './channelz'; import { ConnectivityStateListener, @@ -89,12 +92,15 @@ export class Subchannel { // Channelz info private readonly channelzEnabled: boolean = true; private channelzRef: SubchannelRef; - private channelzTrace: ChannelzTrace; - private callTracker = new ChannelzCallTracker(); - private childrenTracker = new ChannelzChildrenTracker(); + + private channelzTrace: ChannelzTrace | ChannelzTraceStub; + private callTracker: ChannelzCallTracker | ChannelzCallTrackerStub; + private childrenTracker: + | ChannelzChildrenTracker + | ChannelzChildrenTrackerStub; // Channelz socket info - private streamTracker = new ChannelzCallTracker(); + private streamTracker: ChannelzCallTracker | ChannelzCallTrackerStub; /** * A class representing a connection to a single backend. @@ -127,16 +133,24 @@ export class Subchannel { if (options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; + this.channelzTrace = new ChannelzTraceStub(); + this.callTracker = new ChannelzCallTrackerStub(); + this.childrenTracker = new ChannelzChildrenTrackerStub(); + this.streamTracker = new ChannelzCallTrackerStub(); + } else { + this.channelzTrace = new ChannelzTrace(); + this.callTracker = new ChannelzCallTracker(); + this.childrenTracker = new ChannelzChildrenTracker(); + this.streamTracker = new ChannelzCallTracker(); } - this.channelzTrace = new ChannelzTrace(); + this.channelzRef = registerChannelzSubchannel( this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled ); - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); - } + + this.channelzTrace.addTrace('CT_INFO', 'Subchannel created'); this.trace( 'Subchannel constructed with options ' + JSON.stringify(options, undefined, 2) @@ -338,12 +352,8 @@ export class Subchannel { this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount - 1)); this.refcount -= 1; if (this.refcount === 0) { - if (this.channelzEnabled) { - this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); - } - if (this.channelzEnabled) { - unregisterChannelzRef(this.channelzRef); - } + this.channelzTrace.addTrace('CT_INFO', 'Shutting down'); + unregisterChannelzRef(this.channelzRef); process.nextTick(() => { this.transitionToState( [ConnectivityState.CONNECTING, ConnectivityState.READY], diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index c4941b068..620488635 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -28,6 +28,7 @@ import { ChannelCredentials } from './channel-credentials'; import { ChannelOptions } from './channel-options'; import { ChannelzCallTracker, + ChannelzCallTrackerStub, registerChannelzSocket, SocketInfo, SocketRef, @@ -136,7 +137,7 @@ class Http2Transport implements Transport { // Channelz info private channelzRef: SocketRef; private readonly channelzEnabled: boolean = true; - private streamTracker = new ChannelzCallTracker(); + private streamTracker: ChannelzCallTracker | ChannelzCallTrackerStub; private keepalivesSent = 0; private messagesSent = 0; private messagesReceived = 0; @@ -159,12 +160,17 @@ class Http2Transport implements Transport { if (options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; + this.streamTracker = new ChannelzCallTrackerStub(); + } else { + this.streamTracker = new ChannelzCallTracker(); } + this.channelzRef = registerChannelzSocket( this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled ); + // Build user-agent string. this.userAgent = [ options['grpc.primary_user_agent'], @@ -192,6 +198,7 @@ class Http2Transport implements Transport { this.stopKeepalivePings(); this.handleDisconnect(); }); + session.once( 'goaway', (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => { @@ -214,11 +221,13 @@ class Http2Transport implements Transport { this.reportDisconnectToOwner(tooManyPings); } ); + session.once('error', error => { /* Do nothing here. Any error should also trigger a close event, which is * where we want to handle that. */ this.trace('connection closed with error ' + (error as Error).message); }); + if (logging.isTracerEnabled(TRACER_NAME)) { session.on('remoteSettings', (settings: http2.Settings) => { this.trace( @@ -237,6 +246,7 @@ class Http2Transport implements Transport { ); }); } + /* Start the keepalive timer last, because this can trigger trace logs, * which should only happen after everything else is set up. */ if (this.keepaliveWithoutCalls) { @@ -625,6 +635,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { private session: http2.ClientHttp2Session | null = null; private isShutdown = false; constructor(private channelTarget: GrpcUri) {} + private trace(text: string) { logging.trace( LogVerbosity.DEBUG, @@ -632,6 +643,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { uriToString(this.channelTarget) + ' ' + text ); } + private createSession( address: SubchannelAddress, credentials: ChannelCredentials, @@ -641,6 +653,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { if (this.isShutdown) { return Promise.reject(); } + return new Promise((resolve, reject) => { let remoteName: string | null; if (proxyConnectionResult.realTarget) { @@ -767,6 +780,7 @@ export class Http2SubchannelConnector implements SubchannelConnector { }); }); } + connect( address: SubchannelAddress, credentials: ChannelCredentials, diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index 88aa129aa..eaa701f18 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -31,7 +31,7 @@ import { HealthListener, SubchannelInterface, } from '../src/subchannel-interface'; -import { SubchannelRef } from '../src/channelz'; +import { EntityTypes, SubchannelRef } from '../src/channelz'; import { Subchannel } from '../src/subchannel'; import { ConnectivityState } from '../src/connectivity-state'; @@ -196,7 +196,7 @@ export class MockSubchannel implements SubchannelInterface { unref(): void {} getChannelzRef(): SubchannelRef { return { - kind: 'subchannel', + kind: EntityTypes.subchannel, id: -1, name: this.address, }; From a4a676d3788976ef3e8bb1049f37b971eb8d8d4d Mon Sep 17 00:00:00 2001 From: AVVS Date: Tue, 27 Feb 2024 14:17:32 -0800 Subject: [PATCH 665/694] chore: move new functions towards the end of the class --- packages/grpc-js/src/server.ts | 362 ++++++++++++++++----------------- 1 file changed, 181 insertions(+), 181 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 0a5bc0e9b..1229b8c6c 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -1191,6 +1191,187 @@ export class Server { channelzSessionInfo?.streamTracker.addCallFailed(); } + private _channelzHandler(http2Server: http2.Http2Server) { + return ( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) => { + // for handling idle timeout + this.onStreamOpened(stream); + + const channelzSessionInfo = this.sessions.get( + stream.session as http2.ServerHttp2Session + ); + + this.callTracker.addCallStarted(); + channelzSessionInfo?.streamTracker.addCallStarted(); + + if (!this._verifyContentType(stream, headers)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); + return; + } + + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError( + getUnimplementedStatusResponse(path), + stream, + channelzSessionInfo + ); + return; + } + + const callEventTracker: CallEventTracker = { + addMessageSent: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesSent += 1; + channelzSessionInfo.lastMessageSentTimestamp = new Date(); + } + }, + addMessageReceived: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesReceived += 1; + channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); + } + }, + onCallEnd: status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }, + onStreamEnd: success => { + if (channelzSessionInfo) { + if (success) { + channelzSessionInfo.streamTracker.addCallSucceeded(); + } else { + channelzSessionInfo.streamTracker.addCallFailed(); + } + } + }, + }; + + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + callEventTracker, + handler, + this.options + ); + + if (!this._runHandlerForCall(call, handler)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); + + call.sendStatus({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}`, + }); + } + }; + } + + private _streamHandler(http2Server: http2.Http2Server) { + return ( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) => { + // for handling idle timeout + this.onStreamOpened(stream); + + if (this._verifyContentType(stream, headers) !== true) { + return; + } + + const path = headers[HTTP2_HEADER_PATH] as string; + + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError( + getUnimplementedStatusResponse(path), + stream, + null + ); + return; + } + + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + null, + handler, + this.options + ); + + if (!this._runHandlerForCall(call, handler)) { + call.sendStatus({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}`, + }); + } + }; + } + + private _runHandlerForCall( + call: ServerInterceptingCallInterface, + handler: + | UntypedUnaryHandler + | UntypedClientStreamingHandler + | UntypedServerStreamingHandler + | UntypedBidiStreamingHandler + ): boolean { + const { type } = handler; + if (type === 'unary') { + handleUnary(call, handler); + } else if (type === 'clientStream') { + handleClientStreaming(call, handler); + } else if (type === 'serverStream') { + handleServerStreaming(call, handler); + } else if (type === 'bidi') { + handleBidiStreaming(call, handler); + } else { + return false; + } + + return true; + } + + private _setupHandlers( + http2Server: http2.Http2Server | http2.Http2SecureServer + ): void { + if (http2Server === null) { + return; + } + + const serverAddress = http2Server.address(); + let serverAddressString = 'null'; + if (serverAddress) { + if (typeof serverAddress === 'string') { + serverAddressString = serverAddress; + } else { + serverAddressString = serverAddress.address + ':' + serverAddress.port; + } + } + this.serverAddressString = serverAddressString; + + const handler = this.channelzEnabled + ? this._channelzHandler(http2Server) + : this._streamHandler(http2Server); + + const sessionHandler = this.channelzEnabled + ? this._channelzSessionHandler(http2Server) + : this._sessionHandler(http2Server); + + http2Server.on('stream', handler); + http2Server.on('session', sessionHandler); + } + private _sessionHandler( http2Server: http2.Http2Server | http2.Http2SecureServer ) { @@ -1432,187 +1613,6 @@ export class Server { }; } - private _channelzHandler(http2Server: http2.Http2Server) { - return ( - stream: http2.ServerHttp2Stream, - headers: http2.IncomingHttpHeaders - ) => { - // for handling idle timeout - this.onStreamOpened(stream); - - const channelzSessionInfo = this.sessions.get( - stream.session as http2.ServerHttp2Session - ); - - this.callTracker.addCallStarted(); - channelzSessionInfo?.streamTracker.addCallStarted(); - - if (!this._verifyContentType(stream, headers)) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); - return; - } - - const path = headers[HTTP2_HEADER_PATH] as string; - - const handler = this._retrieveHandler(path); - if (!handler) { - this._respondWithError( - getUnimplementedStatusResponse(path), - stream, - channelzSessionInfo - ); - return; - } - - const callEventTracker: CallEventTracker = { - addMessageSent: () => { - if (channelzSessionInfo) { - channelzSessionInfo.messagesSent += 1; - channelzSessionInfo.lastMessageSentTimestamp = new Date(); - } - }, - addMessageReceived: () => { - if (channelzSessionInfo) { - channelzSessionInfo.messagesReceived += 1; - channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); - } - }, - onCallEnd: status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); - } else { - this.callTracker.addCallFailed(); - } - }, - onStreamEnd: success => { - if (channelzSessionInfo) { - if (success) { - channelzSessionInfo.streamTracker.addCallSucceeded(); - } else { - channelzSessionInfo.streamTracker.addCallFailed(); - } - } - }, - }; - - const call = getServerInterceptingCall( - this.interceptors, - stream, - headers, - callEventTracker, - handler, - this.options - ); - - if (!this._runHandlerForCall(call, handler)) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); - - call.sendStatus({ - code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}`, - }); - } - }; - } - - private _streamHandler(http2Server: http2.Http2Server) { - return ( - stream: http2.ServerHttp2Stream, - headers: http2.IncomingHttpHeaders - ) => { - // for handling idle timeout - this.onStreamOpened(stream); - - if (this._verifyContentType(stream, headers) !== true) { - return; - } - - const path = headers[HTTP2_HEADER_PATH] as string; - - const handler = this._retrieveHandler(path); - if (!handler) { - this._respondWithError( - getUnimplementedStatusResponse(path), - stream, - null - ); - return; - } - - const call = getServerInterceptingCall( - this.interceptors, - stream, - headers, - null, - handler, - this.options - ); - - if (!this._runHandlerForCall(call, handler)) { - call.sendStatus({ - code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}`, - }); - } - }; - } - - private _runHandlerForCall( - call: ServerInterceptingCallInterface, - handler: - | UntypedUnaryHandler - | UntypedClientStreamingHandler - | UntypedServerStreamingHandler - | UntypedBidiStreamingHandler - ): boolean { - const { type } = handler; - if (type === 'unary') { - handleUnary(call, handler); - } else if (type === 'clientStream') { - handleClientStreaming(call, handler); - } else if (type === 'serverStream') { - handleServerStreaming(call, handler); - } else if (type === 'bidi') { - handleBidiStreaming(call, handler); - } else { - return false; - } - - return true; - } - - private _setupHandlers( - http2Server: http2.Http2Server | http2.Http2SecureServer - ): void { - if (http2Server === null) { - return; - } - - const serverAddress = http2Server.address(); - let serverAddressString = 'null'; - if (serverAddress) { - if (typeof serverAddress === 'string') { - serverAddressString = serverAddress; - } else { - serverAddressString = serverAddress.address + ':' + serverAddress.port; - } - } - this.serverAddressString = serverAddressString; - - const handler = this.channelzEnabled - ? this._channelzHandler(http2Server) - : this._streamHandler(http2Server); - - const sessionHandler = this.channelzEnabled - ? this._channelzSessionHandler(http2Server) - : this._sessionHandler(http2Server); - - http2Server.on('stream', handler); - http2Server.on('session', sessionHandler); - } - private enableIdleTimeout(session: http2.ServerHttp2Session) { const idleTimeoutObj = { activeStreams: 0, From b8f157ed21add2daffe11e320781e345d7cc38fb Mon Sep 17 00:00:00 2001 From: AVVS Date: Tue, 27 Feb 2024 14:30:55 -0800 Subject: [PATCH 666/694] chore: revert interface -> type change in channelz --- packages/grpc-js/src/channelz.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 6d70b7543..4eab762a8 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -330,25 +330,25 @@ export interface SocketInfo { remoteFlowControlWindow: number | null; } -type ChannelEntry = { +interface ChannelEntry { ref: ChannelRef; getInfo(): ChannelInfo; -}; +} -type SubchannelEntry = { +interface SubchannelEntry { ref: SubchannelRef; getInfo(): SubchannelInfo; -}; +} -type ServerEntry = { +interface ServerEntry { ref: ServerRef; getInfo(): ServerInfo; -}; +} -type SocketEntry = { +interface SocketEntry { ref: SocketRef; getInfo(): SocketInfo; -}; +} export const enum EntityTypes { channel = 'channel', From 0b79b7420a77564babca5e8718ef95c6aedbea71 Mon Sep 17 00:00:00 2001 From: AVVS Date: Tue, 27 Feb 2024 14:35:02 -0800 Subject: [PATCH 667/694] chore: cleanup traces --- packages/grpc-js/src/server.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 1229b8c6c..67b5fdf73 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -1586,9 +1586,7 @@ export class Server { 'Connection dropped by client ' + clientAddress ); } - this.trace( - `DROPPING ${channelzRef.name} - ${channelzRef.kind} - ${channelzRef.id}` - ); + this.sessionChildrenTracker.unrefChild(channelzRef); unregisterChannelzRef(channelzRef); @@ -1637,7 +1635,7 @@ export class Server { private onStreamOpened(stream: http2.ServerHttp2Stream) { const session = stream.session as http2.ServerHttp2Session; - this.trace(`Stream opened for ${session.socket?.remoteAddress}`); + const idleTimeoutObj = this.sessionIdleTimeouts.get(session); if (idleTimeoutObj) { idleTimeoutObj.activeStreams += 1; @@ -1646,26 +1644,16 @@ export class Server { idleTimeoutObj.timeout = null; } - this.trace( - `onStreamOpened: adding on stream close event for ${session.socket?.remoteAddress}` - ); stream.once('close', () => this.onStreamClose(session)); - } else { - this.trace( - `onStreamOpened: missing stream for ${session.socket?.remoteAddress}` - ); } } private onStreamClose(session: http2.ServerHttp2Session) { - this.trace(`Stream closed for ${session.socket?.remoteAddress}`); const idleTimeoutObj = this.sessionIdleTimeouts.get(session); + if (idleTimeoutObj) { idleTimeoutObj.activeStreams -= 1; if (idleTimeoutObj.activeStreams === 0) { - this.trace( - `onStreamClose: set idle timeout for ${this.sessionIdleTimeout}ms ${session.socket?.remoteAddress}` - ); idleTimeoutObj.timeout = setTimeout( this.onIdleTimeout, this.sessionIdleTimeout, From 74102fcc872759d18a62cf5098256eb521064b0c Mon Sep 17 00:00:00 2001 From: AVVS Date: Tue, 27 Feb 2024 14:39:24 -0800 Subject: [PATCH 668/694] chore: extraneous closure, dont need server ref --- packages/grpc-js/src/server.ts | 216 ++++++++++++++++----------------- 1 file changed, 106 insertions(+), 110 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 67b5fdf73..eed097f83 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -1191,131 +1191,127 @@ export class Server { channelzSessionInfo?.streamTracker.addCallFailed(); } - private _channelzHandler(http2Server: http2.Http2Server) { - return ( - stream: http2.ServerHttp2Stream, - headers: http2.IncomingHttpHeaders - ) => { - // for handling idle timeout - this.onStreamOpened(stream); - - const channelzSessionInfo = this.sessions.get( - stream.session as http2.ServerHttp2Session - ); + private _channelzHandler( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) { + // for handling idle timeout + this.onStreamOpened(stream); - this.callTracker.addCallStarted(); - channelzSessionInfo?.streamTracker.addCallStarted(); + const channelzSessionInfo = this.sessions.get( + stream.session as http2.ServerHttp2Session + ); - if (!this._verifyContentType(stream, headers)) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); - return; - } + this.callTracker.addCallStarted(); + channelzSessionInfo?.streamTracker.addCallStarted(); - const path = headers[HTTP2_HEADER_PATH] as string; + if (!this._verifyContentType(stream, headers)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); + return; + } - const handler = this._retrieveHandler(path); - if (!handler) { - this._respondWithError( - getUnimplementedStatusResponse(path), - stream, - channelzSessionInfo - ); - return; - } + const path = headers[HTTP2_HEADER_PATH] as string; - const callEventTracker: CallEventTracker = { - addMessageSent: () => { - if (channelzSessionInfo) { - channelzSessionInfo.messagesSent += 1; - channelzSessionInfo.lastMessageSentTimestamp = new Date(); - } - }, - addMessageReceived: () => { - if (channelzSessionInfo) { - channelzSessionInfo.messagesReceived += 1; - channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); - } - }, - onCallEnd: status => { - if (status.code === Status.OK) { - this.callTracker.addCallSucceeded(); + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError( + getUnimplementedStatusResponse(path), + stream, + channelzSessionInfo + ); + return; + } + + const callEventTracker: CallEventTracker = { + addMessageSent: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesSent += 1; + channelzSessionInfo.lastMessageSentTimestamp = new Date(); + } + }, + addMessageReceived: () => { + if (channelzSessionInfo) { + channelzSessionInfo.messagesReceived += 1; + channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); + } + }, + onCallEnd: status => { + if (status.code === Status.OK) { + this.callTracker.addCallSucceeded(); + } else { + this.callTracker.addCallFailed(); + } + }, + onStreamEnd: success => { + if (channelzSessionInfo) { + if (success) { + channelzSessionInfo.streamTracker.addCallSucceeded(); } else { - this.callTracker.addCallFailed(); - } - }, - onStreamEnd: success => { - if (channelzSessionInfo) { - if (success) { - channelzSessionInfo.streamTracker.addCallSucceeded(); - } else { - channelzSessionInfo.streamTracker.addCallFailed(); - } + channelzSessionInfo.streamTracker.addCallFailed(); } - }, - }; + } + }, + }; - const call = getServerInterceptingCall( - this.interceptors, - stream, - headers, - callEventTracker, - handler, - this.options - ); + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + callEventTracker, + handler, + this.options + ); - if (!this._runHandlerForCall(call, handler)) { - this.callTracker.addCallFailed(); - channelzSessionInfo?.streamTracker.addCallFailed(); + if (!this._runHandlerForCall(call, handler)) { + this.callTracker.addCallFailed(); + channelzSessionInfo?.streamTracker.addCallFailed(); - call.sendStatus({ - code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}`, - }); - } - }; + call.sendStatus({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}`, + }); + } } - private _streamHandler(http2Server: http2.Http2Server) { - return ( - stream: http2.ServerHttp2Stream, - headers: http2.IncomingHttpHeaders - ) => { - // for handling idle timeout - this.onStreamOpened(stream); - - if (this._verifyContentType(stream, headers) !== true) { - return; - } + private _streamHandler( + stream: http2.ServerHttp2Stream, + headers: http2.IncomingHttpHeaders + ) { + // for handling idle timeout + this.onStreamOpened(stream); - const path = headers[HTTP2_HEADER_PATH] as string; + if (this._verifyContentType(stream, headers) !== true) { + return; + } - const handler = this._retrieveHandler(path); - if (!handler) { - this._respondWithError( - getUnimplementedStatusResponse(path), - stream, - null - ); - return; - } + const path = headers[HTTP2_HEADER_PATH] as string; - const call = getServerInterceptingCall( - this.interceptors, + const handler = this._retrieveHandler(path); + if (!handler) { + this._respondWithError( + getUnimplementedStatusResponse(path), stream, - headers, - null, - handler, - this.options + null ); + return; + } - if (!this._runHandlerForCall(call, handler)) { - call.sendStatus({ - code: Status.INTERNAL, - details: `Unknown handler type: ${handler.type}`, - }); - } - }; + const call = getServerInterceptingCall( + this.interceptors, + stream, + headers, + null, + handler, + this.options + ); + + if (!this._runHandlerForCall(call, handler)) { + call.sendStatus({ + code: Status.INTERNAL, + details: `Unknown handler type: ${handler.type}`, + }); + } } private _runHandlerForCall( @@ -1361,14 +1357,14 @@ export class Server { this.serverAddressString = serverAddressString; const handler = this.channelzEnabled - ? this._channelzHandler(http2Server) - : this._streamHandler(http2Server); + ? this._channelzHandler + : this._streamHandler; const sessionHandler = this.channelzEnabled ? this._channelzSessionHandler(http2Server) : this._sessionHandler(http2Server); - http2Server.on('stream', handler); + http2Server.on('stream', handler.bind(this)); http2Server.on('session', sessionHandler); } From 11a98b5f373ff32ba5f4aceabfa8a98197ec1675 Mon Sep 17 00:00:00 2001 From: AVVS Date: Tue, 27 Feb 2024 16:49:20 -0800 Subject: [PATCH 669/694] chore: updated docs, cached onStreamClose per session --- packages/grpc-js/README.md | 3 + packages/grpc-js/src/channel-options.ts | 1 + packages/grpc-js/src/server.ts | 188 ++++++++++++++---------- 3 files changed, 116 insertions(+), 76 deletions(-) diff --git a/packages/grpc-js/README.md b/packages/grpc-js/README.md index eb04ece2f..f3b682f3c 100644 --- a/packages/grpc-js/README.md +++ b/packages/grpc-js/README.md @@ -60,6 +60,9 @@ Many channel arguments supported in `grpc` are not supported in `@grpc/grpc-js`. - `grpc.enable_channelz` - `grpc.dns_min_time_between_resolutions_ms` - `grpc.enable_retries` + - `grpc.max_connection_age_ms` + - `grpc.max_connection_age_grace_ms` + - `grpc.max_connection_idle_ms` - `grpc.per_rpc_retry_buffer_size` - `grpc.retry_buffer_size` - `grpc.service_config_disable_resolution` diff --git a/packages/grpc-js/src/channel-options.ts b/packages/grpc-js/src/channel-options.ts index aa1e6c83e..6804852e2 100644 --- a/packages/grpc-js/src/channel-options.ts +++ b/packages/grpc-js/src/channel-options.ts @@ -54,6 +54,7 @@ export interface ChannelOptions { 'grpc.retry_buffer_size'?: number; 'grpc.max_connection_age_ms'?: number; 'grpc.max_connection_age_grace_ms'?: number; + 'grpc.max_connection_idle_ms'?: number; 'grpc-node.max_session_memory'?: number; 'grpc.service_config_disable_resolution'?: number; 'grpc.client_idle_timeout_ms'?: number; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index eed097f83..8a3a29fd8 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -90,7 +90,7 @@ import { CallEventTracker } from './transport'; const UNLIMITED_CONNECTION_AGE_MS = ~(1 << 31); const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); const KEEPALIVE_TIMEOUT_MS = 20000; -const MAX_CONNECTION_IDLE_MS = 30 * 60 * 1e3; // 30 min +const MAX_CONNECTION_IDLE_MS = ~(1 << 31); const { HTTP2_HEADER_PATH } = http2.constants; @@ -241,6 +241,12 @@ interface Http2ServerInfo { sessions: Set; } +interface SessionIdleTimeoutTracker { + activeStreams: number; + timeout: NodeJS.Timeout | null; + onClose: (session: http2.ServerHttp2Session) => void | null; +} + export interface ServerOptions extends ChannelOptions { interceptors?: ServerInterceptor[]; } @@ -249,11 +255,8 @@ export class Server { private boundPorts: Map = new Map(); private http2Servers: Map = new Map(); private sessionIdleTimeouts = new Map< - http2.Http2Session, - { - activeStreams: number; - timeout: NodeJS.Timeout | null; - } + http2.ServerHttp2Session, + SessionIdleTimeoutTracker >(); private handlers: Map = new Map< @@ -330,7 +333,7 @@ export class Server { this.keepaliveTimeoutMs = this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; this.sessionIdleTimeout = - this.options['grpc.max_connection_idle'] ?? MAX_CONNECTION_IDLE_MS; + this.options['grpc.max_connection_idle_ms'] ?? MAX_CONNECTION_IDLE_MS; this.commonServerOptions = { maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, @@ -903,7 +906,6 @@ export class Server { if (sessionInfo) { this.sessionChildrenTracker.unrefChild(sessionInfo.ref); unregisterChannelzRef(sessionInfo.ref); - this.sessions.delete(session); } callback?.(); }; @@ -1001,7 +1003,7 @@ export class Server { for (const session of allSessions) { session.destroy(http2.constants.NGHTTP2_CANCEL as any); } - }, graceTimeMs).unref?.(); + }, graceTimeMs).unref(); } forceShutdown(): void { @@ -1376,6 +1378,7 @@ export class Server { let connectionAgeTimer: NodeJS.Timeout | null = null; let connectionAgeGraceTimer: NodeJS.Timeout | null = null; + let keeapliveTimeTimer: NodeJS.Timeout | null = null; let sessionClosedByServer = false; const idleTimeoutObj = this.enableIdleTimeout(session); @@ -1389,7 +1392,8 @@ export class Server { sessionClosedByServer = true; this.trace( - `Connection dropped by max connection age: ${session.socket?.remoteAddress}` + 'Connection dropped by max connection age: ' + + session.socket?.remoteAddress ); try { @@ -1410,36 +1414,38 @@ export class Server { if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { connectionAgeGraceTimer = setTimeout(() => { session.destroy(); - }, this.maxConnectionAgeGraceMs).unref?.(); + }, this.maxConnectionAgeGraceMs).unref(); } - }, this.maxConnectionAgeMs + jitter).unref?.(); + }, this.maxConnectionAgeMs + jitter).unref(); } - const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => { - const timeoutTImer = setTimeout(() => { - sessionClosedByServer = true; - session.close(); - }, this.keepaliveTimeoutMs).unref?.(); - - try { - session.ping( - (err: Error | null, duration: number, payload: Buffer) => { - clearTimeout(timeoutTImer); - - if (err) { - sessionClosedByServer = true; - this.trace( - `Connection dropped due to error of a ping frame ${err.message} return in ${duration}` - ); - session.close(); + if (this.keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS) { + keeapliveTimeTimer = setInterval(() => { + const timeoutTimer = setTimeout(() => { + sessionClosedByServer = true; + session.close(); + }, this.keepaliveTimeoutMs).unref(); + + try { + session.ping( + (err: Error | null, duration: number, payload: Buffer) => { + clearTimeout(timeoutTimer); + + if (err) { + sessionClosedByServer = true; + this.trace( + `Connection dropped due to error of a ping frame ${err.message} return in ${duration}` + ); + session.close(); + } } - } - ); - } catch (e) { - // The ping can't be sent because the session is already closed - session.destroy(); - } - }, this.keepaliveTimeMs).unref?.(); + ); + } catch (e) { + // The ping can't be sent because the session is already closed + session.destroy(); + } + }, this.keepaliveTimeMs).unref(); + } session.on('close', () => { if (!sessionClosedByServer) { @@ -1460,8 +1466,12 @@ export class Server { clearTimeout(keeapliveTimeTimer); } - clearTimeout(idleTimeoutObj.timeout); - this.sessionIdleTimeouts.delete(session); + if (idleTimeoutObj !== null) { + if (idleTimeoutObj.timeout !== null) { + clearTimeout(idleTimeoutObj.timeout); + } + this.sessionIdleTimeouts.delete(session); + } this.http2Servers.get(http2Server)?.sessions.delete(session); }); @@ -1503,6 +1513,7 @@ export class Server { let connectionAgeTimer: NodeJS.Timeout | null = null; let connectionAgeGraceTimer: NodeJS.Timeout | null = null; + let keeapliveTimeTimer: NodeJS.Timeout | null = null; let sessionClosedByServer = false; const idleTimeoutObj = this.enableIdleTimeout(session); @@ -1537,43 +1548,48 @@ export class Server { if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { connectionAgeGraceTimer = setTimeout(() => { session.destroy(); - }, this.maxConnectionAgeGraceMs).unref?.(); + }, this.maxConnectionAgeGraceMs).unref(); } - }, this.maxConnectionAgeMs + jitter).unref?.(); + }, this.maxConnectionAgeMs + jitter).unref(); } - const keeapliveTimeTimer: NodeJS.Timeout | null = setInterval(() => { - const timeoutTImer = setTimeout(() => { - sessionClosedByServer = true; - this.channelzTrace.addTrace( - 'CT_INFO', - 'Connection dropped by keepalive timeout from ' + clientAddress - ); + if (this.keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS) { + keeapliveTimeTimer = setInterval(() => { + const timeoutTImer = setTimeout(() => { + sessionClosedByServer = true; + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped by keepalive timeout from ' + clientAddress + ); - session.close(); - }, this.keepaliveTimeoutMs).unref?.(); - try { - session.ping( - (err: Error | null, duration: number, payload: Buffer) => { - clearTimeout(timeoutTImer); - - if (err) { - sessionClosedByServer = true; - this.channelzTrace.addTrace( - 'CT_INFO', - `Connection dropped due to error of a ping frame ${err.message} return in ${duration}` - ); - - session.close(); + session.close(); + }, this.keepaliveTimeoutMs).unref(); + try { + session.ping( + (err: Error | null, duration: number, payload: Buffer) => { + clearTimeout(timeoutTImer); + + if (err) { + sessionClosedByServer = true; + this.channelzTrace.addTrace( + 'CT_INFO', + 'Connection dropped due to error of a ping frame ' + + err.message + + ' return in ' + + duration + ); + + session.close(); + } } - } - ); - channelzSessionInfo.keepAlivesSent += 1; - } catch (e) { - // The ping can't be sent because the session is already closed - session.destroy(); - } - }, this.keepaliveTimeMs).unref?.(); + ); + channelzSessionInfo.keepAlivesSent += 1; + } catch (e) { + // The ping can't be sent because the session is already closed + session.destroy(); + } + }, this.keepaliveTimeMs).unref(); + } session.on('close', () => { if (!sessionClosedByServer) { @@ -1598,8 +1614,12 @@ export class Server { clearTimeout(keeapliveTimeTimer); } - clearTimeout(idleTimeoutObj.timeout); - this.sessionIdleTimeouts.delete(session); + if (idleTimeoutObj !== null) { + if (idleTimeoutObj.timeout !== null) { + clearTimeout(idleTimeoutObj.timeout); + } + this.sessionIdleTimeouts.delete(session); + } this.http2Servers.get(http2Server)?.sessions.delete(session); this.sessions.delete(session); @@ -1607,9 +1627,16 @@ export class Server { }; } - private enableIdleTimeout(session: http2.ServerHttp2Session) { + private enableIdleTimeout( + session: http2.ServerHttp2Session + ): SessionIdleTimeoutTracker | null { + if (this.sessionIdleTimeout >= MAX_CONNECTION_IDLE_MS) { + return null; + } + const idleTimeoutObj = { activeStreams: 0, + onClose: this.onStreamClose.bind(this, session), // so that we don't recreate it each time timeout: setTimeout( this.onIdleTimeout, this.sessionIdleTimeout, @@ -1619,13 +1646,22 @@ export class Server { }; this.sessionIdleTimeouts.set(session, idleTimeoutObj); - this.trace(`Enable idle timeout for ${session.socket?.remoteAddress}`); + const { socket } = session; + this.trace( + 'Enable idle timeout for ' + + socket.remoteAddress + + ':' + + socket.remotePort + ); return idleTimeoutObj; } private onIdleTimeout(ctx: Server, session: http2.ServerHttp2Session) { - ctx.trace(`Idle timeout for ${session.socket?.remoteAddress}`); + const { socket } = session; + ctx.trace( + 'Idle timeout for ' + socket?.remoteAddress + ':' + socket?.remotePort + ); ctx.closeSession(session); } @@ -1640,7 +1676,7 @@ export class Server { idleTimeoutObj.timeout = null; } - stream.once('close', () => this.onStreamClose(session)); + stream.once('close', idleTimeoutObj.onClose); } } From bedb5055e89b39125d9466ee6f3c504f130792f6 Mon Sep 17 00:00:00 2001 From: AVVS Date: Wed, 28 Feb 2024 13:36:24 -0800 Subject: [PATCH 670/694] refactor: no clearTimeout/null timers, use .refresh() + count refs --- packages/grpc-js/src/server.ts | 51 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 8a3a29fd8..32c18ea94 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -243,7 +243,8 @@ interface Http2ServerInfo { interface SessionIdleTimeoutTracker { activeStreams: number; - timeout: NodeJS.Timeout | null; + lastIdle: number; + timeout: NodeJS.Timeout; onClose: (session: http2.ServerHttp2Session) => void | null; } @@ -292,6 +293,7 @@ export class Server { private readonly keepaliveTimeoutMs: number; private readonly sessionIdleTimeout: number; + private readonly sessionHalfIdleTimeout: number; private readonly interceptors: ServerInterceptor[]; @@ -334,6 +336,7 @@ export class Server { this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; this.sessionIdleTimeout = this.options['grpc.max_connection_idle_ms'] ?? MAX_CONNECTION_IDLE_MS; + this.sessionHalfIdleTimeout = Math.ceil(this.sessionIdleTimeout / 2); this.commonServerOptions = { maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, @@ -1467,9 +1470,7 @@ export class Server { } if (idleTimeoutObj !== null) { - if (idleTimeoutObj.timeout !== null) { - clearTimeout(idleTimeoutObj.timeout); - } + clearTimeout(idleTimeoutObj.timeout); this.sessionIdleTimeouts.delete(session); } @@ -1615,9 +1616,7 @@ export class Server { } if (idleTimeoutObj !== null) { - if (idleTimeoutObj.timeout !== null) { - clearTimeout(idleTimeoutObj.timeout); - } + clearTimeout(idleTimeoutObj.timeout); this.sessionIdleTimeouts.delete(session); } @@ -1634,12 +1633,14 @@ export class Server { return null; } - const idleTimeoutObj = { + const idleTimeoutObj: SessionIdleTimeoutTracker = { activeStreams: 0, + lastIdle: Date.now(), onClose: this.onStreamClose.bind(this, session), // so that we don't recreate it each time + // this is 50% of the actual timeout, we will check half-way through and .refresh() for a subsequent check timeout: setTimeout( this.onIdleTimeout, - this.sessionIdleTimeout, + this.sessionHalfIdleTimeout, this, session ).unref(), @@ -1660,9 +1661,25 @@ export class Server { private onIdleTimeout(ctx: Server, session: http2.ServerHttp2Session) { const { socket } = session; ctx.trace( - 'Idle timeout for ' + socket?.remoteAddress + ':' + socket?.remotePort + 'Session idle timeout checkpoint for ' + + socket?.remoteAddress + + ':' + + socket?.remotePort ); - ctx.closeSession(session); + + const sessionInfo = ctx.sessionIdleTimeouts.get(session); + // if it is called while we have activeStreams - timer will not be rescheduled + // until last active stream is closed, then it will call .refresh() on the timer + // important part is to not clearTimeout(timer) or it becomes unusable + // for future refreshes + if (sessionInfo && sessionInfo.activeStreams === 0) { + const idleFor = Date.now() - sessionInfo.lastIdle; + if (idleFor >= this.sessionIdleTimeout) { + ctx.closeSession(session); + } else { + sessionInfo.timeout.refresh(); + } + } } private onStreamOpened(stream: http2.ServerHttp2Stream) { @@ -1671,11 +1688,6 @@ export class Server { const idleTimeoutObj = this.sessionIdleTimeouts.get(session); if (idleTimeoutObj) { idleTimeoutObj.activeStreams += 1; - if (idleTimeoutObj.timeout) { - clearTimeout(idleTimeoutObj.timeout); - idleTimeoutObj.timeout = null; - } - stream.once('close', idleTimeoutObj.onClose); } } @@ -1686,12 +1698,7 @@ export class Server { if (idleTimeoutObj) { idleTimeoutObj.activeStreams -= 1; if (idleTimeoutObj.activeStreams === 0) { - idleTimeoutObj.timeout = setTimeout( - this.onIdleTimeout, - this.sessionIdleTimeout, - this, - session - ).unref(); + idleTimeoutObj.timeout.refresh(); } } } From b873dce908ddfefc42da43be8a612c4cdc2eefcc Mon Sep 17 00:00:00 2001 From: AVVS Date: Wed, 28 Feb 2024 14:26:42 -0800 Subject: [PATCH 671/694] chore: simplify idle timeout further, fix wrong ref --- packages/grpc-js/src/server.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 32c18ea94..46fecc2e3 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -293,7 +293,6 @@ export class Server { private readonly keepaliveTimeoutMs: number; private readonly sessionIdleTimeout: number; - private readonly sessionHalfIdleTimeout: number; private readonly interceptors: ServerInterceptor[]; @@ -336,7 +335,6 @@ export class Server { this.options['grpc.keepalive_timeout_ms'] ?? KEEPALIVE_TIMEOUT_MS; this.sessionIdleTimeout = this.options['grpc.max_connection_idle_ms'] ?? MAX_CONNECTION_IDLE_MS; - this.sessionHalfIdleTimeout = Math.ceil(this.sessionIdleTimeout / 2); this.commonServerOptions = { maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, @@ -1636,11 +1634,10 @@ export class Server { const idleTimeoutObj: SessionIdleTimeoutTracker = { activeStreams: 0, lastIdle: Date.now(), - onClose: this.onStreamClose.bind(this, session), // so that we don't recreate it each time - // this is 50% of the actual timeout, we will check half-way through and .refresh() for a subsequent check + onClose: this.onStreamClose.bind(this, session), timeout: setTimeout( this.onIdleTimeout, - this.sessionHalfIdleTimeout, + this.sessionIdleTimeout, this, session ).unref(), @@ -1658,7 +1655,11 @@ export class Server { return idleTimeoutObj; } - private onIdleTimeout(ctx: Server, session: http2.ServerHttp2Session) { + private onIdleTimeout( + this: undefined, + ctx: Server, + session: http2.ServerHttp2Session + ) { const { socket } = session; ctx.trace( 'Session idle timeout checkpoint for ' + @@ -1668,17 +1669,17 @@ export class Server { ); const sessionInfo = ctx.sessionIdleTimeouts.get(session); + // if it is called while we have activeStreams - timer will not be rescheduled // until last active stream is closed, then it will call .refresh() on the timer // important part is to not clearTimeout(timer) or it becomes unusable // for future refreshes - if (sessionInfo && sessionInfo.activeStreams === 0) { - const idleFor = Date.now() - sessionInfo.lastIdle; - if (idleFor >= this.sessionIdleTimeout) { - ctx.closeSession(session); - } else { - sessionInfo.timeout.refresh(); - } + if ( + sessionInfo !== undefined && + sessionInfo.activeStreams === 0 && + Date.now() - sessionInfo.lastIdle >= ctx.sessionIdleTimeout + ) { + ctx.closeSession(session); } } @@ -1698,6 +1699,7 @@ export class Server { if (idleTimeoutObj) { idleTimeoutObj.activeStreams -= 1; if (idleTimeoutObj.activeStreams === 0) { + idleTimeoutObj.lastIdle = Date.now(); idleTimeoutObj.timeout.refresh(); } } From 62e8ea97e659ea3c98ab77a38702fb5e0b67fed8 Mon Sep 17 00:00:00 2001 From: AVVS Date: Sat, 2 Mar 2024 07:58:54 -0800 Subject: [PATCH 672/694] chore: tests & cleanup of unref?.() --- packages/grpc-js/package.json | 2 +- .../grpc-js/src/load-balancer-pick-first.ts | 3 +- packages/grpc-js/src/resolver-dns.ts | 3 +- .../grpc-js/src/resolving-load-balancer.ts | 1 + packages/grpc-js/src/server.ts | 207 ++++++++++-------- packages/grpc-js/src/transport.ts | 3 +- packages/grpc-js/test/common.ts | 21 ++ packages/grpc-js/test/test-idle-timer.ts | 86 ++++++++ 8 files changed, 235 insertions(+), 91 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 34d8b558b..1c7935473 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -20,9 +20,9 @@ "@types/lodash": "^4.14.202", "@types/mocha": "^10.0.6", "@types/ncp": "^2.0.8", + "@types/node": ">=20.11.20", "@types/pify": "^5.0.4", "@types/semver": "^7.5.8", - "@types/node": ">=20.11.20", "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "@typescript-eslint/typescript-estree": "^7.1.0", diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 29bbfbf07..f6c43b33d 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -415,7 +415,8 @@ export class PickFirstLoadBalancer implements LoadBalancer { } this.connectionDelayTimeout = setTimeout(() => { this.startNextSubchannelConnecting(subchannelIndex + 1); - }, CONNECTION_DELAY_INTERVAL_MS).unref?.(); + }, CONNECTION_DELAY_INTERVAL_MS); + this.connectionDelayTimeout.unref?.(); } private pickSubchannel(subchannel: SubchannelInterface) { diff --git a/packages/grpc-js/src/resolver-dns.ts b/packages/grpc-js/src/resolver-dns.ts index 6652839b0..6463c2656 100644 --- a/packages/grpc-js/src/resolver-dns.ts +++ b/packages/grpc-js/src/resolver-dns.ts @@ -309,7 +309,8 @@ class DnsResolver implements Resolver { if (this.continueResolving) { this.startResolutionWithBackoff(); } - }, this.minTimeBetweenResolutionsMs).unref?.(); + }, this.minTimeBetweenResolutionsMs); + this.nextResolutionTimer.unref?.(); this.isNextResolutionTimerRunning = true; } diff --git a/packages/grpc-js/src/resolving-load-balancer.ts b/packages/grpc-js/src/resolving-load-balancer.ts index 82c4ff436..72aef0dfd 100644 --- a/packages/grpc-js/src/resolving-load-balancer.ts +++ b/packages/grpc-js/src/resolving-load-balancer.ts @@ -212,6 +212,7 @@ export class ResolvingLoadBalancer implements LoadBalancer { methodConfig: [], }; } + this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); this.childLoadBalancer = new ChildLoadBalancerHandler( { diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 46fecc2e3..b0fd5e7d3 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -95,6 +95,7 @@ const MAX_CONNECTION_IDLE_MS = ~(1 << 31); const { HTTP2_HEADER_PATH } = http2.constants; const TRACER_NAME = 'server'; +const kMaxAge = Buffer.from('max_age'); type AnyHttp2Server = http2.Http2Server | http2.Http2SecureServer; @@ -369,65 +370,61 @@ export class Server { private getChannelzSessionInfoGetter( session: http2.ServerHttp2Session - ): () => SocketInfo { - return () => { - const sessionInfo = this.sessions.get(session)!; - const sessionSocket = session.socket; - const remoteAddress = sessionSocket.remoteAddress - ? stringToSubchannelAddress( - sessionSocket.remoteAddress, - sessionSocket.remotePort - ) - : null; - const localAddress = sessionSocket.localAddress - ? stringToSubchannelAddress( - sessionSocket.localAddress!, - sessionSocket.localPort - ) - : null; - let tlsInfo: TlsInfo | null; - if (session.encrypted) { - const tlsSocket: TLSSocket = sessionSocket as TLSSocket; - const cipherInfo: CipherNameAndProtocol & { standardName?: string } = - tlsSocket.getCipher(); - const certificate = tlsSocket.getCertificate(); - const peerCertificate = tlsSocket.getPeerCertificate(); - tlsInfo = { - cipherSuiteStandardName: cipherInfo.standardName ?? null, - cipherSuiteOtherName: cipherInfo.standardName - ? null - : cipherInfo.name, - localCertificate: - certificate && 'raw' in certificate ? certificate.raw : null, - remoteCertificate: - peerCertificate && 'raw' in peerCertificate - ? peerCertificate.raw - : null, - }; - } else { - tlsInfo = null; - } - const socketInfo: SocketInfo = { - remoteAddress: remoteAddress, - localAddress: localAddress, - security: tlsInfo, - remoteName: null, - streamsStarted: sessionInfo.streamTracker.callsStarted, - streamsSucceeded: sessionInfo.streamTracker.callsSucceeded, - streamsFailed: sessionInfo.streamTracker.callsFailed, - messagesSent: sessionInfo.messagesSent, - messagesReceived: sessionInfo.messagesReceived, - keepAlivesSent: sessionInfo.keepAlivesSent, - lastLocalStreamCreatedTimestamp: null, - lastRemoteStreamCreatedTimestamp: - sessionInfo.streamTracker.lastCallStartedTimestamp, - lastMessageSentTimestamp: sessionInfo.lastMessageSentTimestamp, - lastMessageReceivedTimestamp: sessionInfo.lastMessageReceivedTimestamp, - localFlowControlWindow: session.state.localWindowSize ?? null, - remoteFlowControlWindow: session.state.remoteWindowSize ?? null, + ): SocketInfo { + const sessionInfo = this.sessions.get(session)!; + const sessionSocket = session.socket; + const remoteAddress = sessionSocket.remoteAddress + ? stringToSubchannelAddress( + sessionSocket.remoteAddress, + sessionSocket.remotePort + ) + : null; + const localAddress = sessionSocket.localAddress + ? stringToSubchannelAddress( + sessionSocket.localAddress!, + sessionSocket.localPort + ) + : null; + let tlsInfo: TlsInfo | null; + if (session.encrypted) { + const tlsSocket: TLSSocket = sessionSocket as TLSSocket; + const cipherInfo: CipherNameAndProtocol & { standardName?: string } = + tlsSocket.getCipher(); + const certificate = tlsSocket.getCertificate(); + const peerCertificate = tlsSocket.getPeerCertificate(); + tlsInfo = { + cipherSuiteStandardName: cipherInfo.standardName ?? null, + cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, + localCertificate: + certificate && 'raw' in certificate ? certificate.raw : null, + remoteCertificate: + peerCertificate && 'raw' in peerCertificate + ? peerCertificate.raw + : null, }; - return socketInfo; + } else { + tlsInfo = null; + } + const socketInfo: SocketInfo = { + remoteAddress: remoteAddress, + localAddress: localAddress, + security: tlsInfo, + remoteName: null, + streamsStarted: sessionInfo.streamTracker.callsStarted, + streamsSucceeded: sessionInfo.streamTracker.callsSucceeded, + streamsFailed: sessionInfo.streamTracker.callsFailed, + messagesSent: sessionInfo.messagesSent, + messagesReceived: sessionInfo.messagesReceived, + keepAlivesSent: sessionInfo.keepAlivesSent, + lastLocalStreamCreatedTimestamp: null, + lastRemoteStreamCreatedTimestamp: + sessionInfo.streamTracker.lastCallStartedTimestamp, + lastMessageSentTimestamp: sessionInfo.lastMessageSentTimestamp, + lastMessageReceivedTimestamp: sessionInfo.lastMessageReceivedTimestamp, + localFlowControlWindow: session.state.localWindowSize ?? null, + remoteFlowControlWindow: session.state.remoteWindowSize ?? null, }; + return socketInfo; } private trace(text: string): void { @@ -1004,7 +1001,7 @@ export class Server { for (const session of allSessions) { session.destroy(http2.constants.NGHTTP2_CANCEL as any); } - }, graceTimeMs).unref(); + }, graceTimeMs).unref?.(); } forceShutdown(): void { @@ -1380,6 +1377,7 @@ export class Server { let connectionAgeTimer: NodeJS.Timeout | null = null; let connectionAgeGraceTimer: NodeJS.Timeout | null = null; let keeapliveTimeTimer: NodeJS.Timeout | null = null; + let keepaliveTimeoutTimer: NodeJS.Timeout | null = null; let sessionClosedByServer = false; const idleTimeoutObj = this.enableIdleTimeout(session); @@ -1401,7 +1399,7 @@ export class Server { session.goaway( http2.constants.NGHTTP2_NO_ERROR, ~(1 << 31), - Buffer.from('max_age') + kMaxAge ); } catch (e) { // The goaway can't be sent because the session is already closed @@ -1415,37 +1413,47 @@ export class Server { if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { connectionAgeGraceTimer = setTimeout(() => { session.destroy(); - }, this.maxConnectionAgeGraceMs).unref(); + }, this.maxConnectionAgeGraceMs); + connectionAgeGraceTimer.unref?.(); } - }, this.maxConnectionAgeMs + jitter).unref(); + }, this.maxConnectionAgeMs + jitter); + connectionAgeTimer.unref?.(); } if (this.keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS) { keeapliveTimeTimer = setInterval(() => { - const timeoutTimer = setTimeout(() => { + keepaliveTimeoutTimer = setTimeout(() => { sessionClosedByServer = true; session.close(); - }, this.keepaliveTimeoutMs).unref(); + }, this.keepaliveTimeoutMs); + keepaliveTimeoutTimer.unref?.(); try { session.ping( (err: Error | null, duration: number, payload: Buffer) => { - clearTimeout(timeoutTimer); + if (keepaliveTimeoutTimer) { + clearTimeout(keepaliveTimeoutTimer); + } if (err) { sessionClosedByServer = true; this.trace( - `Connection dropped due to error of a ping frame ${err.message} return in ${duration}` + 'Connection dropped due to error of a ping frame ' + + err.message + + ' return in ' + + duration ); session.close(); } } ); } catch (e) { + clearTimeout(keepaliveTimeoutTimer); // The ping can't be sent because the session is already closed session.destroy(); } - }, this.keepaliveTimeMs).unref(); + }, this.keepaliveTimeMs); + keeapliveTimeTimer.unref?.(); } session.on('close', () => { @@ -1464,7 +1472,10 @@ export class Server { } if (keeapliveTimeTimer) { - clearTimeout(keeapliveTimeTimer); + clearInterval(keeapliveTimeTimer); + if (keepaliveTimeoutTimer) { + clearTimeout(keepaliveTimeoutTimer); + } } if (idleTimeoutObj !== null) { @@ -1483,15 +1494,13 @@ export class Server { return (session: http2.ServerHttp2Session) => { const channelzRef = registerChannelzSocket( session.socket?.remoteAddress ?? 'unknown', - this.getChannelzSessionInfoGetter(session), + this.getChannelzSessionInfoGetter.bind(this, session), this.channelzEnabled ); const channelzSessionInfo: ChannelzSessionInfo = { ref: channelzRef, - streamTracker: this.channelzEnabled - ? new ChannelzCallTracker() - : new ChannelzCallTrackerStub(), + streamTracker: new ChannelzCallTracker(), messagesSent: 0, messagesReceived: 0, keepAlivesSent: 0, @@ -1513,6 +1522,7 @@ export class Server { let connectionAgeTimer: NodeJS.Timeout | null = null; let connectionAgeGraceTimer: NodeJS.Timeout | null = null; let keeapliveTimeTimer: NodeJS.Timeout | null = null; + let keepaliveTimeoutTimer: NodeJS.Timeout | null = null; let sessionClosedByServer = false; const idleTimeoutObj = this.enableIdleTimeout(session); @@ -1533,7 +1543,7 @@ export class Server { session.goaway( http2.constants.NGHTTP2_NO_ERROR, ~(1 << 31), - Buffer.from('max_age') + kMaxAge ); } catch (e) { // The goaway can't be sent because the session is already closed @@ -1547,14 +1557,16 @@ export class Server { if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { connectionAgeGraceTimer = setTimeout(() => { session.destroy(); - }, this.maxConnectionAgeGraceMs).unref(); + }, this.maxConnectionAgeGraceMs); + connectionAgeGraceTimer.unref?.(); } - }, this.maxConnectionAgeMs + jitter).unref(); + }, this.maxConnectionAgeMs + jitter); + connectionAgeTimer.unref?.(); } if (this.keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS) { keeapliveTimeTimer = setInterval(() => { - const timeoutTImer = setTimeout(() => { + keepaliveTimeoutTimer = setTimeout(() => { sessionClosedByServer = true; this.channelzTrace.addTrace( 'CT_INFO', @@ -1562,11 +1574,15 @@ export class Server { ); session.close(); - }, this.keepaliveTimeoutMs).unref(); + }, this.keepaliveTimeoutMs); + keepaliveTimeoutTimer.unref?.(); + try { session.ping( (err: Error | null, duration: number, payload: Buffer) => { - clearTimeout(timeoutTImer); + if (keepaliveTimeoutTimer) { + clearTimeout(keepaliveTimeoutTimer); + } if (err) { sessionClosedByServer = true; @@ -1584,10 +1600,12 @@ export class Server { ); channelzSessionInfo.keepAlivesSent += 1; } catch (e) { + clearTimeout(keepaliveTimeoutTimer); // The ping can't be sent because the session is already closed session.destroy(); } - }, this.keepaliveTimeMs).unref(); + }, this.keepaliveTimeMs); + keeapliveTimeTimer.unref?.(); } session.on('close', () => { @@ -1610,7 +1628,10 @@ export class Server { } if (keeapliveTimeTimer) { - clearTimeout(keeapliveTimeTimer); + clearInterval(keeapliveTimeTimer); + if (keepaliveTimeoutTimer) { + clearTimeout(keepaliveTimeoutTimer); + } } if (idleTimeoutObj !== null) { @@ -1640,8 +1661,9 @@ export class Server { this.sessionIdleTimeout, this, session - ).unref(), + ), }; + idleTimeoutObj.timeout.unref?.(); this.sessionIdleTimeouts.set(session, idleTimeoutObj); const { socket } = session; @@ -1661,13 +1683,6 @@ export class Server { session: http2.ServerHttp2Session ) { const { socket } = session; - ctx.trace( - 'Session idle timeout checkpoint for ' + - socket?.remoteAddress + - ':' + - socket?.remotePort - ); - const sessionInfo = ctx.sessionIdleTimeouts.get(session); // if it is called while we have activeStreams - timer will not be rescheduled @@ -1679,6 +1694,15 @@ export class Server { sessionInfo.activeStreams === 0 && Date.now() - sessionInfo.lastIdle >= ctx.sessionIdleTimeout ) { + ctx.trace( + 'Session idle timeout triggered for ' + + socket?.remoteAddress + + ':' + + socket?.remotePort + + ' last idle at ' + + sessionInfo.lastIdle + ); + ctx.closeSession(session); } } @@ -1701,6 +1725,15 @@ export class Server { if (idleTimeoutObj.activeStreams === 0) { idleTimeoutObj.lastIdle = Date.now(); idleTimeoutObj.timeout.refresh(); + + this.trace( + 'Session onStreamClose' + + session.socket?.remoteAddress + + ':' + + session.socket?.remotePort + + ' at ' + + idleTimeoutObj.lastIdle + ); } } } diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 620488635..71d0f26b3 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -477,7 +477,8 @@ class Http2Transport implements Transport { ); this.keepaliveTimerId = setTimeout(() => { this.maybeSendPing(); - }, this.keepaliveTimeMs).unref?.(); + }, this.keepaliveTimeMs); + this.keepaliveTimerId.unref?.(); } /* Otherwise, there is already either a keepalive timer or a ping pending, * wait for those to resolve. */ diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index eaa701f18..fcdbb4500 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -140,6 +140,27 @@ export class TestClient { return this.client.getChannel().getConnectivityState(false); } + waitForClientState( + deadline: grpc.Deadline, + state: ConnectivityState, + callback: (error?: Error) => void + ) { + this.client + .getChannel() + .watchConnectivityState(this.getChannelState(), deadline, err => { + if (err) { + return callback(err); + } + + const currentState = this.getChannelState(); + if (currentState === state) { + callback(); + } else { + return this.waitForClientState(deadline, currentState, callback); + } + }); + } + close() { this.client.close(); } diff --git a/packages/grpc-js/test/test-idle-timer.ts b/packages/grpc-js/test/test-idle-timer.ts index 3fdeb1f64..a8f457e3f 100644 --- a/packages/grpc-js/test/test-idle-timer.ts +++ b/packages/grpc-js/test/test-idle-timer.ts @@ -128,3 +128,89 @@ describe('Channel idle timer', () => { }); }); }); + +describe('Server idle timer', () => { + let server: TestServer; + let client: TestClient | null = null; + before(() => { + server = new TestServer(false, { + 'grpc.max_connection_idle_ms': 500, // small for testing purposes + }); + return server.start(); + }); + afterEach(() => { + if (client) { + client.close(); + client = null; + } + }); + after(() => { + server.shutdown(); + }); + + it('Should go idle after the specified time after a request ends', function (done) { + this.timeout(5000); + client = TestClient.createFromServer(server); + client.sendRequest(error => { + assert.ifError(error); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); + client?.waitForClientState( + Date.now() + 600, + grpc.connectivityState.IDLE, + done + ); + }); + }); + + it('Should be able to make a request after going idle', function (done) { + this.timeout(5000); + client = TestClient.createFromServer(server); + client.sendRequest(error => { + assert.ifError(error); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); + + client!.waitForClientState( + Date.now() + 600, + grpc.connectivityState.IDLE, + err => { + if (err) return done(err); + + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.IDLE + ); + client!.sendRequest(error => { + assert.ifError(error); + done(); + }); + } + ); + }); + }); + + it('Should go idle after the specified time after waitForReady ends', function (done) { + this.timeout(5000); + client = TestClient.createFromServer(server); + const deadline = new Date(); + deadline.setSeconds(deadline.getSeconds() + 3); + client.waitForReady(deadline, error => { + assert.ifError(error); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); + + client!.waitForClientState( + Date.now() + 600, + grpc.connectivityState.IDLE, + done + ); + }); + }); +}); From 4a3fefa2b34c96d2f05eb263bbf800bb0dcd36ee Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 4 Mar 2024 09:33:41 -0800 Subject: [PATCH 673/694] grpc-js: pick_first: Don't automatically reconnect after connection drop --- packages/grpc-js/src/load-balancer-pick-first.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index 29bbfbf07..d625339f4 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -348,7 +348,6 @@ export class PickFirstLoadBalancer implements LoadBalancer { if (newState !== ConnectivityState.READY) { this.removeCurrentPick(); this.calculateAndReportNewState(); - this.requestReresolution(); } return; } From cf321a80b1918affaa41da9fe7e6424876ca5b48 Mon Sep 17 00:00:00 2001 From: AVVS Date: Mon, 4 Mar 2024 18:25:23 -0800 Subject: [PATCH 674/694] chore: use iterators for tracking map, const for default values --- packages/grpc-js/src/channelz.ts | 61 ++++++++++++++++++-------------- packages/grpc-js/src/server.ts | 4 +-- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 4eab762a8..697ea5d8a 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -133,6 +133,11 @@ interface TraceEvent { */ const TARGET_RETAINED_TRACES = 32; +/** + * Default number of sockets/servers/channels/subchannels to return + */ +const DEFAULT_MAX_RESULTS = 100; + export class ChannelzTraceStub { readonly events: TraceEvent[] = []; readonly creationTimestamp: Date = new Date(); @@ -198,19 +203,15 @@ export class ChannelzTrace { } } +type RefOrderedMap = OrderedMap< + number, + { ref: { id: number; kind: EntityTypes; name: string }; count: number } +>; + export class ChannelzChildrenTracker { - private channelChildren = new OrderedMap< - number, - { ref: ChannelRef; count: number } - >(); - private subchannelChildren = new OrderedMap< - number, - { ref: SubchannelRef; count: number } - >(); - private socketChildren = new OrderedMap< - number, - { ref: SocketRef; count: number } - >(); + private channelChildren: RefOrderedMap = new OrderedMap(); + private subchannelChildren: RefOrderedMap = new OrderedMap(); + private socketChildren: RefOrderedMap = new OrderedMap(); private trackerMap = { [EntityTypes.channel]: this.channelChildren, [EntityTypes.subchannel]: this.subchannelChildren, @@ -219,16 +220,19 @@ export class ChannelzChildrenTracker { refChild(child: ChannelRef | SubchannelRef | SocketRef) { const tracker = this.trackerMap[child.kind]; - const trackedChild = tracker.getElementByKey(child.id); - - if (trackedChild === undefined) { - tracker.setElement(child.id, { - // @ts-expect-error union issues - ref: child, - count: 1, - }); + const trackedChild = tracker.find(child.id); + + if (trackedChild.equals(tracker.end())) { + tracker.setElement( + child.id, + { + ref: child, + count: 1, + }, + trackedChild + ); } else { - trackedChild.count += 1; + trackedChild.pointer[1].count += 1; } } @@ -245,9 +249,9 @@ export class ChannelzChildrenTracker { getChildLists(): ChannelzChildren { return { - channels: this.channelChildren, - subchannels: this.subchannelChildren, - sockets: this.socketChildren, + channels: this.channelChildren as ChannelzChildren['channels'], + subchannels: this.subchannelChildren as ChannelzChildren['subchannels'], + sockets: this.socketChildren as ChannelzChildren['sockets'], }; } } @@ -585,7 +589,8 @@ function GetTopChannels( call: ServerUnaryCall, callback: sendUnaryData ): void { - const maxResults = parseInt(call.request.max_results, 10) || 100; + const maxResults = + parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS; const resultList: ChannelMessage[] = []; const startId = parseInt(call.request.start_channel_id, 10); const channelEntries = entityMaps[EntityTypes.channel]; @@ -649,7 +654,8 @@ function GetServers( call: ServerUnaryCall, callback: sendUnaryData ): void { - const maxResults = parseInt(call.request.max_results, 10) || 100; + const maxResults = + parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS; const startId = parseInt(call.request.start_server_id, 10); const serverEntries = entityMaps[EntityTypes.server]; const resultList: ServerMessage[] = []; @@ -820,7 +826,8 @@ function GetServerSockets( } const startId = parseInt(call.request.start_socket_id, 10); - const maxResults = parseInt(call.request.max_results, 10) || 100; + const maxResults = + parseInt(call.request.max_results, 10) || DEFAULT_MAX_RESULTS; const resolvedInfo = serverEntry.getInfo(); // If we wanted to include listener sockets in the result, this line would // instead say diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index b0fd5e7d3..feb511b41 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -368,7 +368,7 @@ export class Server { }; } - private getChannelzSessionInfoGetter( + private getChannelzSessionInfo( session: http2.ServerHttp2Session ): SocketInfo { const sessionInfo = this.sessions.get(session)!; @@ -1494,7 +1494,7 @@ export class Server { return (session: http2.ServerHttp2Session) => { const channelzRef = registerChannelzSocket( session.socket?.remoteAddress ?? 'unknown', - this.getChannelzSessionInfoGetter.bind(this, session), + this.getChannelzSessionInfo.bind(this, session), this.channelzEnabled ); From 07ee52acb04625f08092da59ad823c8651f41bee Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 5 Mar 2024 10:26:39 -0800 Subject: [PATCH 675/694] grpc-js: Rearrange some function calls to revert event order changes --- packages/grpc-js/src/server-call.ts | 4 ++-- packages/grpc-js/src/server-interceptors.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index 95393fba9..77fee7df2 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -200,11 +200,11 @@ export class ServerWritableStreamImpl } _final(callback: Function): void { + callback(null); this.call.sendStatus({ ...this.pendingStatus, metadata: this.pendingStatus.metadata ?? this.trailingMetadata, }); - callback(null); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -273,11 +273,11 @@ export class ServerDuplexStreamImpl } _final(callback: Function): void { + callback(null); this.call.sendStatus({ ...this.pendingStatus, metadata: this.pendingStatus.metadata ?? this.trailingMetadata, }); - callback(null); } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/grpc-js/src/server-interceptors.ts b/packages/grpc-js/src/server-interceptors.ts index 60c5c8865..9dfbbb2ff 100644 --- a/packages/grpc-js/src/server-interceptors.ts +++ b/packages/grpc-js/src/server-interceptors.ts @@ -842,7 +842,6 @@ export class BaseServerInterceptingCall if (this.checkCancelled()) { return; } - this.notifyOnCancel(); trace( 'Request to method ' + @@ -869,8 +868,11 @@ export class BaseServerInterceptingCall }; this.stream.sendTrailers(trailersToSend); + this.notifyOnCancel(); }); this.stream.end(); + } else { + this.notifyOnCancel(); } } else { if (this.callEventTracker && !this.streamEnded) { @@ -886,6 +888,7 @@ export class BaseServerInterceptingCall ...status.metadata?.toHttp2Headers(), }; this.stream.respond(trailersToSend, { endStream: true }); + this.notifyOnCancel(); } } startRead(): void { From 74ddb3bd6fb0d37b716bf6b4f0eb694b8db761ec Mon Sep 17 00:00:00 2001 From: AVVS Date: Tue, 5 Mar 2024 15:34:29 -0800 Subject: [PATCH 676/694] chore: address ts errors --- packages/grpc-js/src/channelz.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/grpc-js/src/channelz.ts b/packages/grpc-js/src/channelz.ts index 697ea5d8a..c207e567c 100644 --- a/packages/grpc-js/src/channelz.ts +++ b/packages/grpc-js/src/channelz.ts @@ -66,28 +66,26 @@ export type TraceSeverity = | 'CT_WARNING' | 'CT_ERROR'; -export interface ChannelRef { - kind: EntityTypes.channel; +interface Ref { + kind: EntityTypes; id: number; name: string; } -export interface SubchannelRef { +export interface ChannelRef extends Ref { + kind: EntityTypes.channel; +} + +export interface SubchannelRef extends Ref { kind: EntityTypes.subchannel; - id: number; - name: string; } -export interface ServerRef { +export interface ServerRef extends Ref { kind: EntityTypes.server; - id: number; - name: string; } -export interface SocketRef { +export interface SocketRef extends Ref { kind: EntityTypes.socket; - id: number; - name: string; } function channelRefToMessage(ref: ChannelRef): ChannelRefMessage { @@ -361,6 +359,8 @@ export const enum EntityTypes { socket = 'socket', } +type EntryOrderedMap = OrderedMap any }>; + const entityMaps = { [EntityTypes.channel]: new OrderedMap(), [EntityTypes.subchannel]: new OrderedMap(), @@ -404,6 +404,8 @@ const generateRegisterFn = (kind: R) => { return nextId++; } + const entityMap: EntryOrderedMap = entityMaps[kind]; + return ( name: string, getInfo: () => InfoByType, @@ -412,8 +414,7 @@ const generateRegisterFn = (kind: R) => { const id = getNextId(); const ref = { id, name, kind } as RefByType; if (channelzEnabled) { - // @ts-expect-error typing issues - entityMaps[kind].setElement(id, { ref, getInfo }); + entityMap.setElement(id, { ref, getInfo }); } return ref; }; From 4d235c339b5dc60efa58d398139c5f57d30ec023 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 7 Mar 2024 09:24:04 -0800 Subject: [PATCH 677/694] grpc-js: Bump to 1.10.2 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 1c7935473..839735f6f 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.1", + "version": "1.10.2", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From d0c20268878f01b3083b3543065b8ea6de50ac26 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 15 Mar 2024 09:23:08 -0700 Subject: [PATCH 678/694] Revert "grpc-js: pick_first: Don't automatically reconnect after connection drop" This reverts commit 4a3fefa2b34c96d2f05eb263bbf800bb0dcd36ee. --- packages/grpc-js/src/load-balancer-pick-first.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grpc-js/src/load-balancer-pick-first.ts b/packages/grpc-js/src/load-balancer-pick-first.ts index f5339aed8..f6c43b33d 100644 --- a/packages/grpc-js/src/load-balancer-pick-first.ts +++ b/packages/grpc-js/src/load-balancer-pick-first.ts @@ -348,6 +348,7 @@ export class PickFirstLoadBalancer implements LoadBalancer { if (newState !== ConnectivityState.READY) { this.removeCurrentPick(); this.calculateAndReportNewState(); + this.requestReresolution(); } return; } From a8c6c33daa560b1b249f9c6edf975fdbc2d547a0 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 15 Mar 2024 09:24:01 -0700 Subject: [PATCH 679/694] grpc-js: Bump version to 1.10.3 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 839735f6f..b2395b59a 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.2", + "version": "1.10.3", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", From d7d171776d686fbecdbf06e83ad278681c86cbda Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 15 Mar 2024 15:16:58 -0700 Subject: [PATCH 680/694] grpc-js: Add more info to deadline exceeded errors --- packages/grpc-js/src/call-interface.ts | 4 +++ packages/grpc-js/src/deadline.ts | 11 ++++++ packages/grpc-js/src/internal-channel.ts | 2 +- packages/grpc-js/src/load-balancing-call.ts | 28 ++++++++++++++-- packages/grpc-js/src/resolving-call.ts | 37 +++++++++++++++++++-- packages/grpc-js/src/retrying-call.ts | 24 +++++++++++-- packages/grpc-js/src/subchannel-address.ts | 8 +++-- packages/grpc-js/src/subchannel-call.ts | 4 +++ 8 files changed, 109 insertions(+), 9 deletions(-) diff --git a/packages/grpc-js/src/call-interface.ts b/packages/grpc-js/src/call-interface.ts index c0c63b957..c93c504f6 100644 --- a/packages/grpc-js/src/call-interface.ts +++ b/packages/grpc-js/src/call-interface.ts @@ -171,3 +171,7 @@ export interface Call { getCallNumber(): number; setCredentials(credentials: CallCredentials): void; } + +export interface DeadlineInfoProvider { + getDeadlineInfo(): string[]; +} diff --git a/packages/grpc-js/src/deadline.ts b/packages/grpc-js/src/deadline.ts index 8f8fe67b7..de05e381e 100644 --- a/packages/grpc-js/src/deadline.ts +++ b/packages/grpc-js/src/deadline.ts @@ -93,3 +93,14 @@ export function deadlineToString(deadline: Deadline): string { } } } + +/** + * Calculate the difference between two dates as a number of seconds and format + * it as a string. + * @param startDate + * @param endDate + * @returns + */ +export function formatDateDifference(startDate: Date, endDate: Date): string { + return ((endDate.getTime() - startDate.getTime()) / 1000).toFixed(3) + 's'; +} diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 823c935af..469ace557 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -684,7 +684,7 @@ export class InternalChannel { host: string, credentials: CallCredentials, deadline: Deadline - ): Call { + ): LoadBalancingCall | RetryingCall { // Create a RetryingCall if retries are enabled if (this.options['grpc.enable_retries'] === 0) { return this.createLoadBalancingCall( diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 25a36553a..9940500bc 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -18,6 +18,7 @@ import { CallCredentials } from './call-credentials'; import { Call, + DeadlineInfoProvider, InterceptingListener, MessageContext, StatusObject, @@ -25,7 +26,7 @@ import { import { SubchannelCall } from './subchannel-call'; import { ConnectivityState } from './connectivity-state'; import { LogVerbosity, Status } from './constants'; -import { Deadline, getDeadlineTimeoutString } from './deadline'; +import { Deadline, formatDateDifference, getDeadlineTimeoutString } from './deadline'; import { InternalChannel } from './internal-channel'; import { Metadata } from './metadata'; import { PickResultType } from './picker'; @@ -48,7 +49,7 @@ export interface LoadBalancingCallInterceptingListener onReceiveStatus(status: StatusObjectWithProgress): void; } -export class LoadBalancingCall implements Call { +export class LoadBalancingCall implements Call, DeadlineInfoProvider { private child: SubchannelCall | null = null; private readPending = false; private pendingMessage: { context: MessageContext; message: Buffer } | null = @@ -59,6 +60,8 @@ export class LoadBalancingCall implements Call { private metadata: Metadata | null = null; private listener: InterceptingListener | null = null; private onCallEnded: ((statusCode: Status) => void) | null = null; + private startTime: Date; + private childStartTime: Date | null = null; constructor( private readonly channel: InternalChannel, private readonly callConfig: CallConfig, @@ -80,6 +83,26 @@ export class LoadBalancingCall implements Call { /* Currently, call credentials are only allowed on HTTPS connections, so we * can assume that the scheme is "https" */ this.serviceUrl = `https://${hostname}/${serviceName}`; + this.startTime = new Date(); + } + getDeadlineInfo(): string[] { + const deadlineInfo: string[] = []; + if (this.childStartTime) { + if (this.childStartTime > this.startTime) { + if (this.metadata?.getOptions().waitForReady) { + deadlineInfo.push('wait_for_ready'); + } + deadlineInfo.push(`LB pick: ${formatDateDifference(this.startTime, this.childStartTime)}`); + } + deadlineInfo.push(...this.child!.getDeadlineInfo()); + return deadlineInfo; + } else { + if (this.metadata?.getOptions().waitForReady) { + deadlineInfo.push('wait_for_ready'); + } + deadlineInfo.push('Waiting for LB pick'); + } + return deadlineInfo; } private trace(text: string): void { @@ -209,6 +232,7 @@ export class LoadBalancingCall implements Call { } }, }); + this.childStartTime = new Date(); } catch (error) { this.trace( 'Failed to start call on picked subchannel ' + diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index 723533dba..2c81e7883 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -19,6 +19,7 @@ import { CallCredentials } from './call-credentials'; import { Call, CallStreamOptions, + DeadlineInfoProvider, InterceptingListener, MessageContext, StatusObject, @@ -27,6 +28,7 @@ import { LogVerbosity, Propagate, Status } from './constants'; import { Deadline, deadlineToString, + formatDateDifference, getRelativeTimeout, minDeadline, } from './deadline'; @@ -39,7 +41,7 @@ import { restrictControlPlaneStatusCode } from './control-plane-status'; const TRACER_NAME = 'resolving_call'; export class ResolvingCall implements Call { - private child: Call | null = null; + private child: (Call & DeadlineInfoProvider) | null = null; private readPending = false; private pendingMessage: { context: MessageContext; message: Buffer } | null = null; @@ -56,6 +58,10 @@ export class ResolvingCall implements Call { private deadlineTimer: NodeJS.Timeout = setTimeout(() => {}, 0); private filterStack: FilterStack | null = null; + private deadlineStartTime: Date | null = null; + private configReceivedTime: Date | null = null; + private childStartTime: Date | null = null; + constructor( private readonly channel: InternalChannel, private readonly method: string, @@ -97,12 +103,37 @@ export class ResolvingCall implements Call { private runDeadlineTimer() { clearTimeout(this.deadlineTimer); + this.deadlineStartTime = new Date(); this.trace('Deadline: ' + deadlineToString(this.deadline)); const timeout = getRelativeTimeout(this.deadline); if (timeout !== Infinity) { this.trace('Deadline will be reached in ' + timeout + 'ms'); const handleDeadline = () => { - this.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); + if (!this.deadlineStartTime) { + this.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); + return; + } + const deadlineInfo: string[] = []; + const deadlineEndTime = new Date(); + deadlineInfo.push(`Deadline exceeded after ${formatDateDifference(this.deadlineStartTime, deadlineEndTime)}`); + if (this.configReceivedTime) { + if (this.configReceivedTime > this.deadlineStartTime) { + deadlineInfo.push(`name resolution: ${formatDateDifference(this.deadlineStartTime, this.configReceivedTime)}`); + } + if (this.childStartTime) { + if (this.childStartTime > this.configReceivedTime) { + deadlineInfo.push(`metadata filters: ${formatDateDifference(this.configReceivedTime, this.childStartTime)}`); + } + } else { + deadlineInfo.push('waiting for metadata filters'); + } + } else { + deadlineInfo.push('waiting for name resolution'); + } + if (this.child) { + deadlineInfo.push(...this.child.getDeadlineInfo()); + } + this.cancelWithStatus(Status.DEADLINE_EXCEEDED, deadlineInfo.join(',')); }; if (timeout <= 0) { process.nextTick(handleDeadline); @@ -176,6 +207,7 @@ export class ResolvingCall implements Call { return; } // configResult.type === 'SUCCESS' + this.configReceivedTime = new Date(); const config = configResult.config; if (config.status !== Status.OK) { const { code, details } = restrictControlPlaneStatusCode( @@ -215,6 +247,7 @@ export class ResolvingCall implements Call { this.deadline ); this.trace('Created child [' + this.child.getCallNumber() + ']'); + this.childStartTime = new Date(); this.child.start(filteredMetadata, { onReceiveMetadata: metadata => { this.trace('Received metadata'); diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index e6e1cbb44..14969916e 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -17,12 +17,13 @@ import { CallCredentials } from './call-credentials'; import { LogVerbosity, Status } from './constants'; -import { Deadline } from './deadline'; +import { Deadline, formatDateDifference } from './deadline'; import { Metadata } from './metadata'; import { CallConfig } from './resolver'; import * as logging from './logging'; import { Call, + DeadlineInfoProvider, InterceptingListener, MessageContext, StatusObject, @@ -121,6 +122,7 @@ interface UnderlyingCall { state: UnderlyingCallState; call: LoadBalancingCall; nextMessageToSend: number; + startTime: Date; } /** @@ -170,7 +172,7 @@ interface WriteBufferEntry { const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts'; -export class RetryingCall implements Call { +export class RetryingCall implements Call, DeadlineInfoProvider { private state: RetryingCallState; private listener: InterceptingListener | null = null; private initialMetadata: Metadata | null = null; @@ -198,6 +200,7 @@ export class RetryingCall implements Call { private committedCallIndex: number | null = null; private initialRetryBackoffSec = 0; private nextRetryBackoffSec = 0; + private startTime: Date; constructor( private readonly channel: InternalChannel, private readonly callConfig: CallConfig, @@ -223,6 +226,22 @@ export class RetryingCall implements Call { } else { this.state = 'TRANSPARENT_ONLY'; } + this.startTime = new Date(); + } + getDeadlineInfo(): string[] { + if (this.underlyingCalls.length === 0) { + return []; + } + const deadlineInfo: string[] = []; + const latestCall = this.underlyingCalls[this.underlyingCalls.length - 1]; + if (this.underlyingCalls.length > 1) { + deadlineInfo.push(`previous attempts: ${this.underlyingCalls.length - 1}`); + } + if (latestCall.startTime > this.startTime) { + deadlineInfo.push(`time to current attempt start: ${formatDateDifference(this.startTime, latestCall.startTime)}`); + } + deadlineInfo.push(...latestCall.call.getDeadlineInfo()); + return deadlineInfo; } getCallNumber(): number { return this.callNumber; @@ -628,6 +647,7 @@ export class RetryingCall implements Call { state: 'ACTIVE', call: child, nextMessageToSend: 0, + startTime: new Date() }); const previousAttempts = this.attempts - 1; const initialMetadata = this.initialMetadata!.clone(); diff --git a/packages/grpc-js/src/subchannel-address.ts b/packages/grpc-js/src/subchannel-address.ts index 70a7962f7..7e4f3e475 100644 --- a/packages/grpc-js/src/subchannel-address.ts +++ b/packages/grpc-js/src/subchannel-address.ts @@ -15,7 +15,7 @@ * */ -import { isIP } from 'net'; +import { isIP, isIPv6 } from 'net'; export interface TcpSubchannelAddress { port: number; @@ -63,7 +63,11 @@ export function subchannelAddressEqual( export function subchannelAddressToString(address: SubchannelAddress): string { if (isTcpSubchannelAddress(address)) { - return address.host + ':' + address.port; + if (isIPv6(address.host)) { + return '[' + address.host + ']:' + address.port; + } else { + return address.host + ':' + address.port; + } } else { return address.path; } diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 3b9b6152f..33cf544d8 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -70,6 +70,7 @@ export interface SubchannelCall { startRead(): void; halfClose(): void; getCallNumber(): number; + getDeadlineInfo(): string[]; } export interface StatusObjectWithRstCode extends StatusObject { @@ -288,6 +289,9 @@ export class Http2SubchannelCall implements SubchannelCall { this.callEventTracker.onStreamEnd(false); }); } + getDeadlineInfo(): string[] { + return [`remote_addr=${this.getPeer()}`]; + } public onDisconnect() { this.endCall({ From 14f1d02c9af266104e896c70fccc144b724461f6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 20 Mar 2024 15:44:18 -0700 Subject: [PATCH 681/694] grpc-js: Avoid sending redundant RST_STREAMs from the client --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel-call.ts | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index b2395b59a..a8a9f2d06 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.3", + "version": "1.10.4", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 3b9b6152f..fd1cfb454 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -105,6 +105,8 @@ export class Http2SubchannelCall implements SubchannelCall { private internalError: SystemError | null = null; + private serverEndedCall = false; + constructor( private readonly http2Stream: http2.ClientHttp2Stream, private readonly callEventTracker: CallEventTracker, @@ -182,6 +184,7 @@ export class Http2SubchannelCall implements SubchannelCall { this.maybeOutputStatus(); }); http2Stream.on('close', () => { + this.serverEndedCall = true; /* Use process.next tick to ensure that this code happens after any * "error" event that may be emitted at about the same time, so that * we can bubble up the error message from that event. */ @@ -400,6 +403,7 @@ export class Http2SubchannelCall implements SubchannelCall { } private handleTrailers(headers: http2.IncomingHttpHeaders) { + this.serverEndedCall = true; this.callEventTracker.onStreamEnd(true); let headersString = ''; for (const header of Object.keys(headers)) { @@ -445,7 +449,15 @@ export class Http2SubchannelCall implements SubchannelCall { private destroyHttp2Stream() { // The http2 stream could already have been destroyed if cancelWithStatus // is called in response to an internal http2 error. - if (!this.http2Stream.destroyed) { + if (this.http2Stream.destroyed) { + return; + } + /* If the server ended the call, sending an RST_STREAM is redundant, so we + * just half close on the client side instead to finish closing the stream. + */ + if (this.serverEndedCall) { + this.http2Stream.end(); + } else { /* If the call has ended with an OK status, communicate that when closing * the stream, partly to avoid a situation in which we detect an error * RST_STREAM as a result after we have the status */ From f4330f72c940645f910a7a78b3231ceab9cc2619 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 21 Mar 2024 09:49:58 -0700 Subject: [PATCH 682/694] Use call start times in some trace logs --- packages/grpc-js/src/load-balancing-call.ts | 3 ++- packages/grpc-js/src/retrying-call.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/src/load-balancing-call.ts b/packages/grpc-js/src/load-balancing-call.ts index 9940500bc..764769753 100644 --- a/packages/grpc-js/src/load-balancing-call.ts +++ b/packages/grpc-js/src/load-balancing-call.ts @@ -121,7 +121,8 @@ export class LoadBalancingCall implements Call, DeadlineInfoProvider { status.code + ' details="' + status.details + - '"' + '" start time=' + + this.startTime.toISOString() ); const finalStatus = { ...status, progress }; this.listener?.onReceiveStatus(finalStatus); diff --git a/packages/grpc-js/src/retrying-call.ts b/packages/grpc-js/src/retrying-call.ts index 14969916e..1c5ffaa4f 100644 --- a/packages/grpc-js/src/retrying-call.ts +++ b/packages/grpc-js/src/retrying-call.ts @@ -261,7 +261,8 @@ export class RetryingCall implements Call, DeadlineInfoProvider { statusObject.code + ' details="' + statusObject.details + - '"' + '" start time=' + + this.startTime.toISOString() ); this.bufferTracker.freeAll(this.callNumber); this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length; From 9948aea5a536c9796ecf54814cab81aeb9c9ff3c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 21 Mar 2024 14:58:37 -0700 Subject: [PATCH 683/694] grpc-js: Ensure server interceptors work with builder utility classes --- packages/grpc-js/src/server-interceptors.ts | 20 +++++++++-- .../grpc-js/test/test-server-interceptors.ts | 35 +++++++++---------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/grpc-js/src/server-interceptors.ts b/packages/grpc-js/src/server-interceptors.ts index 9dfbbb2ff..c9cfe416b 100644 --- a/packages/grpc-js/src/server-interceptors.ts +++ b/packages/grpc-js/src/server-interceptors.ts @@ -345,7 +345,12 @@ export class ServerInterceptingCall implements ServerInterceptingCallInterface { private nextCall: ServerInterceptingCallInterface, responder?: Responder ) { - this.responder = { ...defaultResponder, ...responder }; + this.responder = { + start: responder?.start ?? defaultResponder.start, + sendMetadata: responder?.sendMetadata ?? defaultResponder.sendMetadata, + sendMessage: responder?.sendMessage ?? defaultResponder.sendMessage, + sendStatus: responder?.sendStatus ?? defaultResponder.sendStatus, + }; } private processPendingMessage() { @@ -369,8 +374,17 @@ export class ServerInterceptingCall implements ServerInterceptingCallInterface { start(listener: InterceptingServerListener): void { this.responder.start(interceptedListener => { const fullInterceptedListener: FullServerListener = { - ...defaultServerListener, - ...interceptedListener, + onReceiveMetadata: + interceptedListener?.onReceiveMetadata ?? + defaultServerListener.onReceiveMetadata, + onReceiveMessage: + interceptedListener?.onReceiveMessage ?? + defaultServerListener.onReceiveMessage, + onReceiveHalfClose: + interceptedListener?.onReceiveHalfClose ?? + defaultServerListener.onReceiveHalfClose, + onCancel: + interceptedListener?.onCancel ?? defaultServerListener.onCancel, }; const finalInterceptingListener = new InterceptingServerListenerImpl( fullInterceptedListener, diff --git a/packages/grpc-js/test/test-server-interceptors.ts b/packages/grpc-js/test/test-server-interceptors.ts index 5e93d32d2..e94169721 100644 --- a/packages/grpc-js/test/test-server-interceptors.ts +++ b/packages/grpc-js/test/test-server-interceptors.ts @@ -30,25 +30,22 @@ const testAuthInterceptor: grpc.ServerInterceptor = ( methodDescriptor, call ) => { - return new grpc.ServerInterceptingCall(call, { - start: next => { - const authListener: grpc.ServerListener = { - onReceiveMetadata: (metadata, mdNext) => { - if ( - metadata.get(AUTH_HEADER_KEY)?.[0] !== AUTH_HEADER_ALLOWED_VALUE - ) { - call.sendStatus({ - code: grpc.status.UNAUTHENTICATED, - details: 'Auth metadata not correct', - }); - } else { - mdNext(metadata); - } - }, - }; - next(authListener); - }, - }); + const authListener = (new grpc.ServerListenerBuilder()) + .withOnReceiveMetadata((metadata, mdNext) => { + if ( + metadata.get(AUTH_HEADER_KEY)?.[0] !== AUTH_HEADER_ALLOWED_VALUE + ) { + call.sendStatus({ + code: grpc.status.UNAUTHENTICATED, + details: 'Auth metadata not correct', + }); + } else { + mdNext(metadata); + } + }).build(); + const responder = (new grpc.ResponderBuilder()) + .withStart(next => next(authListener)).build(); + return new grpc.ServerInterceptingCall(call, responder); }; let eventCounts = { From e1f831a57bff9e5bce48aa80edb5ae527c397ca6 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 1 Apr 2024 09:54:06 -0700 Subject: [PATCH 684/694] grpc-js: Call custom checkServerIdentity when target name override is set --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/transport.ts | 8 +++-- .../grpc-js/test/test-channel-credentials.ts | 30 +++++++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index a8a9f2d06..09ae217d8 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.4", + "version": "1.10.5", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 71d0f26b3..66a5d4556 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -694,11 +694,13 @@ export class Http2SubchannelConnector implements SubchannelConnector { if (options['grpc.ssl_target_name_override']) { const sslTargetNameOverride = options['grpc.ssl_target_name_override']!; + const originalCheckServerIdentity = + connectionOptions.checkServerIdentity ?? checkServerIdentity; connectionOptions.checkServerIdentity = ( host: string, cert: PeerCertificate ): Error | undefined => { - return checkServerIdentity(sslTargetNameOverride, cert); + return originalCheckServerIdentity(sslTargetNameOverride, cert); }; connectionOptions.servername = sslTargetNameOverride; } else { @@ -804,11 +806,13 @@ export class Http2SubchannelConnector implements SubchannelConnector { // This option is used for testing only. if (options['grpc.ssl_target_name_override']) { const sslTargetNameOverride = options['grpc.ssl_target_name_override']!; + const originalCheckServerIdentity = + connectionOptions.checkServerIdentity ?? checkServerIdentity; connectionOptions.checkServerIdentity = ( host: string, cert: PeerCertificate ): Error | undefined => { - return checkServerIdentity(sslTargetNameOverride, cert); + return originalCheckServerIdentity(sslTargetNameOverride, cert); }; connectionOptions.servername = sslTargetNameOverride; } else { diff --git a/packages/grpc-js/test/test-channel-credentials.ts b/packages/grpc-js/test/test-channel-credentials.ts index b05b0d048..b5c011581 100644 --- a/packages/grpc-js/test/test-channel-credentials.ts +++ b/packages/grpc-js/test/test-channel-credentials.ts @@ -150,8 +150,12 @@ describe('ChannelCredentials Implementation', () => { describe('ChannelCredentials usage', () => { let client: ServiceClient; let server: grpc.Server; + let portNum: number; + let caCert: Buffer; + const hostnameOverride = 'foo.test.google.fr'; before(async () => { const { ca, key, cert } = await pFixtures; + caCert = ca; const serverCreds = grpc.ServerCredentials.createSsl(null, [ { private_key: key, cert_chain: cert }, ]); @@ -178,9 +182,10 @@ describe('ChannelCredentials usage', () => { reject(err); return; } + portNum = port; client = new echoService(`localhost:${port}`, combinedCreds, { - 'grpc.ssl_target_name_override': 'foo.test.google.fr', - 'grpc.default_authority': 'foo.test.google.fr', + 'grpc.ssl_target_name_override': hostnameOverride, + 'grpc.default_authority': hostnameOverride, }); server.start(); resolve(); @@ -207,4 +212,25 @@ describe('ChannelCredentials usage', () => { ); assert2.afterMustCallsSatisfied(done); }); + + it('Should call the checkServerIdentity callback', done => { + const channelCreds = ChannelCredentials.createSsl(caCert, null, null, { + checkServerIdentity: assert2.mustCall((hostname, cert) => { + assert.strictEqual(hostname, hostnameOverride); + return undefined; + }), + }); + const client = new echoService(`localhost:${portNum}`, channelCreds, { + 'grpc.ssl_target_name_override': hostnameOverride, + 'grpc.default_authority': hostnameOverride, + }); + client.echo( + { value: 'test value', value2: 3 }, + assert2.mustCall((error: ServiceError, response: any) => { + assert.ifError(error); + assert.deepStrictEqual(response, { value: 'test value', value2: 3 }); + }) + ); + assert2.afterMustCallsSatisfied(done); + }); }); From 213230c73b9300ae9fff6b8f2e66f9913439dde7 Mon Sep 17 00:00:00 2001 From: David Fiala Date: Wed, 27 Mar 2024 16:39:45 -0700 Subject: [PATCH 685/694] Resolve exception when Error.stackTraceLimit is undefined Some applications may explicitly set Error.stackTraceLimit = undefined. In this case it is not safe to assume new Error().stack is available. --- packages/grpc-js/src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/src/client.ts b/packages/grpc-js/src/client.ts index e122f6cf4..995d5b328 100644 --- a/packages/grpc-js/src/client.ts +++ b/packages/grpc-js/src/client.ts @@ -110,7 +110,7 @@ export type ClientOptions = Partial & { }; function getErrorStackString(error: Error): string { - return error.stack!.split('\n').slice(1).join('\n'); + return error.stack?.split('\n').slice(1).join('\n') || 'no stack trace available'; } /** From 0d9a8c1dcf0f46eda09540518c50d28519201d58 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 3 Apr 2024 09:40:22 -0700 Subject: [PATCH 686/694] grpc-js: Fix check for whether to send a trailers-only response --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/server-interceptors.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 09ae217d8..3b8ccf2c1 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.5", + "version": "1.10.6", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/server-interceptors.ts b/packages/grpc-js/src/server-interceptors.ts index c9cfe416b..b62d55108 100644 --- a/packages/grpc-js/src/server-interceptors.ts +++ b/packages/grpc-js/src/server-interceptors.ts @@ -866,7 +866,7 @@ export class BaseServerInterceptingCall status.details ); - if (this.stream.headersSent) { + if (this.metadataSent) { if (!this.wantTrailers) { this.wantTrailers = true; this.stream.once('wantTrailers', () => { From 2af21a55f366416030ec2e58b8b7c16f033a9e8e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 8 Apr 2024 17:47:26 -0700 Subject: [PATCH 687/694] Merge pull request #2712 from sergiitk/psm-interop-pkg-dev PSM Interop: Migrate to Artifact Registry --- packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 7 ++++--- packages/grpc-js-xds/scripts/xds_k8s_url_map.sh | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh index 504c3ff37..c900a4ea5 100755 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh @@ -19,8 +19,9 @@ set -eo pipefail readonly GITHUB_REPOSITORY_NAME="grpc-node" readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" ## xDS test client Docker images -readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server:558b5b0bfac8e21755c223063274a779b3898afe" -readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" +readonly DOCKER_REGISTRY="us-docker.pkg.dev" +readonly SERVER_IMAGE_NAME="us-docker.pkg.dev/grpc-testing/psm-interop/java-server:canonical" +readonly CLIENT_IMAGE_NAME="us-docker.pkg.dev/grpc-testing/psm-interop/node-client" readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" readonly LANGUAGE_NAME="Node" @@ -46,7 +47,7 @@ build_test_app_docker_images() { -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . - gcloud -q auth configure-docker + gcloud -q auth configure-docker "${DOCKER_REGISTRY}" docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" if is_version_branch "${TESTING_VERSION}"; then tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh index 9344d054b..d6e2c7ed4 100644 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh @@ -19,7 +19,8 @@ set -eo pipefail readonly GITHUB_REPOSITORY_NAME="grpc-node" readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" ## xDS test client Docker images -readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/node-client" +readonly DOCKER_REGISTRY="us-docker.pkg.dev" +readonly CLIENT_IMAGE_NAME="us-docker.pkg.dev/grpc-testing/psm-interop/node-client" readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" readonly LANGUAGE_NAME="Node" @@ -45,7 +46,7 @@ build_test_app_docker_images() { -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ . - gcloud -q auth configure-docker + gcloud -q auth configure-docker "${DOCKER_REGISTRY}" docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" if is_version_branch "${TESTING_VERSION}"; then tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" From 8754ccb7dbefa0345a5805092a275f7e6c4cf0d2 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 11 Apr 2024 10:56:18 -0700 Subject: [PATCH 688/694] grpc-js: Improve reporting of HTTP error codes --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/subchannel-call.ts | 121 ++++++++++++++---------- 2 files changed, 73 insertions(+), 50 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 3b8ccf2c1..c9999465c 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.6", + "version": "1.10.7", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index d54a6bcbf..0ce7d72cb 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -82,6 +82,39 @@ export interface SubchannelCallInterceptingListener onReceiveStatus(status: StatusObjectWithRstCode): void; } +function mapHttpStatusCode(code: number): StatusObject { + const details = `Received HTTP status code ${code}`; + let mappedStatusCode: number; + switch (code) { + // TODO(murgatroid99): handle 100 and 101 + case 400: + mappedStatusCode = Status.INTERNAL; + break; + case 401: + mappedStatusCode = Status.UNAUTHENTICATED; + break; + case 403: + mappedStatusCode = Status.PERMISSION_DENIED; + break; + case 404: + mappedStatusCode = Status.UNIMPLEMENTED; + break; + case 429: + case 502: + case 503: + case 504: + mappedStatusCode = Status.UNAVAILABLE; + break; + default: + mappedStatusCode = Status.UNKNOWN; + } + return { + code: mappedStatusCode, + details: details, + metadata: new Metadata() + }; +} + export class Http2SubchannelCall implements SubchannelCall { private decoder = new StreamDecoder(); @@ -98,8 +131,7 @@ export class Http2SubchannelCall implements SubchannelCall { private unpushedReadMessages: Buffer[] = []; - // Status code mapped from :status. To be used if grpc-status is not received - private mappedStatusCode: Status = Status.UNKNOWN; + private httpStatusCode: number | undefined; // This is populated (non-null) if and only if the call has ended private finalStatus: StatusObject | null = null; @@ -121,29 +153,7 @@ export class Http2SubchannelCall implements SubchannelCall { headersString += '\t\t' + header + ': ' + headers[header] + '\n'; } this.trace('Received server headers:\n' + headersString); - switch (headers[':status']) { - // TODO(murgatroid99): handle 100 and 101 - case 400: - this.mappedStatusCode = Status.INTERNAL; - break; - case 401: - this.mappedStatusCode = Status.UNAUTHENTICATED; - break; - case 403: - this.mappedStatusCode = Status.PERMISSION_DENIED; - break; - case 404: - this.mappedStatusCode = Status.UNIMPLEMENTED; - break; - case 429: - case 502: - case 503: - case 504: - this.mappedStatusCode = Status.UNAVAILABLE; - break; - default: - this.mappedStatusCode = Status.UNKNOWN; - } + this.httpStatusCode = headers[':status']; if (flags & http2.constants.NGHTTP2_FLAG_END_STREAM) { this.handleTrailers(headers); @@ -208,8 +218,14 @@ export class Http2SubchannelCall implements SubchannelCall { if (this.finalStatus !== null) { return; } - code = Status.INTERNAL; - details = `Received RST_STREAM with code ${http2Stream.rstCode}`; + if (this.httpStatusCode && this.httpStatusCode !== 200) { + const mappedStatus = mapHttpStatusCode(this.httpStatusCode); + code = mappedStatus.code; + details = mappedStatus.details; + } else { + code = Status.INTERNAL; + details = `Received RST_STREAM with code ${http2Stream.rstCode} (Call ended without gRPC status)`; + } break; case http2.constants.NGHTTP2_REFUSED_STREAM: code = Status.UNAVAILABLE; @@ -421,31 +437,38 @@ export class Http2SubchannelCall implements SubchannelCall { metadata = new Metadata(); } const metadataMap = metadata.getMap(); - let code: Status = this.mappedStatusCode; - if ( - code === Status.UNKNOWN && - typeof metadataMap['grpc-status'] === 'string' - ) { - const receivedStatus = Number(metadataMap['grpc-status']); - if (receivedStatus in Status) { - code = receivedStatus; - this.trace('received status code ' + receivedStatus + ' from server'); - } + let status: StatusObject; + if (typeof metadataMap['grpc-status'] === 'string') { + const receivedStatus: Status = Number(metadataMap['grpc-status']); + this.trace('received status code ' + receivedStatus + ' from server'); metadata.remove('grpc-status'); - } - let details = ''; - if (typeof metadataMap['grpc-message'] === 'string') { - try { - details = decodeURI(metadataMap['grpc-message']); - } catch (e) { - details = metadataMap['grpc-message']; + let details = ''; + if (typeof metadataMap['grpc-message'] === 'string') { + try { + details = decodeURI(metadataMap['grpc-message']); + } catch (e) { + details = metadataMap['grpc-message']; + } + metadata.remove('grpc-message'); + this.trace( + 'received status details string "' + details + '" from server' + ); } - metadata.remove('grpc-message'); - this.trace( - 'received status details string "' + details + '" from server' - ); + status = { + code: receivedStatus, + details: details, + metadata: metadata + }; + } else if (this.httpStatusCode) { + status = mapHttpStatusCode(this.httpStatusCode); + status.metadata = metadata; + } else { + status = { + code: Status.UNKNOWN, + details: 'No status information received', + metadata: metadata + }; } - const status: StatusObject = { code, details, metadata }; // This is a no-op if the call was already ended when handling headers. this.endCall(status); } From e4f2ecd0537191c3d4c6f27b2faa38daeda7059c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 30 Apr 2024 15:49:20 -0700 Subject: [PATCH 689/694] grpc-js(-xds): Pick up proto-loader update --- .../grpc/testing/ClientConfigureRequest.ts | 30 +- .../generated/grpc/testing/GrpclbRouteType.ts | 45 +- .../LoadBalancerAccumulatedStatsResponse.ts | 8 +- .../grpc/testing/LoadBalancerStatsResponse.ts | 2 +- .../grpc/testing/LoadBalancerStatsService.ts | 38 +- .../interop/generated/grpc/testing/Payload.ts | 6 +- .../generated/grpc/testing/PayloadType.ts | 21 +- .../grpc/testing/ReconnectService.ts | 38 +- .../grpc/testing/ResponseParameters.ts | 4 +- .../generated/grpc/testing/SimpleRequest.ts | 22 +- .../generated/grpc/testing/SimpleResponse.ts | 10 +- .../grpc/testing/StreamingInputCallRequest.ts | 8 +- .../testing/StreamingOutputCallRequest.ts | 14 +- .../testing/StreamingOutputCallResponse.ts | 4 +- .../generated/grpc/testing/TestService.ts | 92 +-- .../grpc/testing/UnimplementedService.ts | 21 +- .../XdsUpdateClientConfigureService.ts | 21 +- .../grpc/testing/XdsUpdateHealthService.ts | 38 +- .../grpc-js-xds/interop/generated/test.ts | 26 +- .../grpc-js-xds/interop/xds-interop-client.ts | 2 +- packages/grpc-js-xds/package.json | 6 +- packages/grpc-js-xds/src/generated/cluster.ts | 9 + .../envoy/admin/v3/BootstrapConfigDump.ts | 32 - .../envoy/admin/v3/ClientResourceStatus.ts | 57 +- .../envoy/admin/v3/ClustersConfigDump.ts | 6 +- .../generated/envoy/admin/v3/ConfigDump.ts | 65 -- .../envoy/admin/v3/EcdsConfigDump.ts | 6 +- .../envoy/admin/v3/EndpointsConfigDump.ts | 6 +- .../envoy/admin/v3/ListenersConfigDump.ts | 6 +- .../envoy/admin/v3/RoutesConfigDump.ts | 6 +- .../envoy/admin/v3/ScopedRoutesConfigDump.ts | 6 +- .../envoy/admin/v3/SecretsConfigDump.ts | 162 ----- .../config/accesslog/v3/ComparisonFilter.ts | 33 +- .../config/accesslog/v3/GrpcStatusFilter.ts | 80 ++- .../config/accesslog/v3/LogTypeFilter.ts | 6 +- .../envoy/config/bootstrap/v3/Admin.ts | 75 -- .../envoy/config/bootstrap/v3/Bootstrap.ts | 642 ------------------ .../config/bootstrap/v3/ClusterManager.ts | 99 --- .../config/bootstrap/v3/CustomInlineHeader.ts | 85 --- .../envoy/config/bootstrap/v3/FatalAction.ts | 39 -- .../config/bootstrap/v3/LayeredRuntime.ts | 25 - .../envoy/config/bootstrap/v3/Runtime.ts | 77 --- .../envoy/config/bootstrap/v3/RuntimeLayer.ts | 142 ---- .../envoy/config/bootstrap/v3/Watchdog.ts | 141 ---- .../envoy/config/bootstrap/v3/Watchdogs.ts | 35 - .../config/cluster/v3/CircuitBreakers.ts | 6 +- .../envoy/config/cluster/v3/Cluster.ts | 469 +++++++++++-- .../config/cluster/v3/UpstreamBindConfig.ts | 25 - .../envoy/config/core/v3/ApiConfigSource.ts | 76 ++- .../envoy/config/core/v3/ApiVersion.ts | 41 +- .../envoy/config/core/v3/BindConfig.ts | 2 + .../envoy/config/core/v3/ConfigSource.ts | 8 +- .../envoy/config/core/v3/Extension.ts | 2 + .../envoy/config/core/v3/HeaderValueOption.ts | 46 +- .../envoy/config/core/v3/HealthCheck.ts | 15 +- .../envoy/config/core/v3/HealthStatus.ts | 61 +- .../envoy/config/core/v3/HealthStatusSet.ts | 6 +- .../config/core/v3/Http2ProtocolOptions.ts | 2 + .../config/core/v3/HttpProtocolOptions.ts | 51 +- .../generated/envoy/config/core/v3/Node.ts | 2 + .../config/core/v3/ProxyProtocolConfig.ts | 26 +- .../core/v3/ProxyProtocolPassThroughTLVs.ts | 26 +- .../envoy/config/core/v3/RequestMethod.ts | 54 +- .../envoy/config/core/v3/RoutingPriority.ts | 34 +- .../envoy/config/core/v3/SelfConfigSource.ts | 6 +- .../envoy/config/core/v3/SocketAddress.ts | 20 +- .../envoy/config/core/v3/SocketOption.ts | 33 +- .../core/v3/SubstitutionFormatString.ts | 2 + .../envoy/config/core/v3/TrafficDirection.ts | 35 +- .../envoy/config/core/v3/UdpSocketConfig.ts | 1 - .../envoy/config/endpoint/v3/LbEndpoint.ts | 6 +- .../envoy/config/listener/v3/FilterChain.ts | 2 + .../config/listener/v3/FilterChainMatch.ts | 33 +- .../envoy/config/listener/v3/Listener.ts | 39 +- .../envoy/config/metrics/v3/DogStatsdSink.ts | 65 -- .../metrics/v3/HistogramBucketSettings.ts | 35 - .../envoy/config/metrics/v3/HystrixSink.ts | 61 -- .../envoy/config/metrics/v3/StatsConfig.ts | 148 ---- .../envoy/config/metrics/v3/StatsMatcher.ts | 47 -- .../envoy/config/metrics/v3/StatsSink.ts | 43 -- .../envoy/config/metrics/v3/StatsdSink.ts | 103 --- .../envoy/config/metrics/v3/TagSpecifier.ts | 174 ----- .../config/overload/v3/BufferFactoryConfig.ts | 50 -- .../config/overload/v3/OverloadAction.ts | 42 -- .../config/overload/v3/OverloadManager.ts | 44 -- .../config/overload/v3/ResourceMonitor.ts | 33 - .../v3/ScaleTimersOverloadActionConfig.ts | 89 --- .../envoy/config/overload/v3/ScaledTrigger.ts | 28 - .../config/overload/v3/ThresholdTrigger.ts | 18 - .../envoy/config/overload/v3/Trigger.ts | 24 - .../envoy/config/route/v3/HeaderMatcher.ts | 11 +- .../envoy/config/route/v3/RateLimit.ts | 28 +- .../envoy/config/route/v3/RedirectAction.ts | 47 +- .../envoy/config/route/v3/RetryPolicy.ts | 20 +- .../envoy/config/route/v3/RouteAction.ts | 86 ++- .../envoy/config/route/v3/VirtualHost.ts | 37 +- .../envoy/config/route/v3/WeightedCluster.ts | 2 + .../data/accesslog/v3/AccessLogCommon.ts | 8 +- .../envoy/data/accesslog/v3/AccessLogType.ts | 52 +- .../data/accesslog/v3/HTTPAccessLogEntry.ts | 38 +- .../accesslog/v3/HTTPRequestProperties.ts | 6 +- .../envoy/data/accesslog/v3/ResponseFlags.ts | 29 +- .../envoy/data/accesslog/v3/TLSProperties.ts | 32 +- .../filters/common/fault/v3/FaultDelay.ts | 15 +- .../v3/HttpConnectionManager.ts | 234 ++++++- .../common/v3/LocalityLbConfig.ts | 1 - .../ring_hash/v3/RingHash.ts | 46 +- .../v3/CertificateProviderPluginInstance.ts | 52 -- .../tls/v3/CertificateValidationContext.ts | 372 ---------- .../transport_sockets/tls/v3/GenericSecret.ts | 17 - .../tls/v3/PrivateKeyProvider.ts | 39 -- .../tls/v3/SdsSecretConfig.ts | 23 - .../transport_sockets/tls/v3/Secret.ts | 36 - .../tls/v3/TlsCertificate.ts | 127 ---- .../transport_sockets/tls/v3/TlsParameters.ts | 211 ------ .../tls/v3/TlsSessionTicketKeys.ts | 61 -- .../envoy/service/status/v3/ClientConfig.ts | 14 +- .../service/status/v3/ClientConfigStatus.ts | 45 +- .../envoy/service/status/v3/ConfigStatus.ts | 52 +- .../envoy/service/status/v3/PerXdsConfig.ts | 14 +- .../envoy/type/matcher/v3/RegexMatcher.ts | 4 + .../envoy/type/v3/CodecClientType.ts | 25 +- .../envoy/type/v3/FractionalPercent.ts | 45 +- .../google/protobuf/FieldDescriptorProto.ts | 108 ++- .../generated/google/protobuf/FieldOptions.ts | 48 +- .../generated/google/protobuf/FileOptions.ts | 30 +- .../generated/google/protobuf/NullValue.ts | 12 +- .../src/generated/google/protobuf/Value.ts | 6 +- .../udpa/annotations/PackageVersionStatus.ts | 37 +- .../udpa/annotations/StatusAnnotation.ts | 6 +- .../src/generated/validate/FieldRules.ts | 1 - .../src/generated/validate/KnownRegex.ts | 32 +- .../src/generated/validate/StringRules.ts | 6 +- .../annotations/v3/PackageVersionStatus.ts | 37 +- .../xds/annotations/v3/StatusAnnotation.ts | 6 +- .../generated/xds/core/v3/ResourceLocator.ts | 24 +- packages/grpc-js/package.json | 2 +- 137 files changed, 2344 insertions(+), 4269 deletions(-) delete mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts delete mode 100644 packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureRequest.ts index 7f07e8966..4128fdda4 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureRequest.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ClientConfigureRequest.ts @@ -5,7 +5,7 @@ * Metadata to be attached for the given type of RPCs. */ export interface _grpc_testing_ClientConfigureRequest_Metadata { - 'type'?: (_grpc_testing_ClientConfigureRequest_RpcType | keyof typeof _grpc_testing_ClientConfigureRequest_RpcType); + 'type'?: (_grpc_testing_ClientConfigureRequest_RpcType); 'key'?: (string); 'value'?: (string); } @@ -14,7 +14,7 @@ export interface _grpc_testing_ClientConfigureRequest_Metadata { * Metadata to be attached for the given type of RPCs. */ export interface _grpc_testing_ClientConfigureRequest_Metadata__Output { - 'type': (keyof typeof _grpc_testing_ClientConfigureRequest_RpcType); + 'type': (_grpc_testing_ClientConfigureRequest_RpcType__Output); 'key': (string); 'value': (string); } @@ -24,10 +24,24 @@ export interface _grpc_testing_ClientConfigureRequest_Metadata__Output { /** * Type of RPCs to send. */ -export enum _grpc_testing_ClientConfigureRequest_RpcType { - EMPTY_CALL = 0, - UNARY_CALL = 1, -} +export const _grpc_testing_ClientConfigureRequest_RpcType = { + EMPTY_CALL: 'EMPTY_CALL', + UNARY_CALL: 'UNARY_CALL', +} as const; + +/** + * Type of RPCs to send. + */ +export type _grpc_testing_ClientConfigureRequest_RpcType = + | 'EMPTY_CALL' + | 0 + | 'UNARY_CALL' + | 1 + +/** + * Type of RPCs to send. + */ +export type _grpc_testing_ClientConfigureRequest_RpcType__Output = typeof _grpc_testing_ClientConfigureRequest_RpcType[keyof typeof _grpc_testing_ClientConfigureRequest_RpcType] /** * Configurations for a test client. @@ -36,7 +50,7 @@ export interface ClientConfigureRequest { /** * The types of RPCs the client sends. */ - 'types'?: (_grpc_testing_ClientConfigureRequest_RpcType | keyof typeof _grpc_testing_ClientConfigureRequest_RpcType)[]; + 'types'?: (_grpc_testing_ClientConfigureRequest_RpcType)[]; /** * The collection of custom metadata to be attached to RPCs sent by the client. */ @@ -55,7 +69,7 @@ export interface ClientConfigureRequest__Output { /** * The types of RPCs the client sends. */ - 'types': (keyof typeof _grpc_testing_ClientConfigureRequest_RpcType)[]; + 'types': (_grpc_testing_ClientConfigureRequest_RpcType__Output)[]; /** * The collection of custom metadata to be attached to RPCs sent by the client. */ diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/GrpclbRouteType.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/GrpclbRouteType.ts index 8ab0146b7..667442b41 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/GrpclbRouteType.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/GrpclbRouteType.ts @@ -8,17 +8,52 @@ * the address of this server from the gRPCLB server BalanceLoad RPC). Exactly * how this detection is done is context and server dependent. */ -export enum GrpclbRouteType { +export const GrpclbRouteType = { /** * Server didn't detect the route that a client took to reach it. */ - GRPCLB_ROUTE_TYPE_UNKNOWN = 0, + GRPCLB_ROUTE_TYPE_UNKNOWN: 'GRPCLB_ROUTE_TYPE_UNKNOWN', /** * Indicates that a client reached a server via gRPCLB fallback. */ - GRPCLB_ROUTE_TYPE_FALLBACK = 1, + GRPCLB_ROUTE_TYPE_FALLBACK: 'GRPCLB_ROUTE_TYPE_FALLBACK', /** * Indicates that a client reached a server as a gRPCLB-given backend. */ - GRPCLB_ROUTE_TYPE_BACKEND = 2, -} + GRPCLB_ROUTE_TYPE_BACKEND: 'GRPCLB_ROUTE_TYPE_BACKEND', +} as const; + +/** + * The type of route that a client took to reach a server w.r.t. gRPCLB. + * The server must fill in "fallback" if it detects that the RPC reached + * the server via the "gRPCLB fallback" path, and "backend" if it detects + * that the RPC reached the server via "gRPCLB backend" path (i.e. if it got + * the address of this server from the gRPCLB server BalanceLoad RPC). Exactly + * how this detection is done is context and server dependent. + */ +export type GrpclbRouteType = + /** + * Server didn't detect the route that a client took to reach it. + */ + | 'GRPCLB_ROUTE_TYPE_UNKNOWN' + | 0 + /** + * Indicates that a client reached a server via gRPCLB fallback. + */ + | 'GRPCLB_ROUTE_TYPE_FALLBACK' + | 1 + /** + * Indicates that a client reached a server as a gRPCLB-given backend. + */ + | 'GRPCLB_ROUTE_TYPE_BACKEND' + | 2 + +/** + * The type of route that a client took to reach a server w.r.t. gRPCLB. + * The server must fill in "fallback" if it detects that the RPC reached + * the server via the "gRPCLB fallback" path, and "backend" if it detects + * that the RPC reached the server via "gRPCLB backend" path (i.e. if it got + * the address of this server from the gRPCLB server BalanceLoad RPC). Exactly + * how this detection is done is context and server dependent. + */ +export type GrpclbRouteType__Output = typeof GrpclbRouteType[keyof typeof GrpclbRouteType] diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsResponse.ts index 91157ac4e..000ef9ecf 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsResponse.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerAccumulatedStatsResponse.ts @@ -32,16 +32,19 @@ export interface LoadBalancerAccumulatedStatsResponse { /** * The total number of RPCs have ever issued for each type. * Deprecated: use stats_per_method.rpcs_started instead. + * @deprecated */ 'num_rpcs_started_by_method'?: ({[key: string]: number}); /** * The total number of RPCs have ever completed successfully for each type. * Deprecated: use stats_per_method.result instead. + * @deprecated */ 'num_rpcs_succeeded_by_method'?: ({[key: string]: number}); /** * The total number of RPCs have ever failed for each type. * Deprecated: use stats_per_method.result instead. + * @deprecated */ 'num_rpcs_failed_by_method'?: ({[key: string]: number}); /** @@ -58,21 +61,24 @@ export interface LoadBalancerAccumulatedStatsResponse__Output { /** * The total number of RPCs have ever issued for each type. * Deprecated: use stats_per_method.rpcs_started instead. + * @deprecated */ 'num_rpcs_started_by_method': ({[key: string]: number}); /** * The total number of RPCs have ever completed successfully for each type. * Deprecated: use stats_per_method.result instead. + * @deprecated */ 'num_rpcs_succeeded_by_method': ({[key: string]: number}); /** * The total number of RPCs have ever failed for each type. * Deprecated: use stats_per_method.result instead. + * @deprecated */ 'num_rpcs_failed_by_method': ({[key: string]: number}); /** * Per-method RPC statistics. The key is the RpcType in string form; e.g. * 'EMPTY_CALL' or 'UNARY_CALL' */ - 'stats_per_method'?: ({[key: string]: _grpc_testing_LoadBalancerAccumulatedStatsResponse_MethodStats__Output}); + 'stats_per_method': ({[key: string]: _grpc_testing_LoadBalancerAccumulatedStatsResponse_MethodStats__Output}); } diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsResponse.ts index 184a6e258..ab33612c3 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsResponse.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsResponse.ts @@ -36,5 +36,5 @@ export interface LoadBalancerStatsResponse__Output { * The number of RPCs that failed to record a remote peer. */ 'num_failures': (number); - 'rpcs_by_method'?: ({[key: string]: _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer__Output}); + 'rpcs_by_method': ({[key: string]: _grpc_testing_LoadBalancerStatsResponse_RpcsByPeer__Output}); } diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsService.ts index 26cfee9d7..9d11d9418 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsService.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/LoadBalancerStatsService.ts @@ -1,6 +1,7 @@ // Original file: proto/grpc/testing/test.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { LoadBalancerAccumulatedStatsRequest as _grpc_testing_LoadBalancerAccumulatedStatsRequest, LoadBalancerAccumulatedStatsRequest__Output as _grpc_testing_LoadBalancerAccumulatedStatsRequest__Output } from '../../grpc/testing/LoadBalancerAccumulatedStatsRequest'; import type { LoadBalancerAccumulatedStatsResponse as _grpc_testing_LoadBalancerAccumulatedStatsResponse, LoadBalancerAccumulatedStatsResponse__Output as _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output } from '../../grpc/testing/LoadBalancerAccumulatedStatsResponse'; import type { LoadBalancerStatsRequest as _grpc_testing_LoadBalancerStatsRequest, LoadBalancerStatsRequest__Output as _grpc_testing_LoadBalancerStatsRequest__Output } from '../../grpc/testing/LoadBalancerStatsRequest'; @@ -13,32 +14,32 @@ export interface LoadBalancerStatsServiceClient extends grpc.Client { /** * Gets the accumulated stats for RPCs sent by a test client. */ - GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; - GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; - GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; - GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; + GetClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; /** * Gets the accumulated stats for RPCs sent by a test client. */ - getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; - getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; - getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; - getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; + getClientAccumulatedStats(argument: _grpc_testing_LoadBalancerAccumulatedStatsRequest, callback: grpc.requestCallback<_grpc_testing_LoadBalancerAccumulatedStatsResponse__Output>): grpc.ClientUnaryCall; /** * Gets the backend distribution for RPCs sent by a test client. */ - GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; - GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; - GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; - GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; + GetClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; /** * Gets the backend distribution for RPCs sent by a test client. */ - getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; - getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; - getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; - getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_LoadBalancerStatsResponse__Output) => void): grpc.ClientUnaryCall; + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; + getClientStats(argument: _grpc_testing_LoadBalancerStatsRequest, callback: grpc.requestCallback<_grpc_testing_LoadBalancerStatsResponse__Output>): grpc.ClientUnaryCall; } @@ -57,3 +58,8 @@ export interface LoadBalancerStatsServiceHandlers extends grpc.UntypedServiceImp GetClientStats: grpc.handleUnaryCall<_grpc_testing_LoadBalancerStatsRequest__Output, _grpc_testing_LoadBalancerStatsResponse>; } + +export interface LoadBalancerStatsServiceDefinition extends grpc.ServiceDefinition { + GetClientAccumulatedStats: MethodDefinition<_grpc_testing_LoadBalancerAccumulatedStatsRequest, _grpc_testing_LoadBalancerAccumulatedStatsResponse, _grpc_testing_LoadBalancerAccumulatedStatsRequest__Output, _grpc_testing_LoadBalancerAccumulatedStatsResponse__Output> + GetClientStats: MethodDefinition<_grpc_testing_LoadBalancerStatsRequest, _grpc_testing_LoadBalancerStatsResponse, _grpc_testing_LoadBalancerStatsRequest__Output, _grpc_testing_LoadBalancerStatsResponse__Output> +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/Payload.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/Payload.ts index 79102d2bf..17eb9e60a 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/Payload.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/Payload.ts @@ -1,6 +1,6 @@ // Original file: proto/grpc/testing/messages.proto -import type { PayloadType as _grpc_testing_PayloadType } from '../../grpc/testing/PayloadType'; +import type { PayloadType as _grpc_testing_PayloadType, PayloadType__Output as _grpc_testing_PayloadType__Output } from '../../grpc/testing/PayloadType'; /** * A block of data, to simply increase gRPC message size. @@ -9,7 +9,7 @@ export interface Payload { /** * The type of data in body. */ - 'type'?: (_grpc_testing_PayloadType | keyof typeof _grpc_testing_PayloadType); + 'type'?: (_grpc_testing_PayloadType); /** * Primary contents of payload. */ @@ -23,7 +23,7 @@ export interface Payload__Output { /** * The type of data in body. */ - 'type': (keyof typeof _grpc_testing_PayloadType); + 'type': (_grpc_testing_PayloadType__Output); /** * Primary contents of payload. */ diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/PayloadType.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/PayloadType.ts index 3cf9d375a..64e526090 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/PayloadType.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/PayloadType.ts @@ -3,9 +3,24 @@ /** * The type of payload that should be returned. */ -export enum PayloadType { +export const PayloadType = { /** * Compressable text format. */ - COMPRESSABLE = 0, -} + COMPRESSABLE: 'COMPRESSABLE', +} as const; + +/** + * The type of payload that should be returned. + */ +export type PayloadType = + /** + * Compressable text format. + */ + | 'COMPRESSABLE' + | 0 + +/** + * The type of payload that should be returned. + */ +export type PayloadType__Output = typeof PayloadType[keyof typeof PayloadType] diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectService.ts index e489e2849..2e3f25680 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectService.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ReconnectService.ts @@ -1,6 +1,7 @@ // Original file: proto/grpc/testing/test.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; import type { ReconnectInfo as _grpc_testing_ReconnectInfo, ReconnectInfo__Output as _grpc_testing_ReconnectInfo__Output } from '../../grpc/testing/ReconnectInfo'; import type { ReconnectParams as _grpc_testing_ReconnectParams, ReconnectParams__Output as _grpc_testing_ReconnectParams__Output } from '../../grpc/testing/ReconnectParams'; @@ -9,23 +10,23 @@ import type { ReconnectParams as _grpc_testing_ReconnectParams, ReconnectParams_ * A service used to control reconnect server. */ export interface ReconnectServiceClient extends grpc.Client { - Start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - Start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - Start(argument: _grpc_testing_ReconnectParams, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - Start(argument: _grpc_testing_ReconnectParams, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - start(argument: _grpc_testing_ReconnectParams, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - start(argument: _grpc_testing_ReconnectParams, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + Start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + Start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + Start(argument: _grpc_testing_ReconnectParams, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + Start(argument: _grpc_testing_ReconnectParams, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + start(argument: _grpc_testing_ReconnectParams, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; - Stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; - Stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; - Stop(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; - Stop(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; - stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; - stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; - stop(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; - stop(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ReconnectInfo__Output) => void): grpc.ClientUnaryCall; + Stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; + Stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; + Stop(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; + Stop(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; + stop(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_ReconnectInfo__Output>): grpc.ClientUnaryCall; } @@ -38,3 +39,8 @@ export interface ReconnectServiceHandlers extends grpc.UntypedServiceImplementat Stop: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_ReconnectInfo>; } + +export interface ReconnectServiceDefinition extends grpc.ServiceDefinition { + Start: MethodDefinition<_grpc_testing_ReconnectParams, _grpc_testing_Empty, _grpc_testing_ReconnectParams__Output, _grpc_testing_Empty__Output> + Stop: MethodDefinition<_grpc_testing_Empty, _grpc_testing_ReconnectInfo, _grpc_testing_Empty__Output, _grpc_testing_ReconnectInfo__Output> +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/ResponseParameters.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/ResponseParameters.ts index 04ca94ced..15f2f01f4 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/ResponseParameters.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/ResponseParameters.ts @@ -21,7 +21,7 @@ export interface ResponseParameters { * implement the full compression tests by introspecting the call to verify * the response's compression status. */ - 'compressed'?: (_grpc_testing_BoolValue); + 'compressed'?: (_grpc_testing_BoolValue | null); } /** @@ -43,5 +43,5 @@ export interface ResponseParameters__Output { * implement the full compression tests by introspecting the call to verify * the response's compression status. */ - 'compressed'?: (_grpc_testing_BoolValue__Output); + 'compressed': (_grpc_testing_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleRequest.ts index 056eb10b2..21843af69 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleRequest.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleRequest.ts @@ -1,6 +1,6 @@ // Original file: proto/grpc/testing/messages.proto -import type { PayloadType as _grpc_testing_PayloadType } from '../../grpc/testing/PayloadType'; +import type { PayloadType as _grpc_testing_PayloadType, PayloadType__Output as _grpc_testing_PayloadType__Output } from '../../grpc/testing/PayloadType'; import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; import type { BoolValue as _grpc_testing_BoolValue, BoolValue__Output as _grpc_testing_BoolValue__Output } from '../../grpc/testing/BoolValue'; import type { EchoStatus as _grpc_testing_EchoStatus, EchoStatus__Output as _grpc_testing_EchoStatus__Output } from '../../grpc/testing/EchoStatus'; @@ -13,7 +13,7 @@ export interface SimpleRequest { * Desired payload type in the response from the server. * If response_type is RANDOM, server randomly chooses one from other formats. */ - 'response_type'?: (_grpc_testing_PayloadType | keyof typeof _grpc_testing_PayloadType); + 'response_type'?: (_grpc_testing_PayloadType); /** * Desired payload size in the response from the server. */ @@ -21,7 +21,7 @@ export interface SimpleRequest { /** * Optional input payload sent along with the request. */ - 'payload'?: (_grpc_testing_Payload); + 'payload'?: (_grpc_testing_Payload | null); /** * Whether SimpleResponse should include username. */ @@ -36,15 +36,15 @@ export interface SimpleRequest { * implement the full compression tests by introspecting the call to verify * the response's compression status. */ - 'response_compressed'?: (_grpc_testing_BoolValue); + 'response_compressed'?: (_grpc_testing_BoolValue | null); /** * Whether server should return a given status */ - 'response_status'?: (_grpc_testing_EchoStatus); + 'response_status'?: (_grpc_testing_EchoStatus | null); /** * Whether the server should expect this request to be compressed. */ - 'expect_compressed'?: (_grpc_testing_BoolValue); + 'expect_compressed'?: (_grpc_testing_BoolValue | null); /** * Whether SimpleResponse should include server_id. */ @@ -63,7 +63,7 @@ export interface SimpleRequest__Output { * Desired payload type in the response from the server. * If response_type is RANDOM, server randomly chooses one from other formats. */ - 'response_type': (keyof typeof _grpc_testing_PayloadType); + 'response_type': (_grpc_testing_PayloadType__Output); /** * Desired payload size in the response from the server. */ @@ -71,7 +71,7 @@ export interface SimpleRequest__Output { /** * Optional input payload sent along with the request. */ - 'payload'?: (_grpc_testing_Payload__Output); + 'payload': (_grpc_testing_Payload__Output | null); /** * Whether SimpleResponse should include username. */ @@ -86,15 +86,15 @@ export interface SimpleRequest__Output { * implement the full compression tests by introspecting the call to verify * the response's compression status. */ - 'response_compressed'?: (_grpc_testing_BoolValue__Output); + 'response_compressed': (_grpc_testing_BoolValue__Output | null); /** * Whether server should return a given status */ - 'response_status'?: (_grpc_testing_EchoStatus__Output); + 'response_status': (_grpc_testing_EchoStatus__Output | null); /** * Whether the server should expect this request to be compressed. */ - 'expect_compressed'?: (_grpc_testing_BoolValue__Output); + 'expect_compressed': (_grpc_testing_BoolValue__Output | null); /** * Whether SimpleResponse should include server_id. */ diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleResponse.ts index 661f336ce..b737c31fa 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleResponse.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/SimpleResponse.ts @@ -1,7 +1,7 @@ // Original file: proto/grpc/testing/messages.proto import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; -import type { GrpclbRouteType as _grpc_testing_GrpclbRouteType } from '../../grpc/testing/GrpclbRouteType'; +import type { GrpclbRouteType as _grpc_testing_GrpclbRouteType, GrpclbRouteType__Output as _grpc_testing_GrpclbRouteType__Output } from '../../grpc/testing/GrpclbRouteType'; /** * Unary response, as configured by the request. @@ -10,7 +10,7 @@ export interface SimpleResponse { /** * Payload to increase message size. */ - 'payload'?: (_grpc_testing_Payload); + 'payload'?: (_grpc_testing_Payload | null); /** * The user the request came from, for verifying authentication was * successful when the client expected it. @@ -28,7 +28,7 @@ export interface SimpleResponse { /** * gRPCLB Path. */ - 'grpclb_route_type'?: (_grpc_testing_GrpclbRouteType | keyof typeof _grpc_testing_GrpclbRouteType); + 'grpclb_route_type'?: (_grpc_testing_GrpclbRouteType); /** * Server hostname. */ @@ -42,7 +42,7 @@ export interface SimpleResponse__Output { /** * Payload to increase message size. */ - 'payload'?: (_grpc_testing_Payload__Output); + 'payload': (_grpc_testing_Payload__Output | null); /** * The user the request came from, for verifying authentication was * successful when the client expected it. @@ -60,7 +60,7 @@ export interface SimpleResponse__Output { /** * gRPCLB Path. */ - 'grpclb_route_type': (keyof typeof _grpc_testing_GrpclbRouteType); + 'grpclb_route_type': (_grpc_testing_GrpclbRouteType__Output); /** * Server hostname. */ diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallRequest.ts index 56ad2b217..f45568849 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallRequest.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingInputCallRequest.ts @@ -10,14 +10,14 @@ export interface StreamingInputCallRequest { /** * Optional input payload sent along with the request. */ - 'payload'?: (_grpc_testing_Payload); + 'payload'?: (_grpc_testing_Payload | null); /** * Whether the server should expect this request to be compressed. This field * is "nullable" in order to interoperate seamlessly with servers not able to * implement the full compression tests by introspecting the call to verify * the request's compression status. */ - 'expect_compressed'?: (_grpc_testing_BoolValue); + 'expect_compressed'?: (_grpc_testing_BoolValue | null); } /** @@ -27,12 +27,12 @@ export interface StreamingInputCallRequest__Output { /** * Optional input payload sent along with the request. */ - 'payload'?: (_grpc_testing_Payload__Output); + 'payload': (_grpc_testing_Payload__Output | null); /** * Whether the server should expect this request to be compressed. This field * is "nullable" in order to interoperate seamlessly with servers not able to * implement the full compression tests by introspecting the call to verify * the request's compression status. */ - 'expect_compressed'?: (_grpc_testing_BoolValue__Output); + 'expect_compressed': (_grpc_testing_BoolValue__Output | null); } diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallRequest.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallRequest.ts index 52922062d..0d812b74f 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallRequest.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallRequest.ts @@ -1,6 +1,6 @@ // Original file: proto/grpc/testing/messages.proto -import type { PayloadType as _grpc_testing_PayloadType } from '../../grpc/testing/PayloadType'; +import type { PayloadType as _grpc_testing_PayloadType, PayloadType__Output as _grpc_testing_PayloadType__Output } from '../../grpc/testing/PayloadType'; import type { ResponseParameters as _grpc_testing_ResponseParameters, ResponseParameters__Output as _grpc_testing_ResponseParameters__Output } from '../../grpc/testing/ResponseParameters'; import type { Payload as _grpc_testing_Payload, Payload__Output as _grpc_testing_Payload__Output } from '../../grpc/testing/Payload'; import type { EchoStatus as _grpc_testing_EchoStatus, EchoStatus__Output as _grpc_testing_EchoStatus__Output } from '../../grpc/testing/EchoStatus'; @@ -15,7 +15,7 @@ export interface StreamingOutputCallRequest { * might be of different types. This is to simulate a mixed type of payload * stream. */ - 'response_type'?: (_grpc_testing_PayloadType | keyof typeof _grpc_testing_PayloadType); + 'response_type'?: (_grpc_testing_PayloadType); /** * Configuration for each expected response message. */ @@ -23,11 +23,11 @@ export interface StreamingOutputCallRequest { /** * Optional input payload sent along with the request. */ - 'payload'?: (_grpc_testing_Payload); + 'payload'?: (_grpc_testing_Payload | null); /** * Whether server should return a given status */ - 'response_status'?: (_grpc_testing_EchoStatus); + 'response_status'?: (_grpc_testing_EchoStatus | null); } /** @@ -40,7 +40,7 @@ export interface StreamingOutputCallRequest__Output { * might be of different types. This is to simulate a mixed type of payload * stream. */ - 'response_type': (keyof typeof _grpc_testing_PayloadType); + 'response_type': (_grpc_testing_PayloadType__Output); /** * Configuration for each expected response message. */ @@ -48,9 +48,9 @@ export interface StreamingOutputCallRequest__Output { /** * Optional input payload sent along with the request. */ - 'payload'?: (_grpc_testing_Payload__Output); + 'payload': (_grpc_testing_Payload__Output | null); /** * Whether server should return a given status */ - 'response_status'?: (_grpc_testing_EchoStatus__Output); + 'response_status': (_grpc_testing_EchoStatus__Output | null); } diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallResponse.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallResponse.ts index 19ab306dd..e2eb435cd 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallResponse.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/StreamingOutputCallResponse.ts @@ -9,7 +9,7 @@ export interface StreamingOutputCallResponse { /** * Payload to increase response size. */ - 'payload'?: (_grpc_testing_Payload); + 'payload'?: (_grpc_testing_Payload | null); } /** @@ -19,5 +19,5 @@ export interface StreamingOutputCallResponse__Output { /** * Payload to increase response size. */ - 'payload'?: (_grpc_testing_Payload__Output); + 'payload': (_grpc_testing_Payload__Output | null); } diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/TestService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/TestService.ts index dbb606c83..139d3c0ef 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/TestService.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/TestService.ts @@ -1,6 +1,7 @@ // Original file: proto/grpc/testing/test.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; import type { SimpleRequest as _grpc_testing_SimpleRequest, SimpleRequest__Output as _grpc_testing_SimpleRequest__Output } from '../../grpc/testing/SimpleRequest'; import type { SimpleResponse as _grpc_testing_SimpleResponse, SimpleResponse__Output as _grpc_testing_SimpleResponse__Output } from '../../grpc/testing/SimpleResponse'; @@ -19,34 +20,34 @@ export interface TestServiceClient extends grpc.Client { * headers set such that a caching HTTP proxy (such as GFE) can * satisfy subsequent requests. */ - CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + CacheableUnaryCall(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; /** * One request followed by one response. Response has cache control * headers set such that a caching HTTP proxy (such as GFE) can * satisfy subsequent requests. */ - cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + cacheableUnaryCall(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; /** * One empty request followed by one empty response. */ - EmptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - EmptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - EmptyCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - EmptyCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + EmptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + EmptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + EmptyCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + EmptyCall(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; /** * One empty request followed by one empty response. */ - emptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - emptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - emptyCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - emptyCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + emptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + emptyCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + emptyCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + emptyCall(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; /** * A sequence of requests with each request served by the server immediately. @@ -84,18 +85,18 @@ export interface TestServiceClient extends grpc.Client { * A sequence of requests followed by one response (streamed upload). * The server returns the aggregated size of client payload as the result. */ - StreamingInputCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; - StreamingInputCall(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; - StreamingInputCall(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; - StreamingInputCall(callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + StreamingInputCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + StreamingInputCall(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + StreamingInputCall(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + StreamingInputCall(callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; /** * A sequence of requests followed by one response (streamed upload). * The server returns the aggregated size of client payload as the result. */ - streamingInputCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; - streamingInputCall(metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; - streamingInputCall(options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; - streamingInputCall(callback: (error?: grpc.ServiceError, result?: _grpc_testing_StreamingInputCallResponse__Output) => void): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + streamingInputCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + streamingInputCall(metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + streamingInputCall(options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; + streamingInputCall(callback: grpc.requestCallback<_grpc_testing_StreamingInputCallResponse__Output>): grpc.ClientWritableStream<_grpc_testing_StreamingInputCallRequest>; /** * One request followed by a sequence of responses (streamed download). @@ -113,34 +114,34 @@ export interface TestServiceClient extends grpc.Client { /** * One request followed by one response. */ - UnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - UnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - UnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - UnaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + UnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + UnaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + UnaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + UnaryCall(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; /** * One request followed by one response. */ - unaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - unaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - unaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; - unaryCall(argument: _grpc_testing_SimpleRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_SimpleResponse__Output) => void): grpc.ClientUnaryCall; + unaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + unaryCall(argument: _grpc_testing_SimpleRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + unaryCall(argument: _grpc_testing_SimpleRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; + unaryCall(argument: _grpc_testing_SimpleRequest, callback: grpc.requestCallback<_grpc_testing_SimpleResponse__Output>): grpc.ClientUnaryCall; /** * The test server will not implement this method. It will be used * to test the behavior when clients call unimplemented methods. */ - UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - UnimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - UnimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; /** * The test server will not implement this method. It will be used * to test the behavior when clients call unimplemented methods. */ - unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - unimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - unimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; } @@ -200,3 +201,14 @@ export interface TestServiceHandlers extends grpc.UntypedServiceImplementation { UnimplementedCall: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; } + +export interface TestServiceDefinition extends grpc.ServiceDefinition { + CacheableUnaryCall: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + EmptyCall: MethodDefinition<_grpc_testing_Empty, _grpc_testing_Empty, _grpc_testing_Empty__Output, _grpc_testing_Empty__Output> + FullDuplexCall: MethodDefinition<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse, _grpc_testing_StreamingOutputCallRequest__Output, _grpc_testing_StreamingOutputCallResponse__Output> + HalfDuplexCall: MethodDefinition<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse, _grpc_testing_StreamingOutputCallRequest__Output, _grpc_testing_StreamingOutputCallResponse__Output> + StreamingInputCall: MethodDefinition<_grpc_testing_StreamingInputCallRequest, _grpc_testing_StreamingInputCallResponse, _grpc_testing_StreamingInputCallRequest__Output, _grpc_testing_StreamingInputCallResponse__Output> + StreamingOutputCall: MethodDefinition<_grpc_testing_StreamingOutputCallRequest, _grpc_testing_StreamingOutputCallResponse, _grpc_testing_StreamingOutputCallRequest__Output, _grpc_testing_StreamingOutputCallResponse__Output> + UnaryCall: MethodDefinition<_grpc_testing_SimpleRequest, _grpc_testing_SimpleResponse, _grpc_testing_SimpleRequest__Output, _grpc_testing_SimpleResponse__Output> + UnimplementedCall: MethodDefinition<_grpc_testing_Empty, _grpc_testing_Empty, _grpc_testing_Empty__Output, _grpc_testing_Empty__Output> +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/UnimplementedService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/UnimplementedService.ts index d21dfcd0f..aea5d8b4a 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/UnimplementedService.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/UnimplementedService.ts @@ -1,6 +1,7 @@ // Original file: proto/grpc/testing/test.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; /** @@ -11,17 +12,17 @@ export interface UnimplementedServiceClient extends grpc.Client { /** * A call that no server should implement */ - UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - UnimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - UnimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + UnimplementedCall(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; /** * A call that no server should implement */ - unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - unimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - unimplementedCall(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + unimplementedCall(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; } @@ -36,3 +37,7 @@ export interface UnimplementedServiceHandlers extends grpc.UntypedServiceImpleme UnimplementedCall: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; } + +export interface UnimplementedServiceDefinition extends grpc.ServiceDefinition { + UnimplementedCall: MethodDefinition<_grpc_testing_Empty, _grpc_testing_Empty, _grpc_testing_Empty__Output, _grpc_testing_Empty__Output> +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateClientConfigureService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateClientConfigureService.ts index 22947619c..76826b812 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateClientConfigureService.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateClientConfigureService.ts @@ -1,6 +1,7 @@ // Original file: proto/grpc/testing/test.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { ClientConfigureRequest as _grpc_testing_ClientConfigureRequest, ClientConfigureRequest__Output as _grpc_testing_ClientConfigureRequest__Output } from '../../grpc/testing/ClientConfigureRequest'; import type { ClientConfigureResponse as _grpc_testing_ClientConfigureResponse, ClientConfigureResponse__Output as _grpc_testing_ClientConfigureResponse__Output } from '../../grpc/testing/ClientConfigureResponse'; @@ -11,17 +12,17 @@ export interface XdsUpdateClientConfigureServiceClient extends grpc.Client { /** * Update the tes client's configuration. */ - Configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; - Configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; - Configure(argument: _grpc_testing_ClientConfigureRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; - Configure(argument: _grpc_testing_ClientConfigureRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + Configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; + Configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; + Configure(argument: _grpc_testing_ClientConfigureRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; + Configure(argument: _grpc_testing_ClientConfigureRequest, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; /** * Update the tes client's configuration. */ - configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; - configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; - configure(argument: _grpc_testing_ClientConfigureRequest, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; - configure(argument: _grpc_testing_ClientConfigureRequest, callback: (error?: grpc.ServiceError, result?: _grpc_testing_ClientConfigureResponse__Output) => void): grpc.ClientUnaryCall; + configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; + configure(argument: _grpc_testing_ClientConfigureRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; + configure(argument: _grpc_testing_ClientConfigureRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; + configure(argument: _grpc_testing_ClientConfigureRequest, callback: grpc.requestCallback<_grpc_testing_ClientConfigureResponse__Output>): grpc.ClientUnaryCall; } @@ -35,3 +36,7 @@ export interface XdsUpdateClientConfigureServiceHandlers extends grpc.UntypedSer Configure: grpc.handleUnaryCall<_grpc_testing_ClientConfigureRequest__Output, _grpc_testing_ClientConfigureResponse>; } + +export interface XdsUpdateClientConfigureServiceDefinition extends grpc.ServiceDefinition { + Configure: MethodDefinition<_grpc_testing_ClientConfigureRequest, _grpc_testing_ClientConfigureResponse, _grpc_testing_ClientConfigureRequest__Output, _grpc_testing_ClientConfigureResponse__Output> +} diff --git a/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateHealthService.ts b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateHealthService.ts index aa1e35dca..aa3d6e9c6 100644 --- a/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateHealthService.ts +++ b/packages/grpc-js-xds/interop/generated/grpc/testing/XdsUpdateHealthService.ts @@ -1,29 +1,30 @@ // Original file: proto/grpc/testing/test.proto import type * as grpc from '@grpc/grpc-js' +import type { MethodDefinition } from '@grpc/proto-loader' import type { Empty as _grpc_testing_Empty, Empty__Output as _grpc_testing_Empty__Output } from '../../grpc/testing/Empty'; /** * A service to remotely control health status of an xDS test server. */ export interface XdsUpdateHealthServiceClient extends grpc.Client { - SetNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - SetNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - SetNotServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - SetNotServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setNotServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setNotServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + SetNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + SetNotServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + SetNotServing(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setNotServing(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; - SetServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - SetServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - SetServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - SetServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; - setServing(argument: _grpc_testing_Empty, callback: (error?: grpc.ServiceError, result?: _grpc_testing_Empty__Output) => void): grpc.ClientUnaryCall; + SetServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + SetServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + SetServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + SetServing(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; + setServing(argument: _grpc_testing_Empty, callback: grpc.requestCallback<_grpc_testing_Empty__Output>): grpc.ClientUnaryCall; } @@ -36,3 +37,8 @@ export interface XdsUpdateHealthServiceHandlers extends grpc.UntypedServiceImple SetServing: grpc.handleUnaryCall<_grpc_testing_Empty__Output, _grpc_testing_Empty>; } + +export interface XdsUpdateHealthServiceDefinition extends grpc.ServiceDefinition { + SetNotServing: MethodDefinition<_grpc_testing_Empty, _grpc_testing_Empty, _grpc_testing_Empty__Output, _grpc_testing_Empty__Output> + SetServing: MethodDefinition<_grpc_testing_Empty, _grpc_testing_Empty, _grpc_testing_Empty__Output, _grpc_testing_Empty__Output> +} diff --git a/packages/grpc-js-xds/interop/generated/test.ts b/packages/grpc-js-xds/interop/generated/test.ts index f91f0c970..722f8fe28 100644 --- a/packages/grpc-js-xds/interop/generated/test.ts +++ b/packages/grpc-js-xds/interop/generated/test.ts @@ -1,12 +1,12 @@ import type * as grpc from '@grpc/grpc-js'; -import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; +import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader'; -import type { LoadBalancerStatsServiceClient as _grpc_testing_LoadBalancerStatsServiceClient } from './grpc/testing/LoadBalancerStatsService'; -import type { ReconnectServiceClient as _grpc_testing_ReconnectServiceClient } from './grpc/testing/ReconnectService'; -import type { TestServiceClient as _grpc_testing_TestServiceClient } from './grpc/testing/TestService'; -import type { UnimplementedServiceClient as _grpc_testing_UnimplementedServiceClient } from './grpc/testing/UnimplementedService'; -import type { XdsUpdateClientConfigureServiceClient as _grpc_testing_XdsUpdateClientConfigureServiceClient } from './grpc/testing/XdsUpdateClientConfigureService'; -import type { XdsUpdateHealthServiceClient as _grpc_testing_XdsUpdateHealthServiceClient } from './grpc/testing/XdsUpdateHealthService'; +import type { LoadBalancerStatsServiceClient as _grpc_testing_LoadBalancerStatsServiceClient, LoadBalancerStatsServiceDefinition as _grpc_testing_LoadBalancerStatsServiceDefinition } from './grpc/testing/LoadBalancerStatsService'; +import type { ReconnectServiceClient as _grpc_testing_ReconnectServiceClient, ReconnectServiceDefinition as _grpc_testing_ReconnectServiceDefinition } from './grpc/testing/ReconnectService'; +import type { TestServiceClient as _grpc_testing_TestServiceClient, TestServiceDefinition as _grpc_testing_TestServiceDefinition } from './grpc/testing/TestService'; +import type { UnimplementedServiceClient as _grpc_testing_UnimplementedServiceClient, UnimplementedServiceDefinition as _grpc_testing_UnimplementedServiceDefinition } from './grpc/testing/UnimplementedService'; +import type { XdsUpdateClientConfigureServiceClient as _grpc_testing_XdsUpdateClientConfigureServiceClient, XdsUpdateClientConfigureServiceDefinition as _grpc_testing_XdsUpdateClientConfigureServiceDefinition } from './grpc/testing/XdsUpdateClientConfigureService'; +import type { XdsUpdateHealthServiceClient as _grpc_testing_XdsUpdateHealthServiceClient, XdsUpdateHealthServiceDefinition as _grpc_testing_XdsUpdateHealthServiceDefinition } from './grpc/testing/XdsUpdateHealthService'; type SubtypeConstructor any, Subtype> = { new(...args: ConstructorParameters): Subtype; @@ -28,7 +28,7 @@ export interface ProtoGrpcType { /** * A service used to obtain stats for verifying LB behavior. */ - LoadBalancerStatsService: SubtypeConstructor & { service: ServiceDefinition } + LoadBalancerStatsService: SubtypeConstructor & { service: _grpc_testing_LoadBalancerStatsServiceDefinition } Payload: MessageTypeDefinition PayloadType: EnumTypeDefinition ReconnectInfo: MessageTypeDefinition @@ -36,7 +36,7 @@ export interface ProtoGrpcType { /** * A service used to control reconnect server. */ - ReconnectService: SubtypeConstructor & { service: ServiceDefinition } + ReconnectService: SubtypeConstructor & { service: _grpc_testing_ReconnectServiceDefinition } ResponseParameters: MessageTypeDefinition SimpleRequest: MessageTypeDefinition SimpleResponse: MessageTypeDefinition @@ -48,20 +48,20 @@ export interface ProtoGrpcType { * A simple service to test the various types of RPCs and experiment with * performance with various types of payload. */ - TestService: SubtypeConstructor & { service: ServiceDefinition } + TestService: SubtypeConstructor & { service: _grpc_testing_TestServiceDefinition } /** * A simple service NOT implemented at servers so clients can test for * that case. */ - UnimplementedService: SubtypeConstructor & { service: ServiceDefinition } + UnimplementedService: SubtypeConstructor & { service: _grpc_testing_UnimplementedServiceDefinition } /** * A service to dynamically update the configuration of an xDS test client. */ - XdsUpdateClientConfigureService: SubtypeConstructor & { service: ServiceDefinition } + XdsUpdateClientConfigureService: SubtypeConstructor & { service: _grpc_testing_XdsUpdateClientConfigureServiceDefinition } /** * A service to remotely control health status of an xDS test server. */ - XdsUpdateHealthService: SubtypeConstructor & { service: ServiceDefinition } + XdsUpdateHealthService: SubtypeConstructor & { service: _grpc_testing_XdsUpdateHealthServiceDefinition } } } } diff --git a/packages/grpc-js-xds/interop/xds-interop-client.ts b/packages/grpc-js-xds/interop/xds-interop-client.ts index 1fdaa3a69..a245ad09f 100644 --- a/packages/grpc-js-xds/interop/xds-interop-client.ts +++ b/packages/grpc-js-xds/interop/xds-interop-client.ts @@ -398,7 +398,7 @@ function makeSingleRequest(client: TestServiceClient, type: CallType, failOnFail const startTime = process.hrtime.bigint(); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + currentConfig.timeoutSec); - const callback = (error: grpc.ServiceError | undefined, value: Empty__Output | undefined) => { + const callback = (error: grpc.ServiceError | null, value: Empty__Output | undefined) => { const statusCode = error?.code ?? grpc.status.OK; const duration = process.hrtime.bigint() - startTime; const durationSeconds = Number(duration / TIMESTAMP_ONE_SECOND) | 0; diff --git a/packages/grpc-js-xds/package.json b/packages/grpc-js-xds/package.json index 1286a16a8..9e7d2f18a 100644 --- a/packages/grpc-js-xds/package.json +++ b/packages/grpc-js-xds/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js-xds", - "version": "1.10.0", + "version": "1.10.1", "description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.", "main": "build/src/index.js", "scripts": { @@ -12,7 +12,7 @@ "prepare": "npm run generate-types && 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 envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.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 envoy/extensions/clusters/aggregate/v3/cluster.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" }, @@ -43,7 +43,7 @@ "yargs": "^15.4.1" }, "dependencies": { - "@grpc/proto-loader": "^0.6.0", + "@grpc/proto-loader": "^0.7.13", "google-auth-library": "^7.0.2", "re2-wasm": "^1.0.1", "vscode-uri": "^3.0.7", diff --git a/packages/grpc-js-xds/src/generated/cluster.ts b/packages/grpc-js-xds/src/generated/cluster.ts index 1aa37589b..6e8c5f985 100644 --- a/packages/grpc-js-xds/src/generated/cluster.ts +++ b/packages/grpc-js-xds/src/generated/cluster.ts @@ -101,6 +101,15 @@ export interface ProtoGrpcType { } } } + extensions: { + clusters: { + aggregate: { + v3: { + ClusterConfig: MessageTypeDefinition + } + } + } + } type: { matcher: { v3: { diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts deleted file mode 100644 index d47f00ef3..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/BootstrapConfigDump.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto - -import type { Bootstrap as _envoy_config_bootstrap_v3_Bootstrap, Bootstrap__Output as _envoy_config_bootstrap_v3_Bootstrap__Output } from '../../../envoy/config/bootstrap/v3/Bootstrap'; -import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; - -/** - * This message describes the bootstrap configuration that Envoy was started with. This includes - * any CLI overrides that were merged. Bootstrap configuration information can be used to recreate - * the static portions of an Envoy configuration by reusing the output as the bootstrap - * configuration for another Envoy. - */ -export interface BootstrapConfigDump { - 'bootstrap'?: (_envoy_config_bootstrap_v3_Bootstrap | null); - /** - * The timestamp when the BootstrapConfig was last updated. - */ - 'last_updated'?: (_google_protobuf_Timestamp | null); -} - -/** - * This message describes the bootstrap configuration that Envoy was started with. This includes - * any CLI overrides that were merged. Bootstrap configuration information can be used to recreate - * the static portions of an Envoy configuration by reusing the output as the bootstrap - * configuration for another Envoy. - */ -export interface BootstrapConfigDump__Output { - 'bootstrap': (_envoy_config_bootstrap_v3_Bootstrap__Output | null); - /** - * The timestamp when the BootstrapConfig was last updated. - */ - 'last_updated': (_google_protobuf_Timestamp__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts index 8488bbdd7..b7a78a338 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClientResourceStatus.ts @@ -4,17 +4,17 @@ * Resource status from the view of a xDS client, which tells the synchronization * status between the xDS client and the xDS server. */ -export enum ClientResourceStatus { +export const ClientResourceStatus = { /** * Resource status is not available/unknown. */ - UNKNOWN = 0, + UNKNOWN: 'UNKNOWN', /** * Client requested this resource but hasn't received any update from management * server. The client will not fail requests, but will queue them until update * arrives or the client times out waiting for the resource. */ - REQUESTED = 1, + REQUESTED: 'REQUESTED', /** * This resource has been requested by the client but has either not been * delivered by the server or was previously delivered by the server and then @@ -22,13 +22,56 @@ export enum ClientResourceStatus { * information, please refer to the :ref:`"Knowing When a Requested Resource * Does Not Exist" ` section. */ - DOES_NOT_EXIST = 2, + DOES_NOT_EXIST: 'DOES_NOT_EXIST', /** * Client received this resource and replied with ACK. */ - ACKED = 3, + ACKED: 'ACKED', /** * Client received this resource and replied with NACK. */ - NACKED = 4, -} + NACKED: 'NACKED', +} as const; + +/** + * Resource status from the view of a xDS client, which tells the synchronization + * status between the xDS client and the xDS server. + */ +export type ClientResourceStatus = + /** + * Resource status is not available/unknown. + */ + | 'UNKNOWN' + | 0 + /** + * Client requested this resource but hasn't received any update from management + * server. The client will not fail requests, but will queue them until update + * arrives or the client times out waiting for the resource. + */ + | 'REQUESTED' + | 1 + /** + * This resource has been requested by the client but has either not been + * delivered by the server or was previously delivered by the server and then + * subsequently removed from resources provided by the server. For more + * information, please refer to the :ref:`"Knowing When a Requested Resource + * Does Not Exist" ` section. + */ + | 'DOES_NOT_EXIST' + | 2 + /** + * Client received this resource and replied with ACK. + */ + | 'ACKED' + | 3 + /** + * Client received this resource and replied with NACK. + */ + | 'NACKED' + | 4 + +/** + * Resource status from the view of a xDS client, which tells the synchronization + * status between the xDS client and the xDS server. + */ +export type ClientResourceStatus__Output = typeof ClientResourceStatus[keyof typeof ClientResourceStatus] diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts index aabcd212a..2c3b4f8a5 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ClustersConfigDump.ts @@ -3,7 +3,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus, ClientResourceStatus__Output as _envoy_admin_v3_ClientResourceStatus__Output } from '../../../envoy/admin/v3/ClientResourceStatus'; /** * Describes a dynamically loaded cluster via the CDS API. @@ -37,7 +37,7 @@ export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus); } /** @@ -72,7 +72,7 @@ export interface _envoy_admin_v3_ClustersConfigDump_DynamicCluster__Output { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status': (_envoy_admin_v3_ClientResourceStatus__Output); } /** diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts deleted file mode 100644 index 8a0ab65c2..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ConfigDump.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto - -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; - -/** - * The :ref:`/config_dump ` admin endpoint uses this wrapper - * message to maintain and serve arbitrary configuration information from any component in Envoy. - */ -export interface ConfigDump { - /** - * This list is serialized and dumped in its entirety at the - * :ref:`/config_dump ` endpoint. - * - * The following configurations are currently supported and will be dumped in the order given - * below: - * - * * *bootstrap*: :ref:`BootstrapConfigDump ` - * * *clusters*: :ref:`ClustersConfigDump ` - * * *endpoints*: :ref:`EndpointsConfigDump ` - * * *listeners*: :ref:`ListenersConfigDump ` - * * *scoped_routes*: :ref:`ScopedRoutesConfigDump ` - * * *routes*: :ref:`RoutesConfigDump ` - * * *secrets*: :ref:`SecretsConfigDump ` - * - * EDS Configuration will only be dumped by using parameter `?include_eds` - * - * You can filter output with the resource and mask query parameters. - * See :ref:`/config_dump?resource={} `, - * :ref:`/config_dump?mask={} `, - * or :ref:`/config_dump?resource={},mask={} - * ` for more information. - */ - 'configs'?: (_google_protobuf_Any)[]; -} - -/** - * The :ref:`/config_dump ` admin endpoint uses this wrapper - * message to maintain and serve arbitrary configuration information from any component in Envoy. - */ -export interface ConfigDump__Output { - /** - * This list is serialized and dumped in its entirety at the - * :ref:`/config_dump ` endpoint. - * - * The following configurations are currently supported and will be dumped in the order given - * below: - * - * * *bootstrap*: :ref:`BootstrapConfigDump ` - * * *clusters*: :ref:`ClustersConfigDump ` - * * *endpoints*: :ref:`EndpointsConfigDump ` - * * *listeners*: :ref:`ListenersConfigDump ` - * * *scoped_routes*: :ref:`ScopedRoutesConfigDump ` - * * *routes*: :ref:`RoutesConfigDump ` - * * *secrets*: :ref:`SecretsConfigDump ` - * - * EDS Configuration will only be dumped by using parameter `?include_eds` - * - * You can filter output with the resource and mask query parameters. - * See :ref:`/config_dump?resource={} `, - * :ref:`/config_dump?mask={} `, - * or :ref:`/config_dump?resource={},mask={} - * ` for more information. - */ - 'configs': (_google_protobuf_Any__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts index e63307cb5..70562962b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EcdsConfigDump.ts @@ -3,7 +3,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus, ClientResourceStatus__Output as _envoy_admin_v3_ClientResourceStatus__Output } from '../../../envoy/admin/v3/ClientResourceStatus'; /** * [#next-free-field: 6] @@ -36,7 +36,7 @@ export interface _envoy_admin_v3_EcdsConfigDump_EcdsFilterConfig { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus); } /** @@ -70,7 +70,7 @@ export interface _envoy_admin_v3_EcdsConfigDump_EcdsFilterConfig__Output { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status': (_envoy_admin_v3_ClientResourceStatus__Output); } /** diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts index ab5485dbe..3f362c5c3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/EndpointsConfigDump.ts @@ -3,7 +3,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus, ClientResourceStatus__Output as _envoy_admin_v3_ClientResourceStatus__Output } from '../../../envoy/admin/v3/ClientResourceStatus'; /** * [#next-free-field: 6] @@ -35,7 +35,7 @@ export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus); } /** @@ -68,7 +68,7 @@ export interface _envoy_admin_v3_EndpointsConfigDump_DynamicEndpointConfig__Outp * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status': (_envoy_admin_v3_ClientResourceStatus__Output); } export interface _envoy_admin_v3_EndpointsConfigDump_StaticEndpointConfig { diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts index 946e37953..a90338fdf 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ListenersConfigDump.ts @@ -3,7 +3,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus, ClientResourceStatus__Output as _envoy_admin_v3_ClientResourceStatus__Output } from '../../../envoy/admin/v3/ClientResourceStatus'; /** * Describes a dynamically loaded listener via the LDS API. @@ -44,7 +44,7 @@ export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus); } /** @@ -86,7 +86,7 @@ export interface _envoy_admin_v3_ListenersConfigDump_DynamicListener__Output { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status': (_envoy_admin_v3_ClientResourceStatus__Output); } export interface _envoy_admin_v3_ListenersConfigDump_DynamicListenerState { diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts index 7b9bb29d0..6de43f0eb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/RoutesConfigDump.ts @@ -3,7 +3,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus, ClientResourceStatus__Output as _envoy_admin_v3_ClientResourceStatus__Output } from '../../../envoy/admin/v3/ClientResourceStatus'; /** * [#next-free-field: 6] @@ -35,7 +35,7 @@ export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus); } /** @@ -68,7 +68,7 @@ export interface _envoy_admin_v3_RoutesConfigDump_DynamicRouteConfig__Output { * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status': (_envoy_admin_v3_ClientResourceStatus__Output); } export interface _envoy_admin_v3_RoutesConfigDump_StaticRouteConfig { diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts index c0723ce69..1ce3934cc 100644 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts +++ b/packages/grpc-js-xds/src/generated/envoy/admin/v3/ScopedRoutesConfigDump.ts @@ -3,7 +3,7 @@ import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus, ClientResourceStatus__Output as _envoy_admin_v3_ClientResourceStatus__Output } from '../../../envoy/admin/v3/ClientResourceStatus'; /** * [#next-free-field: 7] @@ -39,7 +39,7 @@ export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfig * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus); } /** @@ -76,7 +76,7 @@ export interface _envoy_admin_v3_ScopedRoutesConfigDump_DynamicScopedRouteConfig * The client status of this resource. * [#not-implemented-hide:] */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status': (_envoy_admin_v3_ClientResourceStatus__Output); } export interface _envoy_admin_v3_ScopedRoutesConfigDump_InlineScopedRouteConfigs { diff --git a/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts b/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts deleted file mode 100644 index 21921edec..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/admin/v3/SecretsConfigDump.ts +++ /dev/null @@ -1,162 +0,0 @@ -// Original file: deps/envoy-api/envoy/admin/v3/config_dump.proto - -import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../google/protobuf/Timestamp'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../google/protobuf/Any'; -import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../envoy/admin/v3/UpdateFailureState'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../envoy/admin/v3/ClientResourceStatus'; - -/** - * DynamicSecret contains secret information fetched via SDS. - * [#next-free-field: 7] - */ -export interface _envoy_admin_v3_SecretsConfigDump_DynamicSecret { - /** - * The name assigned to the secret. - */ - 'name'?: (string); - /** - * This is the per-resource version information. - */ - 'version_info'?: (string); - /** - * The timestamp when the secret was last updated. - */ - 'last_updated'?: (_google_protobuf_Timestamp | null); - /** - * The actual secret information. - * Security sensitive information is redacted (replaced with "[redacted]") for - * private keys and passwords in TLS certificates. - */ - 'secret'?: (_google_protobuf_Any | null); - /** - * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular - * resource along with the reason and timestamp. For successfully updated or - * acknowledged resource, this field should be empty. - * [#not-implemented-hide:] - */ - 'error_state'?: (_envoy_admin_v3_UpdateFailureState | null); - /** - * The client status of this resource. - * [#not-implemented-hide:] - */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); -} - -/** - * DynamicSecret contains secret information fetched via SDS. - * [#next-free-field: 7] - */ -export interface _envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output { - /** - * The name assigned to the secret. - */ - 'name': (string); - /** - * This is the per-resource version information. - */ - 'version_info': (string); - /** - * The timestamp when the secret was last updated. - */ - 'last_updated': (_google_protobuf_Timestamp__Output | null); - /** - * The actual secret information. - * Security sensitive information is redacted (replaced with "[redacted]") for - * private keys and passwords in TLS certificates. - */ - 'secret': (_google_protobuf_Any__Output | null); - /** - * Set if the last update failed, cleared after the next successful update. - * The *error_state* field contains the rejected version of this particular - * resource along with the reason and timestamp. For successfully updated or - * acknowledged resource, this field should be empty. - * [#not-implemented-hide:] - */ - 'error_state': (_envoy_admin_v3_UpdateFailureState__Output | null); - /** - * The client status of this resource. - * [#not-implemented-hide:] - */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); -} - -/** - * StaticSecret specifies statically loaded secret in bootstrap. - */ -export interface _envoy_admin_v3_SecretsConfigDump_StaticSecret { - /** - * The name assigned to the secret. - */ - 'name'?: (string); - /** - * The timestamp when the secret was last updated. - */ - 'last_updated'?: (_google_protobuf_Timestamp | null); - /** - * The actual secret information. - * Security sensitive information is redacted (replaced with "[redacted]") for - * private keys and passwords in TLS certificates. - */ - 'secret'?: (_google_protobuf_Any | null); -} - -/** - * StaticSecret specifies statically loaded secret in bootstrap. - */ -export interface _envoy_admin_v3_SecretsConfigDump_StaticSecret__Output { - /** - * The name assigned to the secret. - */ - 'name': (string); - /** - * The timestamp when the secret was last updated. - */ - 'last_updated': (_google_protobuf_Timestamp__Output | null); - /** - * The actual secret information. - * Security sensitive information is redacted (replaced with "[redacted]") for - * private keys and passwords in TLS certificates. - */ - 'secret': (_google_protobuf_Any__Output | null); -} - -/** - * Envoys SDS implementation fills this message with all secrets fetched dynamically via SDS. - */ -export interface SecretsConfigDump { - /** - * The statically loaded secrets. - */ - 'static_secrets'?: (_envoy_admin_v3_SecretsConfigDump_StaticSecret)[]; - /** - * The dynamically loaded active secrets. These are secrets that are available to service - * clusters or listeners. - */ - 'dynamic_active_secrets'?: (_envoy_admin_v3_SecretsConfigDump_DynamicSecret)[]; - /** - * The dynamically loaded warming secrets. These are secrets that are currently undergoing - * warming in preparation to service clusters or listeners. - */ - 'dynamic_warming_secrets'?: (_envoy_admin_v3_SecretsConfigDump_DynamicSecret)[]; -} - -/** - * Envoys SDS implementation fills this message with all secrets fetched dynamically via SDS. - */ -export interface SecretsConfigDump__Output { - /** - * The statically loaded secrets. - */ - 'static_secrets': (_envoy_admin_v3_SecretsConfigDump_StaticSecret__Output)[]; - /** - * The dynamically loaded active secrets. These are secrets that are available to service - * clusters or listeners. - */ - 'dynamic_active_secrets': (_envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output)[]; - /** - * The dynamically loaded warming secrets. These are secrets that are currently undergoing - * warming in preparation to service clusters or listeners. - */ - 'dynamic_warming_secrets': (_envoy_admin_v3_SecretsConfigDump_DynamicSecret__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts index 07893f2dd..68c8f1b65 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/ComparisonFilter.ts @@ -4,20 +4,39 @@ import type { RuntimeUInt32 as _envoy_config_core_v3_RuntimeUInt32, RuntimeUInt3 // Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -export enum _envoy_config_accesslog_v3_ComparisonFilter_Op { +export const _envoy_config_accesslog_v3_ComparisonFilter_Op = { /** * = */ - EQ = 0, + EQ: 'EQ', /** * >= */ - GE = 1, + GE: 'GE', /** * <= */ - LE = 2, -} + LE: 'LE', +} as const; + +export type _envoy_config_accesslog_v3_ComparisonFilter_Op = + /** + * = + */ + | 'EQ' + | 0 + /** + * >= + */ + | 'GE' + | 1 + /** + * <= + */ + | 'LE' + | 2 + +export type _envoy_config_accesslog_v3_ComparisonFilter_Op__Output = typeof _envoy_config_accesslog_v3_ComparisonFilter_Op[keyof typeof _envoy_config_accesslog_v3_ComparisonFilter_Op] /** * Filter on an integer comparison. @@ -26,7 +45,7 @@ export interface ComparisonFilter { /** * Comparison operator. */ - 'op'?: (_envoy_config_accesslog_v3_ComparisonFilter_Op | keyof typeof _envoy_config_accesslog_v3_ComparisonFilter_Op); + 'op'?: (_envoy_config_accesslog_v3_ComparisonFilter_Op); /** * Value to compare against. */ @@ -40,7 +59,7 @@ export interface ComparisonFilter__Output { /** * Comparison operator. */ - 'op': (keyof typeof _envoy_config_accesslog_v3_ComparisonFilter_Op); + 'op': (_envoy_config_accesslog_v3_ComparisonFilter_Op__Output); /** * Value to compare against. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts index f7ccc8052..ec18bde8a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/GrpcStatusFilter.ts @@ -3,25 +3,63 @@ // Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -export enum _envoy_config_accesslog_v3_GrpcStatusFilter_Status { - OK = 0, - CANCELED = 1, - UNKNOWN = 2, - INVALID_ARGUMENT = 3, - DEADLINE_EXCEEDED = 4, - NOT_FOUND = 5, - ALREADY_EXISTS = 6, - PERMISSION_DENIED = 7, - RESOURCE_EXHAUSTED = 8, - FAILED_PRECONDITION = 9, - ABORTED = 10, - OUT_OF_RANGE = 11, - UNIMPLEMENTED = 12, - INTERNAL = 13, - UNAVAILABLE = 14, - DATA_LOSS = 15, - UNAUTHENTICATED = 16, -} +export const _envoy_config_accesslog_v3_GrpcStatusFilter_Status = { + OK: 'OK', + CANCELED: 'CANCELED', + UNKNOWN: 'UNKNOWN', + INVALID_ARGUMENT: 'INVALID_ARGUMENT', + DEADLINE_EXCEEDED: 'DEADLINE_EXCEEDED', + NOT_FOUND: 'NOT_FOUND', + ALREADY_EXISTS: 'ALREADY_EXISTS', + PERMISSION_DENIED: 'PERMISSION_DENIED', + RESOURCE_EXHAUSTED: 'RESOURCE_EXHAUSTED', + FAILED_PRECONDITION: 'FAILED_PRECONDITION', + ABORTED: 'ABORTED', + OUT_OF_RANGE: 'OUT_OF_RANGE', + UNIMPLEMENTED: 'UNIMPLEMENTED', + INTERNAL: 'INTERNAL', + UNAVAILABLE: 'UNAVAILABLE', + DATA_LOSS: 'DATA_LOSS', + UNAUTHENTICATED: 'UNAUTHENTICATED', +} as const; + +export type _envoy_config_accesslog_v3_GrpcStatusFilter_Status = + | 'OK' + | 0 + | 'CANCELED' + | 1 + | 'UNKNOWN' + | 2 + | 'INVALID_ARGUMENT' + | 3 + | 'DEADLINE_EXCEEDED' + | 4 + | 'NOT_FOUND' + | 5 + | 'ALREADY_EXISTS' + | 6 + | 'PERMISSION_DENIED' + | 7 + | 'RESOURCE_EXHAUSTED' + | 8 + | 'FAILED_PRECONDITION' + | 9 + | 'ABORTED' + | 10 + | 'OUT_OF_RANGE' + | 11 + | 'UNIMPLEMENTED' + | 12 + | 'INTERNAL' + | 13 + | 'UNAVAILABLE' + | 14 + | 'DATA_LOSS' + | 15 + | 'UNAUTHENTICATED' + | 16 + +export type _envoy_config_accesslog_v3_GrpcStatusFilter_Status__Output = typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status[keyof typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status] /** * Filters gRPC requests based on their response status. If a gRPC status is not @@ -31,7 +69,7 @@ export interface GrpcStatusFilter { /** * Logs only responses that have any one of the gRPC statuses in this field. */ - 'statuses'?: (_envoy_config_accesslog_v3_GrpcStatusFilter_Status | keyof typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status)[]; + 'statuses'?: (_envoy_config_accesslog_v3_GrpcStatusFilter_Status)[]; /** * If included and set to true, the filter will instead block all responses * with a gRPC status or inferred gRPC status enumerated in statuses, and @@ -48,7 +86,7 @@ export interface GrpcStatusFilter__Output { /** * Logs only responses that have any one of the gRPC statuses in this field. */ - 'statuses': (keyof typeof _envoy_config_accesslog_v3_GrpcStatusFilter_Status)[]; + 'statuses': (_envoy_config_accesslog_v3_GrpcStatusFilter_Status__Output)[]; /** * If included and set to true, the filter will instead block all responses * with a gRPC status or inferred gRPC status enumerated in statuses, and diff --git a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts index 8d51cd33f..59ad8e308 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/accesslog/v3/LogTypeFilter.ts @@ -1,6 +1,6 @@ // Original file: deps/envoy-api/envoy/config/accesslog/v3/accesslog.proto -import type { AccessLogType as _envoy_data_accesslog_v3_AccessLogType } from '../../../../envoy/data/accesslog/v3/AccessLogType'; +import type { AccessLogType as _envoy_data_accesslog_v3_AccessLogType, AccessLogType__Output as _envoy_data_accesslog_v3_AccessLogType__Output } from '../../../../envoy/data/accesslog/v3/AccessLogType'; /** * Filters based on access log type. @@ -9,7 +9,7 @@ export interface LogTypeFilter { /** * Logs only records which their type is one of the types defined in this field. */ - 'types'?: (_envoy_data_accesslog_v3_AccessLogType | keyof typeof _envoy_data_accesslog_v3_AccessLogType)[]; + 'types'?: (_envoy_data_accesslog_v3_AccessLogType)[]; /** * If this field is set to true, the filter will instead block all records * with a access log type in types field, and allow all other records. @@ -24,7 +24,7 @@ export interface LogTypeFilter__Output { /** * Logs only records which their type is one of the types defined in this field. */ - 'types': (keyof typeof _envoy_data_accesslog_v3_AccessLogType)[]; + 'types': (_envoy_data_accesslog_v3_AccessLogType__Output)[]; /** * If this field is set to true, the filter will instead block all records * with a access log type in types field, and allow all other records. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts deleted file mode 100644 index a7f3826a1..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Admin.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; -import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; -import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; - -/** - * Administration interface :ref:`operations documentation - * `. - * [#next-free-field: 6] - */ -export interface Admin { - /** - * The path to write the access log for the administration server. If no - * access log is desired specify ‘/dev/null’. This is only required if - * :ref:`address ` is set. - * Deprecated in favor of *access_log* which offers more options. - */ - 'access_log_path'?: (string); - /** - * The cpu profiler output path for the administration server. If no profile - * path is specified, the default is ‘/var/log/envoy/envoy.prof’. - */ - 'profile_path'?: (string); - /** - * The TCP address that the administration server will listen on. - * If not specified, Envoy will not start an administration server. - */ - 'address'?: (_envoy_config_core_v3_Address | null); - /** - * Additional socket options that may not be present in Envoy source code or - * precompiled binaries. - */ - 'socket_options'?: (_envoy_config_core_v3_SocketOption)[]; - /** - * Configuration for :ref:`access logs ` - * emitted by the administration server. - */ - 'access_log'?: (_envoy_config_accesslog_v3_AccessLog)[]; -} - -/** - * Administration interface :ref:`operations documentation - * `. - * [#next-free-field: 6] - */ -export interface Admin__Output { - /** - * The path to write the access log for the administration server. If no - * access log is desired specify ‘/dev/null’. This is only required if - * :ref:`address ` is set. - * Deprecated in favor of *access_log* which offers more options. - */ - 'access_log_path': (string); - /** - * The cpu profiler output path for the administration server. If no profile - * path is specified, the default is ‘/var/log/envoy/envoy.prof’. - */ - 'profile_path': (string); - /** - * The TCP address that the administration server will listen on. - * If not specified, Envoy will not start an administration server. - */ - 'address': (_envoy_config_core_v3_Address__Output | null); - /** - * Additional socket options that may not be present in Envoy source code or - * precompiled binaries. - */ - 'socket_options': (_envoy_config_core_v3_SocketOption__Output)[]; - /** - * Configuration for :ref:`access logs ` - * emitted by the administration server. - */ - 'access_log': (_envoy_config_accesslog_v3_AccessLog__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts deleted file mode 100644 index 797148675..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Bootstrap.ts +++ /dev/null @@ -1,642 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_core_v3_Node__Output } from '../../../../envoy/config/core/v3/Node'; -import type { ClusterManager as _envoy_config_bootstrap_v3_ClusterManager, ClusterManager__Output as _envoy_config_bootstrap_v3_ClusterManager__Output } from '../../../../envoy/config/bootstrap/v3/ClusterManager'; -import type { StatsSink as _envoy_config_metrics_v3_StatsSink, StatsSink__Output as _envoy_config_metrics_v3_StatsSink__Output } from '../../../../envoy/config/metrics/v3/StatsSink'; -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { Watchdog as _envoy_config_bootstrap_v3_Watchdog, Watchdog__Output as _envoy_config_bootstrap_v3_Watchdog__Output } from '../../../../envoy/config/bootstrap/v3/Watchdog'; -import type { Tracing as _envoy_config_trace_v3_Tracing, Tracing__Output as _envoy_config_trace_v3_Tracing__Output } from '../../../../envoy/config/trace/v3/Tracing'; -import type { Admin as _envoy_config_bootstrap_v3_Admin, Admin__Output as _envoy_config_bootstrap_v3_Admin__Output } from '../../../../envoy/config/bootstrap/v3/Admin'; -import type { StatsConfig as _envoy_config_metrics_v3_StatsConfig, StatsConfig__Output as _envoy_config_metrics_v3_StatsConfig__Output } from '../../../../envoy/config/metrics/v3/StatsConfig'; -import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; -import type { OverloadManager as _envoy_config_overload_v3_OverloadManager, OverloadManager__Output as _envoy_config_overload_v3_OverloadManager__Output } from '../../../../envoy/config/overload/v3/OverloadManager'; -import type { LayeredRuntime as _envoy_config_bootstrap_v3_LayeredRuntime, LayeredRuntime__Output as _envoy_config_bootstrap_v3_LayeredRuntime__Output } from '../../../../envoy/config/bootstrap/v3/LayeredRuntime'; -import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; -import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; -import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; -import type { Watchdogs as _envoy_config_bootstrap_v3_Watchdogs, Watchdogs__Output as _envoy_config_bootstrap_v3_Watchdogs__Output } from '../../../../envoy/config/bootstrap/v3/Watchdogs'; -import type { FatalAction as _envoy_config_bootstrap_v3_FatalAction, FatalAction__Output as _envoy_config_bootstrap_v3_FatalAction__Output } from '../../../../envoy/config/bootstrap/v3/FatalAction'; -import type { DnsResolutionConfig as _envoy_config_core_v3_DnsResolutionConfig, DnsResolutionConfig__Output as _envoy_config_core_v3_DnsResolutionConfig__Output } from '../../../../envoy/config/core/v3/DnsResolutionConfig'; -import type { CustomInlineHeader as _envoy_config_bootstrap_v3_CustomInlineHeader, CustomInlineHeader__Output as _envoy_config_bootstrap_v3_CustomInlineHeader__Output } from '../../../../envoy/config/bootstrap/v3/CustomInlineHeader'; -import type { Listener as _envoy_config_listener_v3_Listener, Listener__Output as _envoy_config_listener_v3_Listener__Output } from '../../../../envoy/config/listener/v3/Listener'; -import type { Cluster as _envoy_config_cluster_v3_Cluster, Cluster__Output as _envoy_config_cluster_v3_Cluster__Output } from '../../../../envoy/config/cluster/v3/Cluster'; -import type { Secret as _envoy_extensions_transport_sockets_tls_v3_Secret, Secret__Output as _envoy_extensions_transport_sockets_tls_v3_Secret__Output } from '../../../../envoy/extensions/transport_sockets/tls/v3/Secret'; -import type { Long } from '@grpc/proto-loader'; - -/** - * [#next-free-field: 7] - */ -export interface _envoy_config_bootstrap_v3_Bootstrap_DynamicResources { - /** - * All :ref:`Listeners ` are provided by a single - * :ref:`LDS ` configuration source. - */ - 'lds_config'?: (_envoy_config_core_v3_ConfigSource | null); - /** - * xdstp:// resource locator for listener collection. - * [#not-implemented-hide:] - */ - 'lds_resources_locator'?: (string); - /** - * All post-bootstrap :ref:`Cluster ` definitions are - * provided by a single :ref:`CDS ` - * configuration source. - */ - 'cds_config'?: (_envoy_config_core_v3_ConfigSource | null); - /** - * xdstp:// resource locator for cluster collection. - * [#not-implemented-hide:] - */ - 'cds_resources_locator'?: (string); - /** - * A single :ref:`ADS ` source may be optionally - * specified. This must have :ref:`api_type - * ` :ref:`GRPC - * `. Only - * :ref:`ConfigSources ` that have - * the :ref:`ads ` field set will be - * streamed on the ADS channel. - */ - 'ads_config'?: (_envoy_config_core_v3_ApiConfigSource | null); -} - -/** - * [#next-free-field: 7] - */ -export interface _envoy_config_bootstrap_v3_Bootstrap_DynamicResources__Output { - /** - * All :ref:`Listeners ` are provided by a single - * :ref:`LDS ` configuration source. - */ - 'lds_config': (_envoy_config_core_v3_ConfigSource__Output | null); - /** - * xdstp:// resource locator for listener collection. - * [#not-implemented-hide:] - */ - 'lds_resources_locator': (string); - /** - * All post-bootstrap :ref:`Cluster ` definitions are - * provided by a single :ref:`CDS ` - * configuration source. - */ - 'cds_config': (_envoy_config_core_v3_ConfigSource__Output | null); - /** - * xdstp:// resource locator for cluster collection. - * [#not-implemented-hide:] - */ - 'cds_resources_locator': (string); - /** - * A single :ref:`ADS ` source may be optionally - * specified. This must have :ref:`api_type - * ` :ref:`GRPC - * `. Only - * :ref:`ConfigSources ` that have - * the :ref:`ads ` field set will be - * streamed on the ADS channel. - */ - 'ads_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); -} - -export interface _envoy_config_bootstrap_v3_Bootstrap_StaticResources { - /** - * Static :ref:`Listeners `. These listeners are - * available regardless of LDS configuration. - */ - 'listeners'?: (_envoy_config_listener_v3_Listener)[]; - /** - * If a network based configuration source is specified for :ref:`cds_config - * `, it's necessary - * to have some initial cluster definitions available to allow Envoy to know - * how to speak to the management server. These cluster definitions may not - * use :ref:`EDS ` (i.e. they should be static - * IP or DNS-based). - */ - 'clusters'?: (_envoy_config_cluster_v3_Cluster)[]; - /** - * These static secrets can be used by :ref:`SdsSecretConfig - * ` - */ - 'secrets'?: (_envoy_extensions_transport_sockets_tls_v3_Secret)[]; -} - -export interface _envoy_config_bootstrap_v3_Bootstrap_StaticResources__Output { - /** - * Static :ref:`Listeners `. These listeners are - * available regardless of LDS configuration. - */ - 'listeners': (_envoy_config_listener_v3_Listener__Output)[]; - /** - * If a network based configuration source is specified for :ref:`cds_config - * `, it's necessary - * to have some initial cluster definitions available to allow Envoy to know - * how to speak to the management server. These cluster definitions may not - * use :ref:`EDS ` (i.e. they should be static - * IP or DNS-based). - */ - 'clusters': (_envoy_config_cluster_v3_Cluster__Output)[]; - /** - * These static secrets can be used by :ref:`SdsSecretConfig - * ` - */ - 'secrets': (_envoy_extensions_transport_sockets_tls_v3_Secret__Output)[]; -} - -/** - * Bootstrap :ref:`configuration overview `. - * [#next-free-field: 33] - */ -export interface Bootstrap { - /** - * Node identity to present to the management server and for instance - * identification purposes (e.g. in generated headers). - */ - 'node'?: (_envoy_config_core_v3_Node | null); - /** - * Statically specified resources. - */ - 'static_resources'?: (_envoy_config_bootstrap_v3_Bootstrap_StaticResources | null); - /** - * xDS configuration sources. - */ - 'dynamic_resources'?: (_envoy_config_bootstrap_v3_Bootstrap_DynamicResources | null); - /** - * Configuration for the cluster manager which owns all upstream clusters - * within the server. - */ - 'cluster_manager'?: (_envoy_config_bootstrap_v3_ClusterManager | null); - /** - * Optional file system path to search for startup flag files. - */ - 'flags_path'?: (string); - /** - * Optional set of stats sinks. - */ - 'stats_sinks'?: (_envoy_config_metrics_v3_StatsSink)[]; - /** - * Optional duration between flushes to configured stats sinks. For - * performance reasons Envoy latches counters and only flushes counters and - * gauges at a periodic interval. If not specified the default is 5000ms (5 - * seconds). Only one of `stats_flush_interval` or `stats_flush_on_admin` - * can be set. - * Duration must be at least 1ms and at most 5 min. - */ - 'stats_flush_interval'?: (_google_protobuf_Duration | null); - /** - * Optional watchdog configuration. - * This is for a single watchdog configuration for the entire system. - * Deprecated in favor of *watchdogs* which has finer granularity. - */ - 'watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); - /** - * Configuration for an external tracing provider. - * - * .. attention:: - * This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider - * `. - */ - 'tracing'?: (_envoy_config_trace_v3_Tracing | null); - /** - * Configuration for the local administration HTTP server. - */ - 'admin'?: (_envoy_config_bootstrap_v3_Admin | null); - /** - * Configuration for internal processing of stats. - */ - 'stats_config'?: (_envoy_config_metrics_v3_StatsConfig | null); - /** - * Health discovery service config option. - * (:ref:`core.ApiConfigSource `) - */ - 'hds_config'?: (_envoy_config_core_v3_ApiConfigSource | null); - /** - * Optional overload manager configuration. - */ - 'overload_manager'?: (_envoy_config_overload_v3_OverloadManager | null); - /** - * Enable :ref:`stats for event dispatcher `, defaults to false. - * Note that this records a value for each iteration of the event loop on every thread. This - * should normally be minimal overhead, but when using - * :ref:`statsd `, it will send each observed value - * over the wire individually because the statsd protocol doesn't have any way to represent a - * histogram summary. Be aware that this can be a very large volume of data. - */ - 'enable_dispatcher_stats'?: (boolean); - /** - * Configuration for the runtime configuration provider. If not - * specified, a “null” provider will be used which will result in all defaults - * being used. - */ - 'layered_runtime'?: (_envoy_config_bootstrap_v3_LayeredRuntime | null); - /** - * Optional string which will be used in lieu of x-envoy in prefixing headers. - * - * For example, if this string is present and set to X-Foo, then x-envoy-retry-on will be - * transformed into x-foo-retry-on etc. - * - * Note this applies to the headers Envoy will generate, the headers Envoy will sanitize, and the - * headers Envoy will trust for core code and core extensions only. Be VERY careful making - * changes to this string, especially in multi-layer Envoy deployments or deployments using - * extensions which are not upstream. - */ - 'header_prefix'?: (string); - /** - * Optional proxy version which will be used to set the value of :ref:`server.version statistic - * ` if specified. Envoy will not process this value, it will be sent as is to - * :ref:`stats sinks `. - */ - 'stats_server_version_override'?: (_google_protobuf_UInt64Value | null); - /** - * Always use TCP queries instead of UDP queries for DNS lookups. - * This may be overridden on a per-cluster basis in cds_config, - * when :ref:`dns_resolvers ` and - * :ref:`use_tcp_for_dns_lookups ` are - * specified. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple' API only uses UDP for DNS resolution. - * This field is deprecated in favor of *dns_resolution_config* - * which aggregates all of the DNS resolver configuration in a single message. - */ - 'use_tcp_for_dns_lookups'?: (boolean); - /** - * Specifies optional bootstrap extensions to be instantiated at startup time. - * Each item contains extension specific configuration. - * [#extension-category: envoy.bootstrap] - */ - 'bootstrap_extensions'?: (_envoy_config_core_v3_TypedExtensionConfig)[]; - /** - * Configuration sources that will participate in - * xdstp:// URL authority resolution. The algorithm is as - * follows: - * 1. The authority field is taken from the xdstp:// URL, call - * this *resource_authority*. - * 2. *resource_authority* is compared against the authorities in any peer - * *ConfigSource*. The peer *ConfigSource* is the configuration source - * message which would have been used unconditionally for resolution - * with opaque resource names. If there is a match with an authority, the - * peer *ConfigSource* message is used. - * 3. *resource_authority* is compared sequentially with the authorities in - * each configuration source in *config_sources*. The first *ConfigSource* - * to match wins. - * 4. As a fallback, if no configuration source matches, then - * *default_config_source* is used. - * 5. If *default_config_source* is not specified, resolution fails. - * [#not-implemented-hide:] - */ - 'config_sources'?: (_envoy_config_core_v3_ConfigSource)[]; - /** - * Default configuration source for xdstp:// URLs if all - * other resolution fails. - * [#not-implemented-hide:] - */ - 'default_config_source'?: (_envoy_config_core_v3_ConfigSource | null); - /** - * Optional overriding of default socket interface. The value must be the name of one of the - * socket interface factories initialized through a bootstrap extension - */ - 'default_socket_interface'?: (string); - /** - * Global map of CertificateProvider instances. These instances are referred to by name in the - * :ref:`CommonTlsContext.CertificateProviderInstance.instance_name - * ` - * field. - * [#not-implemented-hide:] - */ - 'certificate_provider_instances'?: ({[key: string]: _envoy_config_core_v3_TypedExtensionConfig}); - /** - * A list of :ref:`Node ` field names - * that will be included in the context parameters of the effective - * xdstp:// URL that is sent in a discovery request when resource - * locators are used for LDS/CDS. Any non-string field will have its JSON - * encoding set as the context parameter value, with the exception of - * metadata, which will be flattened (see example below). The supported field - * names are: - * - "cluster" - * - "id" - * - "locality.region" - * - "locality.sub_zone" - * - "locality.zone" - * - "metadata" - * - "user_agent_build_version.metadata" - * - "user_agent_build_version.version" - * - "user_agent_name" - * - "user_agent_version" - * - * The node context parameters act as a base layer dictionary for the context - * parameters (i.e. more specific resource specific context parameters will - * override). Field names will be prefixed with “udpa.node.” when included in - * context parameters. - * - * For example, if node_context_params is ``["user_agent_name", "metadata"]``, - * the implied context parameters might be:: - * - * node.user_agent_name: "envoy" - * node.metadata.foo: "{\"bar\": \"baz\"}" - * node.metadata.some: "42" - * node.metadata.thing: "\"thing\"" - * - * [#not-implemented-hide:] - */ - 'node_context_params'?: (string)[]; - /** - * Optional watchdogs configuration. - * This is used for specifying different watchdogs for the different subsystems. - * [#extension-category: envoy.guarddog_actions] - */ - 'watchdogs'?: (_envoy_config_bootstrap_v3_Watchdogs | null); - /** - * Specifies optional extensions instantiated at startup time and - * invoked during crash time on the request that caused the crash. - */ - 'fatal_actions'?: (_envoy_config_bootstrap_v3_FatalAction)[]; - /** - * Flush stats to sinks only when queried for on the admin interface. If set, - * a flush timer is not created. Only one of `stats_flush_on_admin` or - * `stats_flush_interval` can be set. - */ - 'stats_flush_on_admin'?: (boolean); - /** - * DNS resolution configuration which includes the underlying dns resolver addresses and options. - * This may be overridden on a per-cluster basis in cds_config, when - * :ref:`dns_resolution_config ` - * is specified. - * *dns_resolution_config* will be deprecated once - * :ref:'typed_dns_resolver_config ' - * is fully supported. - */ - 'dns_resolution_config'?: (_envoy_config_core_v3_DnsResolutionConfig | null); - /** - * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, - * or any other DNS resolver types and the related parameters. - * For example, an object of :ref:`DnsResolutionConfig ` - * can be packed into this *typed_dns_resolver_config*. This configuration will replace the - * :ref:'dns_resolution_config ' - * configuration eventually. - * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. - * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, - * this configuration is optional. - * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. - * When *typed_dns_resolver_config* is missing, the default behavior is in place. - * [#not-implemented-hide:] - */ - 'typed_dns_resolver_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); - /** - * Specifies a set of headers that need to be registered as inline header. This configuration - * allows users to customize the inline headers on-demand at Envoy startup without modifying - * Envoy's source code. - * - * Note that the 'set-cookie' header cannot be registered as inline header. - */ - 'inline_headers'?: (_envoy_config_bootstrap_v3_CustomInlineHeader)[]; - 'stats_flush'?: "stats_flush_on_admin"; -} - -/** - * Bootstrap :ref:`configuration overview `. - * [#next-free-field: 33] - */ -export interface Bootstrap__Output { - /** - * Node identity to present to the management server and for instance - * identification purposes (e.g. in generated headers). - */ - 'node': (_envoy_config_core_v3_Node__Output | null); - /** - * Statically specified resources. - */ - 'static_resources': (_envoy_config_bootstrap_v3_Bootstrap_StaticResources__Output | null); - /** - * xDS configuration sources. - */ - 'dynamic_resources': (_envoy_config_bootstrap_v3_Bootstrap_DynamicResources__Output | null); - /** - * Configuration for the cluster manager which owns all upstream clusters - * within the server. - */ - 'cluster_manager': (_envoy_config_bootstrap_v3_ClusterManager__Output | null); - /** - * Optional file system path to search for startup flag files. - */ - 'flags_path': (string); - /** - * Optional set of stats sinks. - */ - 'stats_sinks': (_envoy_config_metrics_v3_StatsSink__Output)[]; - /** - * Optional duration between flushes to configured stats sinks. For - * performance reasons Envoy latches counters and only flushes counters and - * gauges at a periodic interval. If not specified the default is 5000ms (5 - * seconds). Only one of `stats_flush_interval` or `stats_flush_on_admin` - * can be set. - * Duration must be at least 1ms and at most 5 min. - */ - 'stats_flush_interval': (_google_protobuf_Duration__Output | null); - /** - * Optional watchdog configuration. - * This is for a single watchdog configuration for the entire system. - * Deprecated in favor of *watchdogs* which has finer granularity. - */ - 'watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); - /** - * Configuration for an external tracing provider. - * - * .. attention:: - * This field has been deprecated in favor of :ref:`HttpConnectionManager.Tracing.provider - * `. - */ - 'tracing': (_envoy_config_trace_v3_Tracing__Output | null); - /** - * Configuration for the local administration HTTP server. - */ - 'admin': (_envoy_config_bootstrap_v3_Admin__Output | null); - /** - * Configuration for internal processing of stats. - */ - 'stats_config': (_envoy_config_metrics_v3_StatsConfig__Output | null); - /** - * Health discovery service config option. - * (:ref:`core.ApiConfigSource `) - */ - 'hds_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); - /** - * Optional overload manager configuration. - */ - 'overload_manager': (_envoy_config_overload_v3_OverloadManager__Output | null); - /** - * Enable :ref:`stats for event dispatcher `, defaults to false. - * Note that this records a value for each iteration of the event loop on every thread. This - * should normally be minimal overhead, but when using - * :ref:`statsd `, it will send each observed value - * over the wire individually because the statsd protocol doesn't have any way to represent a - * histogram summary. Be aware that this can be a very large volume of data. - */ - 'enable_dispatcher_stats': (boolean); - /** - * Configuration for the runtime configuration provider. If not - * specified, a “null” provider will be used which will result in all defaults - * being used. - */ - 'layered_runtime': (_envoy_config_bootstrap_v3_LayeredRuntime__Output | null); - /** - * Optional string which will be used in lieu of x-envoy in prefixing headers. - * - * For example, if this string is present and set to X-Foo, then x-envoy-retry-on will be - * transformed into x-foo-retry-on etc. - * - * Note this applies to the headers Envoy will generate, the headers Envoy will sanitize, and the - * headers Envoy will trust for core code and core extensions only. Be VERY careful making - * changes to this string, especially in multi-layer Envoy deployments or deployments using - * extensions which are not upstream. - */ - 'header_prefix': (string); - /** - * Optional proxy version which will be used to set the value of :ref:`server.version statistic - * ` if specified. Envoy will not process this value, it will be sent as is to - * :ref:`stats sinks `. - */ - 'stats_server_version_override': (_google_protobuf_UInt64Value__Output | null); - /** - * Always use TCP queries instead of UDP queries for DNS lookups. - * This may be overridden on a per-cluster basis in cds_config, - * when :ref:`dns_resolvers ` and - * :ref:`use_tcp_for_dns_lookups ` are - * specified. - * Setting this value causes failure if the - * ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during - * server startup. Apple' API only uses UDP for DNS resolution. - * This field is deprecated in favor of *dns_resolution_config* - * which aggregates all of the DNS resolver configuration in a single message. - */ - 'use_tcp_for_dns_lookups': (boolean); - /** - * Specifies optional bootstrap extensions to be instantiated at startup time. - * Each item contains extension specific configuration. - * [#extension-category: envoy.bootstrap] - */ - 'bootstrap_extensions': (_envoy_config_core_v3_TypedExtensionConfig__Output)[]; - /** - * Configuration sources that will participate in - * xdstp:// URL authority resolution. The algorithm is as - * follows: - * 1. The authority field is taken from the xdstp:// URL, call - * this *resource_authority*. - * 2. *resource_authority* is compared against the authorities in any peer - * *ConfigSource*. The peer *ConfigSource* is the configuration source - * message which would have been used unconditionally for resolution - * with opaque resource names. If there is a match with an authority, the - * peer *ConfigSource* message is used. - * 3. *resource_authority* is compared sequentially with the authorities in - * each configuration source in *config_sources*. The first *ConfigSource* - * to match wins. - * 4. As a fallback, if no configuration source matches, then - * *default_config_source* is used. - * 5. If *default_config_source* is not specified, resolution fails. - * [#not-implemented-hide:] - */ - 'config_sources': (_envoy_config_core_v3_ConfigSource__Output)[]; - /** - * Default configuration source for xdstp:// URLs if all - * other resolution fails. - * [#not-implemented-hide:] - */ - 'default_config_source': (_envoy_config_core_v3_ConfigSource__Output | null); - /** - * Optional overriding of default socket interface. The value must be the name of one of the - * socket interface factories initialized through a bootstrap extension - */ - 'default_socket_interface': (string); - /** - * Global map of CertificateProvider instances. These instances are referred to by name in the - * :ref:`CommonTlsContext.CertificateProviderInstance.instance_name - * ` - * field. - * [#not-implemented-hide:] - */ - 'certificate_provider_instances': ({[key: string]: _envoy_config_core_v3_TypedExtensionConfig__Output}); - /** - * A list of :ref:`Node ` field names - * that will be included in the context parameters of the effective - * xdstp:// URL that is sent in a discovery request when resource - * locators are used for LDS/CDS. Any non-string field will have its JSON - * encoding set as the context parameter value, with the exception of - * metadata, which will be flattened (see example below). The supported field - * names are: - * - "cluster" - * - "id" - * - "locality.region" - * - "locality.sub_zone" - * - "locality.zone" - * - "metadata" - * - "user_agent_build_version.metadata" - * - "user_agent_build_version.version" - * - "user_agent_name" - * - "user_agent_version" - * - * The node context parameters act as a base layer dictionary for the context - * parameters (i.e. more specific resource specific context parameters will - * override). Field names will be prefixed with “udpa.node.” when included in - * context parameters. - * - * For example, if node_context_params is ``["user_agent_name", "metadata"]``, - * the implied context parameters might be:: - * - * node.user_agent_name: "envoy" - * node.metadata.foo: "{\"bar\": \"baz\"}" - * node.metadata.some: "42" - * node.metadata.thing: "\"thing\"" - * - * [#not-implemented-hide:] - */ - 'node_context_params': (string)[]; - /** - * Optional watchdogs configuration. - * This is used for specifying different watchdogs for the different subsystems. - * [#extension-category: envoy.guarddog_actions] - */ - 'watchdogs': (_envoy_config_bootstrap_v3_Watchdogs__Output | null); - /** - * Specifies optional extensions instantiated at startup time and - * invoked during crash time on the request that caused the crash. - */ - 'fatal_actions': (_envoy_config_bootstrap_v3_FatalAction__Output)[]; - /** - * Flush stats to sinks only when queried for on the admin interface. If set, - * a flush timer is not created. Only one of `stats_flush_on_admin` or - * `stats_flush_interval` can be set. - */ - 'stats_flush_on_admin'?: (boolean); - /** - * DNS resolution configuration which includes the underlying dns resolver addresses and options. - * This may be overridden on a per-cluster basis in cds_config, when - * :ref:`dns_resolution_config ` - * is specified. - * *dns_resolution_config* will be deprecated once - * :ref:'typed_dns_resolver_config ' - * is fully supported. - */ - 'dns_resolution_config': (_envoy_config_core_v3_DnsResolutionConfig__Output | null); - /** - * DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, - * or any other DNS resolver types and the related parameters. - * For example, an object of :ref:`DnsResolutionConfig ` - * can be packed into this *typed_dns_resolver_config*. This configuration will replace the - * :ref:'dns_resolution_config ' - * configuration eventually. - * TODO(yanjunxiang): Investigate the deprecation plan for *dns_resolution_config*. - * During the transition period when both *dns_resolution_config* and *typed_dns_resolver_config* exists, - * this configuration is optional. - * When *typed_dns_resolver_config* is in place, Envoy will use it and ignore *dns_resolution_config*. - * When *typed_dns_resolver_config* is missing, the default behavior is in place. - * [#not-implemented-hide:] - */ - 'typed_dns_resolver_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); - /** - * Specifies a set of headers that need to be registered as inline header. This configuration - * allows users to customize the inline headers on-demand at Envoy startup without modifying - * Envoy's source code. - * - * Note that the 'set-cookie' header cannot be registered as inline header. - */ - 'inline_headers': (_envoy_config_bootstrap_v3_CustomInlineHeader__Output)[]; - 'stats_flush': "stats_flush_on_admin"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts deleted file mode 100644 index 571b96fb7..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/ClusterManager.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { BindConfig as _envoy_config_core_v3_BindConfig, BindConfig__Output as _envoy_config_core_v3_BindConfig__Output } from '../../../../envoy/config/core/v3/BindConfig'; -import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfigSource__Output as _envoy_config_core_v3_ApiConfigSource__Output } from '../../../../envoy/config/core/v3/ApiConfigSource'; -import type { EventServiceConfig as _envoy_config_core_v3_EventServiceConfig, EventServiceConfig__Output as _envoy_config_core_v3_EventServiceConfig__Output } from '../../../../envoy/config/core/v3/EventServiceConfig'; - -export interface _envoy_config_bootstrap_v3_ClusterManager_OutlierDetection { - /** - * Specifies the path to the outlier event log. - */ - 'event_log_path'?: (string); - /** - * [#not-implemented-hide:] - * The gRPC service for the outlier detection event service. - * If empty, outlier detection events won't be sent to a remote endpoint. - */ - 'event_service'?: (_envoy_config_core_v3_EventServiceConfig | null); -} - -export interface _envoy_config_bootstrap_v3_ClusterManager_OutlierDetection__Output { - /** - * Specifies the path to the outlier event log. - */ - 'event_log_path': (string); - /** - * [#not-implemented-hide:] - * The gRPC service for the outlier detection event service. - * If empty, outlier detection events won't be sent to a remote endpoint. - */ - 'event_service': (_envoy_config_core_v3_EventServiceConfig__Output | null); -} - -/** - * Cluster manager :ref:`architecture overview `. - */ -export interface ClusterManager { - /** - * Name of the local cluster (i.e., the cluster that owns the Envoy running - * this configuration). In order to enable :ref:`zone aware routing - * ` this option must be set. - * If *local_cluster_name* is defined then :ref:`clusters - * ` must be defined in the :ref:`Bootstrap - * static cluster resources - * `. This is unrelated to - * the :option:`--service-cluster` option which does not `affect zone aware - * routing `_. - */ - 'local_cluster_name'?: (string); - /** - * Optional global configuration for outlier detection. - */ - 'outlier_detection'?: (_envoy_config_bootstrap_v3_ClusterManager_OutlierDetection | null); - /** - * Optional configuration used to bind newly established upstream connections. - * This may be overridden on a per-cluster basis by upstream_bind_config in the cds_config. - */ - 'upstream_bind_config'?: (_envoy_config_core_v3_BindConfig | null); - /** - * A management server endpoint to stream load stats to via - * *StreamLoadStats*. This must have :ref:`api_type - * ` :ref:`GRPC - * `. - */ - 'load_stats_config'?: (_envoy_config_core_v3_ApiConfigSource | null); -} - -/** - * Cluster manager :ref:`architecture overview `. - */ -export interface ClusterManager__Output { - /** - * Name of the local cluster (i.e., the cluster that owns the Envoy running - * this configuration). In order to enable :ref:`zone aware routing - * ` this option must be set. - * If *local_cluster_name* is defined then :ref:`clusters - * ` must be defined in the :ref:`Bootstrap - * static cluster resources - * `. This is unrelated to - * the :option:`--service-cluster` option which does not `affect zone aware - * routing `_. - */ - 'local_cluster_name': (string); - /** - * Optional global configuration for outlier detection. - */ - 'outlier_detection': (_envoy_config_bootstrap_v3_ClusterManager_OutlierDetection__Output | null); - /** - * Optional configuration used to bind newly established upstream connections. - * This may be overridden on a per-cluster basis by upstream_bind_config in the cds_config. - */ - 'upstream_bind_config': (_envoy_config_core_v3_BindConfig__Output | null); - /** - * A management server endpoint to stream load stats to via - * *StreamLoadStats*. This must have :ref:`api_type - * ` :ref:`GRPC - * `. - */ - 'load_stats_config': (_envoy_config_core_v3_ApiConfigSource__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts deleted file mode 100644 index f0e2d29aa..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/CustomInlineHeader.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - - -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -export enum _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType { - REQUEST_HEADER = 0, - REQUEST_TRAILER = 1, - RESPONSE_HEADER = 2, - RESPONSE_TRAILER = 3, -} - -/** - * Used to specify the header that needs to be registered as an inline header. - * - * If request or response contain multiple headers with the same name and the header - * name is registered as an inline header. Then multiple headers will be folded - * into one, and multiple header values will be concatenated by a suitable delimiter. - * The delimiter is generally a comma. - * - * For example, if 'foo' is registered as an inline header, and the headers contains - * the following two headers: - * - * .. code-block:: text - * - * foo: bar - * foo: eep - * - * Then they will eventually be folded into: - * - * .. code-block:: text - * - * foo: bar, eep - * - * Inline headers provide O(1) search performance, but each inline header imposes - * an additional memory overhead on all instances of the corresponding type of - * HeaderMap or TrailerMap. - */ -export interface CustomInlineHeader { - /** - * The name of the header that is expected to be set as the inline header. - */ - 'inline_header_name'?: (string); - /** - * The type of the header that is expected to be set as the inline header. - */ - 'inline_header_type'?: (_envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType | keyof typeof _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType); -} - -/** - * Used to specify the header that needs to be registered as an inline header. - * - * If request or response contain multiple headers with the same name and the header - * name is registered as an inline header. Then multiple headers will be folded - * into one, and multiple header values will be concatenated by a suitable delimiter. - * The delimiter is generally a comma. - * - * For example, if 'foo' is registered as an inline header, and the headers contains - * the following two headers: - * - * .. code-block:: text - * - * foo: bar - * foo: eep - * - * Then they will eventually be folded into: - * - * .. code-block:: text - * - * foo: bar, eep - * - * Inline headers provide O(1) search performance, but each inline header imposes - * an additional memory overhead on all instances of the corresponding type of - * HeaderMap or TrailerMap. - */ -export interface CustomInlineHeader__Output { - /** - * The name of the header that is expected to be set as the inline header. - */ - 'inline_header_name': (string); - /** - * The type of the header that is expected to be set as the inline header. - */ - 'inline_header_type': (keyof typeof _envoy_config_bootstrap_v3_CustomInlineHeader_InlineHeaderType); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts deleted file mode 100644 index 236afded5..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/FatalAction.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; - -/** - * Fatal actions to run while crashing. Actions can be safe (meaning they are - * async-signal safe) or unsafe. We run all safe actions before we run unsafe actions. - * If using an unsafe action that could get stuck or deadlock, it important to - * have an out of band system to terminate the process. - * - * The interface for the extension is ``Envoy::Server::Configuration::FatalAction``. - * *FatalAction* extensions live in the ``envoy.extensions.fatal_actions`` API - * namespace. - */ -export interface FatalAction { - /** - * Extension specific configuration for the action. It's expected to conform - * to the ``Envoy::Server::Configuration::FatalAction`` interface. - */ - 'config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); -} - -/** - * Fatal actions to run while crashing. Actions can be safe (meaning they are - * async-signal safe) or unsafe. We run all safe actions before we run unsafe actions. - * If using an unsafe action that could get stuck or deadlock, it important to - * have an out of band system to terminate the process. - * - * The interface for the extension is ``Envoy::Server::Configuration::FatalAction``. - * *FatalAction* extensions live in the ``envoy.extensions.fatal_actions`` API - * namespace. - */ -export interface FatalAction__Output { - /** - * Extension specific configuration for the action. It's expected to conform - * to the ``Envoy::Server::Configuration::FatalAction`` interface. - */ - 'config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts deleted file mode 100644 index 3514d3140..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/LayeredRuntime.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { RuntimeLayer as _envoy_config_bootstrap_v3_RuntimeLayer, RuntimeLayer__Output as _envoy_config_bootstrap_v3_RuntimeLayer__Output } from '../../../../envoy/config/bootstrap/v3/RuntimeLayer'; - -/** - * Runtime :ref:`configuration overview `. - */ -export interface LayeredRuntime { - /** - * The :ref:`layers ` of the runtime. This is ordered - * such that later layers in the list overlay earlier entries. - */ - 'layers'?: (_envoy_config_bootstrap_v3_RuntimeLayer)[]; -} - -/** - * Runtime :ref:`configuration overview `. - */ -export interface LayeredRuntime__Output { - /** - * The :ref:`layers ` of the runtime. This is ordered - * such that later layers in the list overlay earlier entries. - */ - 'layers': (_envoy_config_bootstrap_v3_RuntimeLayer__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts deleted file mode 100644 index 4f7713bcf..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Runtime.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; - -/** - * Runtime :ref:`configuration overview ` (deprecated). - */ -export interface Runtime { - /** - * The implementation assumes that the file system tree is accessed via a - * symbolic link. An atomic link swap is used when a new tree should be - * switched to. This parameter specifies the path to the symbolic link. Envoy - * will watch the location for changes and reload the file system tree when - * they happen. If this parameter is not set, there will be no disk based - * runtime. - */ - 'symlink_root'?: (string); - /** - * Specifies the subdirectory to load within the root directory. This is - * useful if multiple systems share the same delivery mechanism. Envoy - * configuration elements can be contained in a dedicated subdirectory. - */ - 'subdirectory'?: (string); - /** - * Specifies an optional subdirectory to load within the root directory. If - * specified and the directory exists, configuration values within this - * directory will override those found in the primary subdirectory. This is - * useful when Envoy is deployed across many different types of servers. - * Sometimes it is useful to have a per service cluster directory for runtime - * configuration. See below for exactly how the override directory is used. - */ - 'override_subdirectory'?: (string); - /** - * Static base runtime. This will be :ref:`overridden - * ` by other runtime layers, e.g. - * disk or admin. This follows the :ref:`runtime protobuf JSON representation - * encoding `. - */ - 'base'?: (_google_protobuf_Struct | null); -} - -/** - * Runtime :ref:`configuration overview ` (deprecated). - */ -export interface Runtime__Output { - /** - * The implementation assumes that the file system tree is accessed via a - * symbolic link. An atomic link swap is used when a new tree should be - * switched to. This parameter specifies the path to the symbolic link. Envoy - * will watch the location for changes and reload the file system tree when - * they happen. If this parameter is not set, there will be no disk based - * runtime. - */ - 'symlink_root': (string); - /** - * Specifies the subdirectory to load within the root directory. This is - * useful if multiple systems share the same delivery mechanism. Envoy - * configuration elements can be contained in a dedicated subdirectory. - */ - 'subdirectory': (string); - /** - * Specifies an optional subdirectory to load within the root directory. If - * specified and the directory exists, configuration values within this - * directory will override those found in the primary subdirectory. This is - * useful when Envoy is deployed across many different types of servers. - * Sometimes it is useful to have a per service cluster directory for runtime - * configuration. See below for exactly how the override directory is used. - */ - 'override_subdirectory': (string); - /** - * Static base runtime. This will be :ref:`overridden - * ` by other runtime layers, e.g. - * disk or admin. This follows the :ref:`runtime protobuf JSON representation - * encoding `. - */ - 'base': (_google_protobuf_Struct__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts deleted file mode 100644 index b072bfa7d..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/RuntimeLayer.ts +++ /dev/null @@ -1,142 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../../google/protobuf/Struct'; -import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../envoy/config/core/v3/ConfigSource'; - -/** - * :ref:`Admin console runtime ` layer. - */ -export interface _envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer { -} - -/** - * :ref:`Admin console runtime ` layer. - */ -export interface _envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer__Output { -} - -/** - * :ref:`Disk runtime ` layer. - */ -export interface _envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer { - /** - * The implementation assumes that the file system tree is accessed via a - * symbolic link. An atomic link swap is used when a new tree should be - * switched to. This parameter specifies the path to the symbolic link. - * Envoy will watch the location for changes and reload the file system tree - * when they happen. See documentation on runtime :ref:`atomicity - * ` for further details on how reloads are - * treated. - */ - 'symlink_root'?: (string); - /** - * Specifies the subdirectory to load within the root directory. This is - * useful if multiple systems share the same delivery mechanism. Envoy - * configuration elements can be contained in a dedicated subdirectory. - */ - 'subdirectory'?: (string); - /** - * :ref:`Append ` the - * service cluster to the path under symlink root. - */ - 'append_service_cluster'?: (boolean); -} - -/** - * :ref:`Disk runtime ` layer. - */ -export interface _envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer__Output { - /** - * The implementation assumes that the file system tree is accessed via a - * symbolic link. An atomic link swap is used when a new tree should be - * switched to. This parameter specifies the path to the symbolic link. - * Envoy will watch the location for changes and reload the file system tree - * when they happen. See documentation on runtime :ref:`atomicity - * ` for further details on how reloads are - * treated. - */ - 'symlink_root': (string); - /** - * Specifies the subdirectory to load within the root directory. This is - * useful if multiple systems share the same delivery mechanism. Envoy - * configuration elements can be contained in a dedicated subdirectory. - */ - 'subdirectory': (string); - /** - * :ref:`Append ` the - * service cluster to the path under symlink root. - */ - 'append_service_cluster': (boolean); -} - -/** - * :ref:`Runtime Discovery Service (RTDS) ` layer. - */ -export interface _envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer { - /** - * Resource to subscribe to at *rtds_config* for the RTDS layer. - */ - 'name'?: (string); - /** - * RTDS configuration source. - */ - 'rtds_config'?: (_envoy_config_core_v3_ConfigSource | null); -} - -/** - * :ref:`Runtime Discovery Service (RTDS) ` layer. - */ -export interface _envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer__Output { - /** - * Resource to subscribe to at *rtds_config* for the RTDS layer. - */ - 'name': (string); - /** - * RTDS configuration source. - */ - 'rtds_config': (_envoy_config_core_v3_ConfigSource__Output | null); -} - -/** - * [#next-free-field: 6] - */ -export interface RuntimeLayer { - /** - * Descriptive name for the runtime layer. This is only used for the runtime - * :http:get:`/runtime` output. - */ - 'name'?: (string); - /** - * :ref:`Static runtime ` layer. - * This follows the :ref:`runtime protobuf JSON representation encoding - * `. Unlike static xDS resources, this static - * layer is overridable by later layers in the runtime virtual filesystem. - */ - 'static_layer'?: (_google_protobuf_Struct | null); - 'disk_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer | null); - 'admin_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer | null); - 'rtds_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer | null); - 'layer_specifier'?: "static_layer"|"disk_layer"|"admin_layer"|"rtds_layer"; -} - -/** - * [#next-free-field: 6] - */ -export interface RuntimeLayer__Output { - /** - * Descriptive name for the runtime layer. This is only used for the runtime - * :http:get:`/runtime` output. - */ - 'name': (string); - /** - * :ref:`Static runtime ` layer. - * This follows the :ref:`runtime protobuf JSON representation encoding - * `. Unlike static xDS resources, this static - * layer is overridable by later layers in the runtime virtual filesystem. - */ - 'static_layer'?: (_google_protobuf_Struct__Output | null); - 'disk_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_DiskLayer__Output | null); - 'admin_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_AdminLayer__Output | null); - 'rtds_layer'?: (_envoy_config_bootstrap_v3_RuntimeLayer_RtdsLayer__Output | null); - 'layer_specifier': "static_layer"|"disk_layer"|"admin_layer"|"rtds_layer"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts deleted file mode 100644 index 8cd743b56..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdog.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; -import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; - -export interface _envoy_config_bootstrap_v3_Watchdog_WatchdogAction { - /** - * Extension specific configuration for the action. - */ - 'config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); - 'event'?: (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent | keyof typeof _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent); -} - -export interface _envoy_config_bootstrap_v3_Watchdog_WatchdogAction__Output { - /** - * Extension specific configuration for the action. - */ - 'config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); - 'event': (keyof typeof _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent); -} - -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -/** - * The events are fired in this order: KILL, MULTIKILL, MEGAMISS, MISS. - * Within an event type, actions execute in the order they are configured. - * For KILL/MULTIKILL there is a default PANIC that will run after the - * registered actions and kills the process if it wasn't already killed. - * It might be useful to specify several debug actions, and possibly an - * alternate FATAL action. - */ -export enum _envoy_config_bootstrap_v3_Watchdog_WatchdogAction_WatchdogEvent { - UNKNOWN = 0, - KILL = 1, - MULTIKILL = 2, - MEGAMISS = 3, - MISS = 4, -} - -/** - * Envoy process watchdog configuration. When configured, this monitors for - * nonresponsive threads and kills the process after the configured thresholds. - * See the :ref:`watchdog documentation ` for more information. - * [#next-free-field: 8] - */ -export interface Watchdog { - /** - * The duration after which Envoy counts a nonresponsive thread in the - * *watchdog_miss* statistic. If not specified the default is 200ms. - */ - 'miss_timeout'?: (_google_protobuf_Duration | null); - /** - * The duration after which Envoy counts a nonresponsive thread in the - * *watchdog_mega_miss* statistic. If not specified the default is - * 1000ms. - */ - 'megamiss_timeout'?: (_google_protobuf_Duration | null); - /** - * If a watched thread has been nonresponsive for this duration, assume a - * programming error and kill the entire Envoy process. Set to 0 to disable - * kill behavior. If not specified the default is 0 (disabled). - */ - 'kill_timeout'?: (_google_protobuf_Duration | null); - /** - * If max(2, ceil(registered_threads * Fraction(*multikill_threshold*))) - * threads have been nonresponsive for at least this duration kill the entire - * Envoy process. Set to 0 to disable this behavior. If not specified the - * default is 0 (disabled). - */ - 'multikill_timeout'?: (_google_protobuf_Duration | null); - /** - * Sets the threshold for *multikill_timeout* in terms of the percentage of - * nonresponsive threads required for the *multikill_timeout*. - * If not specified the default is 0. - */ - 'multikill_threshold'?: (_envoy_type_v3_Percent | null); - /** - * Defines the maximum jitter used to adjust the *kill_timeout* if *kill_timeout* is - * enabled. Enabling this feature would help to reduce risk of synchronized - * watchdog kill events across proxies due to external triggers. Set to 0 to - * disable. If not specified the default is 0 (disabled). - */ - 'max_kill_timeout_jitter'?: (_google_protobuf_Duration | null); - /** - * Register actions that will fire on given WatchDog events. - * See *WatchDogAction* for priority of events. - */ - 'actions'?: (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction)[]; -} - -/** - * Envoy process watchdog configuration. When configured, this monitors for - * nonresponsive threads and kills the process after the configured thresholds. - * See the :ref:`watchdog documentation ` for more information. - * [#next-free-field: 8] - */ -export interface Watchdog__Output { - /** - * The duration after which Envoy counts a nonresponsive thread in the - * *watchdog_miss* statistic. If not specified the default is 200ms. - */ - 'miss_timeout': (_google_protobuf_Duration__Output | null); - /** - * The duration after which Envoy counts a nonresponsive thread in the - * *watchdog_mega_miss* statistic. If not specified the default is - * 1000ms. - */ - 'megamiss_timeout': (_google_protobuf_Duration__Output | null); - /** - * If a watched thread has been nonresponsive for this duration, assume a - * programming error and kill the entire Envoy process. Set to 0 to disable - * kill behavior. If not specified the default is 0 (disabled). - */ - 'kill_timeout': (_google_protobuf_Duration__Output | null); - /** - * If max(2, ceil(registered_threads * Fraction(*multikill_threshold*))) - * threads have been nonresponsive for at least this duration kill the entire - * Envoy process. Set to 0 to disable this behavior. If not specified the - * default is 0 (disabled). - */ - 'multikill_timeout': (_google_protobuf_Duration__Output | null); - /** - * Sets the threshold for *multikill_timeout* in terms of the percentage of - * nonresponsive threads required for the *multikill_timeout*. - * If not specified the default is 0. - */ - 'multikill_threshold': (_envoy_type_v3_Percent__Output | null); - /** - * Defines the maximum jitter used to adjust the *kill_timeout* if *kill_timeout* is - * enabled. Enabling this feature would help to reduce risk of synchronized - * watchdog kill events across proxies due to external triggers. Set to 0 to - * disable. If not specified the default is 0 (disabled). - */ - 'max_kill_timeout_jitter': (_google_protobuf_Duration__Output | null); - /** - * Register actions that will fire on given WatchDog events. - * See *WatchDogAction* for priority of events. - */ - 'actions': (_envoy_config_bootstrap_v3_Watchdog_WatchdogAction__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts b/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts deleted file mode 100644 index b478615ea..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/bootstrap/v3/Watchdogs.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/bootstrap/v3/bootstrap.proto - -import type { Watchdog as _envoy_config_bootstrap_v3_Watchdog, Watchdog__Output as _envoy_config_bootstrap_v3_Watchdog__Output } from '../../../../envoy/config/bootstrap/v3/Watchdog'; - -/** - * Allows you to specify different watchdog configs for different subsystems. - * This allows finer tuned policies for the watchdog. If a subsystem is omitted - * the default values for that system will be used. - */ -export interface Watchdogs { - /** - * Watchdog for the main thread. - */ - 'main_thread_watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); - /** - * Watchdog for the worker threads. - */ - 'worker_watchdog'?: (_envoy_config_bootstrap_v3_Watchdog | null); -} - -/** - * Allows you to specify different watchdog configs for different subsystems. - * This allows finer tuned policies for the watchdog. If a subsystem is omitted - * the default values for that system will be used. - */ -export interface Watchdogs__Output { - /** - * Watchdog for the main thread. - */ - 'main_thread_watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); - /** - * Watchdog for the worker threads. - */ - 'worker_watchdog': (_envoy_config_bootstrap_v3_Watchdog__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts index 4a8a4be36..61f473134 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/CircuitBreakers.ts @@ -1,6 +1,6 @@ // Original file: deps/envoy-api/envoy/config/cluster/v3/circuit_breaker.proto -import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority } from '../../../../envoy/config/core/v3/RoutingPriority'; +import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority, RoutingPriority__Output as _envoy_config_core_v3_RoutingPriority__Output } from '../../../../envoy/config/core/v3/RoutingPriority'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; @@ -50,7 +50,7 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds { * The :ref:`RoutingPriority` * the specified CircuitBreaker settings apply to. */ - 'priority'?: (_envoy_config_core_v3_RoutingPriority | keyof typeof _envoy_config_core_v3_RoutingPriority); + 'priority'?: (_envoy_config_core_v3_RoutingPriority); /** * The maximum number of connections that Envoy will make to the upstream * cluster. If not specified, the default is 1024. @@ -114,7 +114,7 @@ export interface _envoy_config_cluster_v3_CircuitBreakers_Thresholds__Output { * The :ref:`RoutingPriority` * the specified CircuitBreaker settings apply to. */ - 'priority': (keyof typeof _envoy_config_core_v3_RoutingPriority); + 'priority': (_envoy_config_core_v3_RoutingPriority__Output); /** * The maximum number of connections that Envoy will make to the upstream * cluster. If not specified, the default is 1024. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts index be30d8212..8cab36301 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/Cluster.ts @@ -29,22 +29,37 @@ import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_ import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; import type { HealthStatusSet as _envoy_config_core_v3_HealthStatusSet, HealthStatusSet__Output as _envoy_config_core_v3_HealthStatusSet__Output } from '../../../../envoy/config/core/v3/HealthStatusSet'; import type { DoubleValue as _google_protobuf_DoubleValue, DoubleValue__Output as _google_protobuf_DoubleValue__Output } from '../../../../google/protobuf/DoubleValue'; -import type { Long } from '@grpc/proto-loader'; // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto -export enum _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection { +export const _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection = { /** * Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). * If :ref:`http2_protocol_options ` are * present, HTTP2 will be used, otherwise HTTP1.1 will be used. */ - USE_CONFIGURED_PROTOCOL = 0, + USE_CONFIGURED_PROTOCOL: 'USE_CONFIGURED_PROTOCOL', /** * Use HTTP1.1 or HTTP2, depending on which one is used on the downstream connection. */ - USE_DOWNSTREAM_PROTOCOL = 1, -} + USE_DOWNSTREAM_PROTOCOL: 'USE_DOWNSTREAM_PROTOCOL', +} as const; + +export type _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection = + /** + * Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). + * If :ref:`http2_protocol_options ` are + * present, HTTP2 will be used, otherwise HTTP1.1 will be used. + */ + | 'USE_CONFIGURED_PROTOCOL' + | 0 + /** + * Use HTTP1.1 or HTTP2, depending on which one is used on the downstream connection. + */ + | 'USE_DOWNSTREAM_PROTOCOL' + | 1 + +export type _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection__Output = typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection[keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection] /** * Common configuration for all load balancer implementations. @@ -268,36 +283,81 @@ export interface _envoy_config_cluster_v3_Cluster_CustomClusterType__Output { * Refer to :ref:`service discovery type ` * for an explanation on each type. */ -export enum _envoy_config_cluster_v3_Cluster_DiscoveryType { +export const _envoy_config_cluster_v3_Cluster_DiscoveryType = { /** * Refer to the :ref:`static discovery type` * for an explanation. */ - STATIC = 0, + STATIC: 'STATIC', /** * Refer to the :ref:`strict DNS discovery * type` * for an explanation. */ - STRICT_DNS = 1, + STRICT_DNS: 'STRICT_DNS', /** * Refer to the :ref:`logical DNS discovery * type` * for an explanation. */ - LOGICAL_DNS = 2, + LOGICAL_DNS: 'LOGICAL_DNS', /** * Refer to the :ref:`service discovery type` * for an explanation. */ - EDS = 3, + EDS: 'EDS', /** * Refer to the :ref:`original destination discovery * type` * for an explanation. */ - ORIGINAL_DST = 4, -} + ORIGINAL_DST: 'ORIGINAL_DST', +} as const; + +/** + * Refer to :ref:`service discovery type ` + * for an explanation on each type. + */ +export type _envoy_config_cluster_v3_Cluster_DiscoveryType = + /** + * Refer to the :ref:`static discovery type` + * for an explanation. + */ + | 'STATIC' + | 0 + /** + * Refer to the :ref:`strict DNS discovery + * type` + * for an explanation. + */ + | 'STRICT_DNS' + | 1 + /** + * Refer to the :ref:`logical DNS discovery + * type` + * for an explanation. + */ + | 'LOGICAL_DNS' + | 2 + /** + * Refer to the :ref:`service discovery type` + * for an explanation. + */ + | 'EDS' + | 3 + /** + * Refer to the :ref:`original destination discovery + * type` + * for an explanation. + */ + | 'ORIGINAL_DST' + | 4 + +/** + * Refer to :ref:`service discovery type ` + * for an explanation on each type. + */ +export type _envoy_config_cluster_v3_Cluster_DiscoveryType__Output = typeof _envoy_config_cluster_v3_Cluster_DiscoveryType[keyof typeof _envoy_config_cluster_v3_Cluster_DiscoveryType] // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto @@ -324,13 +384,73 @@ export enum _envoy_config_cluster_v3_Cluster_DiscoveryType { * ignored. * [#next-major-version: deprecate AUTO in favor of a V6_PREFERRED option.] */ -export enum _envoy_config_cluster_v3_Cluster_DnsLookupFamily { - AUTO = 0, - V4_ONLY = 1, - V6_ONLY = 2, - V4_PREFERRED = 3, - ALL = 4, -} +export const _envoy_config_cluster_v3_Cluster_DnsLookupFamily = { + AUTO: 'AUTO', + V4_ONLY: 'V4_ONLY', + V6_ONLY: 'V6_ONLY', + V4_PREFERRED: 'V4_PREFERRED', + ALL: 'ALL', +} as const; + +/** + * When V4_ONLY is selected, the DNS resolver will only perform a lookup for + * addresses in the IPv4 family. If V6_ONLY is selected, the DNS resolver will + * only perform a lookup for addresses in the IPv6 family. If AUTO is + * specified, the DNS resolver will first perform a lookup for addresses in + * the IPv6 family and fallback to a lookup for addresses in the IPv4 family. + * This is semantically equivalent to a non-existent V6_PREFERRED option. + * AUTO is a legacy name that is more opaque than + * necessary and will be deprecated in favor of V6_PREFERRED in a future major version of the API. + * If V4_PREFERRED is specified, the DNS resolver will first perform a lookup for addresses in the + * IPv4 family and fallback to a lookup for addresses in the IPv6 family. i.e., the callback + * target will only get v6 addresses if there were NO v4 addresses to return. + * If ALL is specified, the DNS resolver will perform a lookup for both IPv4 and IPv6 families, + * and return all resolved addresses. When this is used, Happy Eyeballs will be enabled for + * upstream connections. Refer to :ref:`Happy Eyeballs Support ` + * for more information. + * For cluster types other than + * :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS`, + * this setting is + * ignored. + * [#next-major-version: deprecate AUTO in favor of a V6_PREFERRED option.] + */ +export type _envoy_config_cluster_v3_Cluster_DnsLookupFamily = + | 'AUTO' + | 0 + | 'V4_ONLY' + | 1 + | 'V6_ONLY' + | 2 + | 'V4_PREFERRED' + | 3 + | 'ALL' + | 4 + +/** + * When V4_ONLY is selected, the DNS resolver will only perform a lookup for + * addresses in the IPv4 family. If V6_ONLY is selected, the DNS resolver will + * only perform a lookup for addresses in the IPv6 family. If AUTO is + * specified, the DNS resolver will first perform a lookup for addresses in + * the IPv6 family and fallback to a lookup for addresses in the IPv4 family. + * This is semantically equivalent to a non-existent V6_PREFERRED option. + * AUTO is a legacy name that is more opaque than + * necessary and will be deprecated in favor of V6_PREFERRED in a future major version of the API. + * If V4_PREFERRED is specified, the DNS resolver will first perform a lookup for addresses in the + * IPv4 family and fallback to a lookup for addresses in the IPv6 family. i.e., the callback + * target will only get v6 addresses if there were NO v4 addresses to return. + * If ALL is specified, the DNS resolver will perform a lookup for both IPv4 and IPv6 families, + * and return all resolved addresses. When this is used, Happy Eyeballs will be enabled for + * upstream connections. Refer to :ref:`Happy Eyeballs Support ` + * for more information. + * For cluster types other than + * :ref:`STRICT_DNS` and + * :ref:`LOGICAL_DNS`, + * this setting is + * ignored. + * [#next-major-version: deprecate AUTO in favor of a V6_PREFERRED option.] + */ +export type _envoy_config_cluster_v3_Cluster_DnsLookupFamily__Output = typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily[keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily] /** * Only valid when discovery type is EDS. @@ -369,18 +489,40 @@ export interface _envoy_config_cluster_v3_Cluster_EdsClusterConfig__Output { /** * The hash function used to hash hosts onto the ketama ring. */ -export enum _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction { +export const _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction = { /** * Use `xxHash `_, this is the default hash function. */ - XX_HASH = 0, + XX_HASH: 'XX_HASH', /** * Use `MurmurHash2 `_, this is compatible with * std:hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled * on Linux and not macOS. */ - MURMUR_HASH_2 = 1, -} + MURMUR_HASH_2: 'MURMUR_HASH_2', +} as const; + +/** + * The hash function used to hash hosts onto the ketama ring. + */ +export type _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction = + /** + * Use `xxHash `_, this is the default hash function. + */ + | 'XX_HASH' + | 0 + /** + * Use `MurmurHash2 `_, this is compatible with + * std:hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled + * on Linux and not macOS. + */ + | 'MURMUR_HASH_2' + | 1 + +/** + * The hash function used to hash hosts onto the ketama ring. + */ +export type _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction__Output = typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction[keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction] // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto @@ -388,42 +530,42 @@ export enum _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction { * Refer to :ref:`load balancer type ` architecture * overview section for information on each type. */ -export enum _envoy_config_cluster_v3_Cluster_LbPolicy { +export const _envoy_config_cluster_v3_Cluster_LbPolicy = { /** * Refer to the :ref:`round robin load balancing * policy` * for an explanation. */ - ROUND_ROBIN = 0, + ROUND_ROBIN: 'ROUND_ROBIN', /** * Refer to the :ref:`least request load balancing * policy` * for an explanation. */ - LEAST_REQUEST = 1, + LEAST_REQUEST: 'LEAST_REQUEST', /** * Refer to the :ref:`ring hash load balancing * policy` * for an explanation. */ - RING_HASH = 2, + RING_HASH: 'RING_HASH', /** * Refer to the :ref:`random load balancing * policy` * for an explanation. */ - RANDOM = 3, + RANDOM: 'RANDOM', /** * Refer to the :ref:`Maglev load balancing policy` * for an explanation. */ - MAGLEV = 5, + MAGLEV: 'MAGLEV', /** * This load balancer type must be specified if the configured cluster provides a cluster * specific load balancer. Consult the configured cluster's documentation for whether to set * this option or not. */ - CLUSTER_PROVIDED = 6, + CLUSTER_PROVIDED: 'CLUSTER_PROVIDED', /** * Use the new :ref:`load_balancing_policy * ` field to determine the LB policy. @@ -431,8 +573,70 @@ export enum _envoy_config_cluster_v3_Cluster_LbPolicy { * ` field without * setting any value in :ref:`lb_policy`. */ - LOAD_BALANCING_POLICY_CONFIG = 7, -} + LOAD_BALANCING_POLICY_CONFIG: 'LOAD_BALANCING_POLICY_CONFIG', +} as const; + +/** + * Refer to :ref:`load balancer type ` architecture + * overview section for information on each type. + */ +export type _envoy_config_cluster_v3_Cluster_LbPolicy = + /** + * Refer to the :ref:`round robin load balancing + * policy` + * for an explanation. + */ + | 'ROUND_ROBIN' + | 0 + /** + * Refer to the :ref:`least request load balancing + * policy` + * for an explanation. + */ + | 'LEAST_REQUEST' + | 1 + /** + * Refer to the :ref:`ring hash load balancing + * policy` + * for an explanation. + */ + | 'RING_HASH' + | 2 + /** + * Refer to the :ref:`random load balancing + * policy` + * for an explanation. + */ + | 'RANDOM' + | 3 + /** + * Refer to the :ref:`Maglev load balancing policy` + * for an explanation. + */ + | 'MAGLEV' + | 5 + /** + * This load balancer type must be specified if the configured cluster provides a cluster + * specific load balancer. Consult the configured cluster's documentation for whether to set + * this option or not. + */ + | 'CLUSTER_PROVIDED' + | 6 + /** + * Use the new :ref:`load_balancing_policy + * ` field to determine the LB policy. + * This has been deprecated in favor of using the :ref:`load_balancing_policy + * ` field without + * setting any value in :ref:`lb_policy`. + */ + | 'LOAD_BALANCING_POLICY_CONFIG' + | 7 + +/** + * Refer to :ref:`load balancer type ` architecture + * overview section for information on each type. + */ +export type _envoy_config_cluster_v3_Cluster_LbPolicy__Output = typeof _envoy_config_cluster_v3_Cluster_LbPolicy[keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy] /** * Optionally divide the endpoints in this cluster into subsets defined by @@ -445,7 +649,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { * metadata. The value defaults to * :ref:`NO_FALLBACK`. */ - 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); + 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); /** * Specifies the default subset of endpoints used during fallback if * fallback_policy is @@ -518,7 +722,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig { * The value defaults to * :ref:`METADATA_NO_FALLBACK`. */ - 'metadata_fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy); + 'metadata_fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy); } /** @@ -532,7 +736,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { * metadata. The value defaults to * :ref:`NO_FALLBACK`. */ - 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy); + 'fallback_policy': (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy__Output); /** * Specifies the default subset of endpoints used during fallback if * fallback_policy is @@ -605,7 +809,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { * The value defaults to * :ref:`METADATA_NO_FALLBACK`. */ - 'metadata_fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy); + 'metadata_fallback_policy': (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy__Output); } // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto @@ -617,19 +821,43 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig__Output { * etc). If DEFAULT_SUBSET is selected, load balancing is performed over the * endpoints matching the values from the default_subset field. */ -export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy { - NO_FALLBACK = 0, - ANY_ENDPOINT = 1, - DEFAULT_SUBSET = 2, -} +export const _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy = { + NO_FALLBACK: 'NO_FALLBACK', + ANY_ENDPOINT: 'ANY_ENDPOINT', + DEFAULT_SUBSET: 'DEFAULT_SUBSET', +} as const; + +/** + * If NO_FALLBACK is selected, a result + * equivalent to no healthy hosts is reported. If ANY_ENDPOINT is selected, + * any cluster endpoint may be returned (subject to policy, health checks, + * etc). If DEFAULT_SUBSET is selected, load balancing is performed over the + * endpoints matching the values from the default_subset field. + */ +export type _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy = + | 'NO_FALLBACK' + | 0 + | 'ANY_ENDPOINT' + | 1 + | 'DEFAULT_SUBSET' + | 2 + +/** + * If NO_FALLBACK is selected, a result + * equivalent to no healthy hosts is reported. If ANY_ENDPOINT is selected, + * any cluster endpoint may be returned (subject to policy, health checks, + * etc). If DEFAULT_SUBSET is selected, load balancing is performed over the + * endpoints matching the values from the default_subset field. + */ +export type _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy__Output = typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy[keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetFallbackPolicy] // Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto -export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy { +export const _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy = { /** * No fallback. Route metadata will be used as-is. */ - METADATA_NO_FALLBACK = 0, + METADATA_NO_FALLBACK: 'METADATA_NO_FALLBACK', /** * A special metadata key ``fallback_list`` will be used to provide variants of metadata to try. * Value of ``fallback_list`` key has to be a list. Every list element has to be a struct - it will @@ -671,8 +899,60 @@ export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFall * * is used. */ - FALLBACK_LIST = 1, -} + FALLBACK_LIST: 'FALLBACK_LIST', +} as const; + +export type _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy = + /** + * No fallback. Route metadata will be used as-is. + */ + | 'METADATA_NO_FALLBACK' + | 0 + /** + * A special metadata key ``fallback_list`` will be used to provide variants of metadata to try. + * Value of ``fallback_list`` key has to be a list. Every list element has to be a struct - it will + * be merged with route metadata, overriding keys that appear in both places. + * ``fallback_list`` entries will be used in order until a host is found. + * + * ``fallback_list`` key itself is removed from metadata before subset load balancing is performed. + * + * Example: + * + * for metadata: + * + * .. code-block:: yaml + * + * version: 1.0 + * fallback_list: + * - version: 2.0 + * hardware: c64 + * - hardware: c32 + * - version: 3.0 + * + * at first, metadata: + * + * .. code-block:: json + * + * {"version": "2.0", "hardware": "c64"} + * + * will be used for load balancing. If no host is found, metadata: + * + * .. code-block:: json + * + * {"version": "1.0", "hardware": "c32"} + * + * is next to try. If it still results in no host, finally metadata: + * + * .. code-block:: json + * + * {"version": "3.0"} + * + * is used. + */ + | 'FALLBACK_LIST' + | 1 + +export type _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy__Output = typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy[keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetMetadataFallbackPolicy] /** * Specifications for subsets. @@ -698,7 +978,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto * The behavior used when no endpoint subset matches the selected route's * metadata. */ - 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); + 'fallback_policy'?: (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); /** * Subset of * :ref:`keys` used by @@ -737,7 +1017,7 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto * The behavior used when no endpoint subset matches the selected route's * metadata. */ - 'fallback_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy); + 'fallback_policy': (_envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy__Output); /** * Subset of * :ref:`keys` used by @@ -757,25 +1037,25 @@ export interface _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelecto /** * Allows to override top level fallback policy per selector. */ -export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy { +export const _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy = { /** * If NOT_DEFINED top level config fallback policy is used instead. */ - NOT_DEFINED = 0, + NOT_DEFINED: 'NOT_DEFINED', /** * If NO_FALLBACK is selected, a result equivalent to no healthy hosts is reported. */ - NO_FALLBACK = 1, + NO_FALLBACK: 'NO_FALLBACK', /** * If ANY_ENDPOINT is selected, any cluster endpoint may be returned * (subject to policy, health checks, etc). */ - ANY_ENDPOINT = 2, + ANY_ENDPOINT: 'ANY_ENDPOINT', /** * If DEFAULT_SUBSET is selected, load balancing is performed over the * endpoints matching the values from the default_subset field. */ - DEFAULT_SUBSET = 3, + DEFAULT_SUBSET: 'DEFAULT_SUBSET', /** * If KEYS_SUBSET is selected, subset selector matching is performed again with metadata * keys reduced to @@ -783,8 +1063,49 @@ export enum _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbS * It allows for a fallback to a different, less specific selector if some of the keys of * the selector are considered optional. */ - KEYS_SUBSET = 4, -} + KEYS_SUBSET: 'KEYS_SUBSET', +} as const; + +/** + * Allows to override top level fallback policy per selector. + */ +export type _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy = + /** + * If NOT_DEFINED top level config fallback policy is used instead. + */ + | 'NOT_DEFINED' + | 0 + /** + * If NO_FALLBACK is selected, a result equivalent to no healthy hosts is reported. + */ + | 'NO_FALLBACK' + | 1 + /** + * If ANY_ENDPOINT is selected, any cluster endpoint may be returned + * (subject to policy, health checks, etc). + */ + | 'ANY_ENDPOINT' + | 2 + /** + * If DEFAULT_SUBSET is selected, load balancing is performed over the + * endpoints matching the values from the default_subset field. + */ + | 'DEFAULT_SUBSET' + | 3 + /** + * If KEYS_SUBSET is selected, subset selector matching is performed again with metadata + * keys reduced to + * :ref:`fallback_keys_subset`. + * It allows for a fallback to a different, less specific selector if some of the keys of + * the selector are considered optional. + */ + | 'KEYS_SUBSET' + | 4 + +/** + * Allows to override top level fallback policy per selector. + */ +export type _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy__Output = typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy[keyof typeof _envoy_config_cluster_v3_Cluster_LbSubsetConfig_LbSubsetSelector_LbSubsetSelectorFallbackPolicy] /** * Specific configuration for the LeastRequest load balancing policy. @@ -1138,7 +1459,7 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig { * The hash function used to hash hosts onto the ketama ring. The value defaults to * :ref:`XX_HASH`. */ - 'hash_function'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction | keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); + 'hash_function'?: (_envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); /** * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered * to further constrain resource use. See also @@ -1163,7 +1484,7 @@ export interface _envoy_config_cluster_v3_Cluster_RingHashLbConfig__Output { * The hash function used to hash hosts onto the ketama ring. The value defaults to * :ref:`XX_HASH`. */ - 'hash_function': (keyof typeof _envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction); + 'hash_function': (_envoy_config_cluster_v3_Cluster_RingHashLbConfig_HashFunction__Output); /** * Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered * to further constrain resource use. See also @@ -1383,7 +1704,7 @@ export interface Cluster { * The :ref:`service discovery type ` * to use for resolving the cluster. */ - 'type'?: (_envoy_config_cluster_v3_Cluster_DiscoveryType | keyof typeof _envoy_config_cluster_v3_Cluster_DiscoveryType); + 'type'?: (_envoy_config_cluster_v3_Cluster_DiscoveryType); /** * Configuration to use for EDS updates for the Cluster. */ @@ -1402,7 +1723,7 @@ export interface Cluster { * The :ref:`load balancer type ` to use * when picking a host in the cluster. */ - 'lb_policy'?: (_envoy_config_cluster_v3_Cluster_LbPolicy | keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); + 'lb_policy'?: (_envoy_config_cluster_v3_Cluster_LbPolicy); /** * Optional :ref:`active health checking ` * configuration for the cluster. If no @@ -1418,6 +1739,7 @@ export interface Cluster { * * .. attention:: * This field has been deprecated in favor of the :ref:`max_requests_per_connection ` field. + * @deprecated */ 'max_requests_per_connection'?: (_google_protobuf_UInt32Value | null); /** @@ -1433,6 +1755,7 @@ export interface Cluster { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'http_protocol_options'?: (_envoy_config_core_v3_Http1ProtocolOptions | null); /** @@ -1449,6 +1772,7 @@ export interface Cluster { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'http2_protocol_options'?: (_envoy_config_core_v3_Http2ProtocolOptions | null); /** @@ -1468,7 +1792,7 @@ export interface Cluster { * value defaults to * :ref:`AUTO`. */ - 'dns_lookup_family'?: (_envoy_config_cluster_v3_Cluster_DnsLookupFamily | keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); + 'dns_lookup_family'?: (_envoy_config_cluster_v3_Cluster_DnsLookupFamily); /** * If DNS resolvers are specified and the cluster type is either * :ref:`STRICT_DNS`, @@ -1482,6 +1806,7 @@ export interface Cluster { * this setting is ignored. * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. + * @deprecated */ 'dns_resolvers'?: (_envoy_config_core_v3_Address)[]; /** @@ -1543,8 +1868,9 @@ export interface Cluster { * ` message. * http_protocol_options can be set via the cluster's * :ref:`extension_protocol_options`. + * @deprecated */ - 'protocol_selection'?: (_envoy_config_cluster_v3_Cluster_ClusterProtocolSelection | keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); + 'protocol_selection'?: (_envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); /** * Common configuration for all load balancer implementations. */ @@ -1570,6 +1896,7 @@ export interface Cluster { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'common_http_protocol_options'?: (_envoy_config_core_v3_HttpProtocolOptions | null); /** @@ -1732,6 +2059,7 @@ export interface Cluster { * Always use TCP queries instead of UDP queries for DNS lookups. * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. + * @deprecated */ 'use_tcp_for_dns_lookups'?: (boolean); /** @@ -1745,6 +2073,7 @@ export interface Cluster { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'upstream_http_protocol_options'?: (_envoy_config_core_v3_UpstreamHttpProtocolOptions | null); /** @@ -1758,6 +2087,7 @@ export interface Cluster { * * This field has been deprecated in favor of ``timeout_budgets``, part of * :ref:`track_cluster_stats `. + * @deprecated */ 'track_timeout_budgets'?: (boolean); /** @@ -1802,6 +2132,7 @@ export interface Cluster { * DNS resolution configuration which includes the underlying dns resolver addresses and options. * This field is deprecated in favor of * :ref:`typed_dns_resolver_config `. + * @deprecated */ 'dns_resolution_config'?: (_envoy_config_core_v3_DnsResolutionConfig | null); /** @@ -1862,7 +2193,7 @@ export interface Cluster__Output { * The :ref:`service discovery type ` * to use for resolving the cluster. */ - 'type'?: (keyof typeof _envoy_config_cluster_v3_Cluster_DiscoveryType); + 'type'?: (_envoy_config_cluster_v3_Cluster_DiscoveryType__Output); /** * Configuration to use for EDS updates for the Cluster. */ @@ -1881,7 +2212,7 @@ export interface Cluster__Output { * The :ref:`load balancer type ` to use * when picking a host in the cluster. */ - 'lb_policy': (keyof typeof _envoy_config_cluster_v3_Cluster_LbPolicy); + 'lb_policy': (_envoy_config_cluster_v3_Cluster_LbPolicy__Output); /** * Optional :ref:`active health checking ` * configuration for the cluster. If no @@ -1897,6 +2228,7 @@ export interface Cluster__Output { * * .. attention:: * This field has been deprecated in favor of the :ref:`max_requests_per_connection ` field. + * @deprecated */ 'max_requests_per_connection': (_google_protobuf_UInt32Value__Output | null); /** @@ -1912,6 +2244,7 @@ export interface Cluster__Output { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'http_protocol_options': (_envoy_config_core_v3_Http1ProtocolOptions__Output | null); /** @@ -1928,6 +2261,7 @@ export interface Cluster__Output { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'http2_protocol_options': (_envoy_config_core_v3_Http2ProtocolOptions__Output | null); /** @@ -1947,7 +2281,7 @@ export interface Cluster__Output { * value defaults to * :ref:`AUTO`. */ - 'dns_lookup_family': (keyof typeof _envoy_config_cluster_v3_Cluster_DnsLookupFamily); + 'dns_lookup_family': (_envoy_config_cluster_v3_Cluster_DnsLookupFamily__Output); /** * If DNS resolvers are specified and the cluster type is either * :ref:`STRICT_DNS`, @@ -1961,6 +2295,7 @@ export interface Cluster__Output { * this setting is ignored. * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. + * @deprecated */ 'dns_resolvers': (_envoy_config_core_v3_Address__Output)[]; /** @@ -2022,8 +2357,9 @@ export interface Cluster__Output { * ` message. * http_protocol_options can be set via the cluster's * :ref:`extension_protocol_options`. + * @deprecated */ - 'protocol_selection': (keyof typeof _envoy_config_cluster_v3_Cluster_ClusterProtocolSelection); + 'protocol_selection': (_envoy_config_cluster_v3_Cluster_ClusterProtocolSelection__Output); /** * Common configuration for all load balancer implementations. */ @@ -2049,6 +2385,7 @@ export interface Cluster__Output { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'common_http_protocol_options': (_envoy_config_core_v3_HttpProtocolOptions__Output | null); /** @@ -2211,6 +2548,7 @@ export interface Cluster__Output { * Always use TCP queries instead of UDP queries for DNS lookups. * This field is deprecated in favor of ``dns_resolution_config`` * which aggregates all of the DNS resolver configuration in a single message. + * @deprecated */ 'use_tcp_for_dns_lookups': (boolean); /** @@ -2224,6 +2562,7 @@ export interface Cluster__Output { * See :ref:`upstream_http_protocol_options * ` * for example usage. + * @deprecated */ 'upstream_http_protocol_options': (_envoy_config_core_v3_UpstreamHttpProtocolOptions__Output | null); /** @@ -2237,6 +2576,7 @@ export interface Cluster__Output { * * This field has been deprecated in favor of ``timeout_budgets``, part of * :ref:`track_cluster_stats `. + * @deprecated */ 'track_timeout_budgets': (boolean); /** @@ -2281,6 +2621,7 @@ export interface Cluster__Output { * DNS resolution configuration which includes the underlying dns resolver addresses and options. * This field is deprecated in favor of * :ref:`typed_dns_resolver_config `. + * @deprecated */ 'dns_resolution_config': (_envoy_config_core_v3_DnsResolutionConfig__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts deleted file mode 100644 index f2dbd0608..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/cluster/v3/UpstreamBindConfig.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/cluster/v3/cluster.proto - -import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; - -/** - * An extensible structure containing the address Envoy should bind to when - * establishing upstream connections. - */ -export interface UpstreamBindConfig { - /** - * The address Envoy should bind to when establishing upstream connections. - */ - 'source_address'?: (_envoy_config_core_v3_Address | null); -} - -/** - * An extensible structure containing the address Envoy should bind to when - * establishing upstream connections. - */ -export interface UpstreamBindConfig__Output { - /** - * The address Envoy should bind to when establishing upstream connections. - */ - 'source_address': (_envoy_config_core_v3_Address__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts index 691ab93ba..0a2f11bdf 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiConfigSource.ts @@ -3,7 +3,7 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; import type { GrpcService as _envoy_config_core_v3_GrpcService, GrpcService__Output as _envoy_config_core_v3_GrpcService__Output } from '../../../../envoy/config/core/v3/GrpcService'; import type { RateLimitSettings as _envoy_config_core_v3_RateLimitSettings, RateLimitSettings__Output as _envoy_config_core_v3_RateLimitSettings__Output } from '../../../../envoy/config/core/v3/RateLimitSettings'; -import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; +import type { ApiVersion as _envoy_config_core_v3_ApiVersion, ApiVersion__Output as _envoy_config_core_v3_ApiVersion__Output } from '../../../../envoy/config/core/v3/ApiVersion'; import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig'; // Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto @@ -11,41 +11,91 @@ import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig /** * APIs may be fetched via either REST or gRPC. */ -export enum _envoy_config_core_v3_ApiConfigSource_ApiType { +export const _envoy_config_core_v3_ApiConfigSource_ApiType = { /** * Ideally this would be 'reserved 0' but one can't reserve the default * value. Instead we throw an exception if this is ever used. + * @deprecated */ - DEPRECATED_AND_UNAVAILABLE_DO_NOT_USE = 0, + DEPRECATED_AND_UNAVAILABLE_DO_NOT_USE: 'DEPRECATED_AND_UNAVAILABLE_DO_NOT_USE', /** * REST-JSON v2 API. The `canonical JSON encoding * `_ for * the v2 protos is used. */ - REST = 1, + REST: 'REST', /** * SotW gRPC service. */ - GRPC = 2, + GRPC: 'GRPC', /** * Using the delta xDS gRPC service, i.e. DeltaDiscovery{Request,Response} * rather than Discovery{Request,Response}. Rather than sending Envoy the entire state * with every update, the xDS server only sends what has changed since the last update. */ - DELTA_GRPC = 3, + DELTA_GRPC: 'DELTA_GRPC', /** * SotW xDS gRPC with ADS. All resources which resolve to this configuration source will be * multiplexed on a single connection to an ADS endpoint. * [#not-implemented-hide:] */ - AGGREGATED_GRPC = 5, + AGGREGATED_GRPC: 'AGGREGATED_GRPC', /** * Delta xDS gRPC with ADS. All resources which resolve to this configuration source will be * multiplexed on a single connection to an ADS endpoint. * [#not-implemented-hide:] */ - AGGREGATED_DELTA_GRPC = 6, -} + AGGREGATED_DELTA_GRPC: 'AGGREGATED_DELTA_GRPC', +} as const; + +/** + * APIs may be fetched via either REST or gRPC. + */ +export type _envoy_config_core_v3_ApiConfigSource_ApiType = + /** + * Ideally this would be 'reserved 0' but one can't reserve the default + * value. Instead we throw an exception if this is ever used. + */ + | 'DEPRECATED_AND_UNAVAILABLE_DO_NOT_USE' + | 0 + /** + * REST-JSON v2 API. The `canonical JSON encoding + * `_ for + * the v2 protos is used. + */ + | 'REST' + | 1 + /** + * SotW gRPC service. + */ + | 'GRPC' + | 2 + /** + * Using the delta xDS gRPC service, i.e. DeltaDiscovery{Request,Response} + * rather than Discovery{Request,Response}. Rather than sending Envoy the entire state + * with every update, the xDS server only sends what has changed since the last update. + */ + | 'DELTA_GRPC' + | 3 + /** + * SotW xDS gRPC with ADS. All resources which resolve to this configuration source will be + * multiplexed on a single connection to an ADS endpoint. + * [#not-implemented-hide:] + */ + | 'AGGREGATED_GRPC' + | 5 + /** + * Delta xDS gRPC with ADS. All resources which resolve to this configuration source will be + * multiplexed on a single connection to an ADS endpoint. + * [#not-implemented-hide:] + */ + | 'AGGREGATED_DELTA_GRPC' + | 6 + +/** + * APIs may be fetched via either REST or gRPC. + */ +export type _envoy_config_core_v3_ApiConfigSource_ApiType__Output = typeof _envoy_config_core_v3_ApiConfigSource_ApiType[keyof typeof _envoy_config_core_v3_ApiConfigSource_ApiType] /** * API configuration source. This identifies the API type and cluster that Envoy @@ -56,7 +106,7 @@ export interface ApiConfigSource { /** * API type (gRPC, REST, delta gRPC) */ - 'api_type'?: (_envoy_config_core_v3_ApiConfigSource_ApiType | keyof typeof _envoy_config_core_v3_ApiConfigSource_ApiType); + 'api_type'?: (_envoy_config_core_v3_ApiConfigSource_ApiType); /** * Cluster names should be used only with REST. If > 1 * cluster is defined, clusters will be cycled through if any kind of failure @@ -94,7 +144,7 @@ export interface ApiConfigSource { * API version for xDS transport protocol. This describes the xDS gRPC/REST * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ - 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); + 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion); /** * A list of config validators that will be executed when a new update is * received from the ApiConfigSource. Note that each validator handles a @@ -117,7 +167,7 @@ export interface ApiConfigSource__Output { /** * API type (gRPC, REST, delta gRPC) */ - 'api_type': (keyof typeof _envoy_config_core_v3_ApiConfigSource_ApiType); + 'api_type': (_envoy_config_core_v3_ApiConfigSource_ApiType__Output); /** * Cluster names should be used only with REST. If > 1 * cluster is defined, clusters will be cycled through if any kind of failure @@ -155,7 +205,7 @@ export interface ApiConfigSource__Output { * API version for xDS transport protocol. This describes the xDS gRPC/REST * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ - 'transport_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); + 'transport_api_version': (_envoy_config_core_v3_ApiVersion__Output); /** * A list of config validators that will be executed when a new update is * received from the ApiConfigSource. Note that each validator handles a diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts index b46f6ec43..d3bad5d4e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ApiVersion.ts @@ -4,19 +4,50 @@ * xDS API and non-xDS services version. This is used to describe both resource and transport * protocol versions (in distinct configuration fields). */ -export enum ApiVersion { +export const ApiVersion = { /** * When not specified, we assume v2, to ease migration to Envoy's stable API * versioning. If a client does not support v2 (e.g. due to deprecation), this * is an invalid value. + * @deprecated */ - AUTO = 0, + AUTO: 'AUTO', /** * Use xDS v2 API. + * @deprecated */ - V2 = 1, + V2: 'V2', /** * Use xDS v3 API. */ - V3 = 2, -} + V3: 'V3', +} as const; + +/** + * xDS API and non-xDS services version. This is used to describe both resource and transport + * protocol versions (in distinct configuration fields). + */ +export type ApiVersion = + /** + * When not specified, we assume v2, to ease migration to Envoy's stable API + * versioning. If a client does not support v2 (e.g. due to deprecation), this + * is an invalid value. + */ + | 'AUTO' + | 0 + /** + * Use xDS v2 API. + */ + | 'V2' + | 1 + /** + * Use xDS v3 API. + */ + | 'V3' + | 2 + +/** + * xDS API and non-xDS services version. This is used to describe both resource and transport + * protocol versions (in distinct configuration fields). + */ +export type ApiVersion__Output = typeof ApiVersion[keyof typeof ApiVersion] diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts index 733c609b1..54543facc 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/BindConfig.ts @@ -31,6 +31,7 @@ export interface BindConfig { /** * Deprecated by * :ref:`extra_source_addresses ` + * @deprecated */ 'additional_source_addresses'?: (_envoy_config_core_v3_SocketAddress)[]; /** @@ -72,6 +73,7 @@ export interface BindConfig__Output { /** * Deprecated by * :ref:`extra_source_addresses ` + * @deprecated */ 'additional_source_addresses': (_envoy_config_core_v3_SocketAddress__Output)[]; /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts index 5438b6e7d..1b98848ef 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ConfigSource.ts @@ -4,7 +4,7 @@ import type { ApiConfigSource as _envoy_config_core_v3_ApiConfigSource, ApiConfi import type { AggregatedConfigSource as _envoy_config_core_v3_AggregatedConfigSource, AggregatedConfigSource__Output as _envoy_config_core_v3_AggregatedConfigSource__Output } from '../../../../envoy/config/core/v3/AggregatedConfigSource'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; import type { SelfConfigSource as _envoy_config_core_v3_SelfConfigSource, SelfConfigSource__Output as _envoy_config_core_v3_SelfConfigSource__Output } from '../../../../envoy/config/core/v3/SelfConfigSource'; -import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; +import type { ApiVersion as _envoy_config_core_v3_ApiVersion, ApiVersion__Output as _envoy_config_core_v3_ApiVersion__Output } from '../../../../envoy/config/core/v3/ApiVersion'; import type { Authority as _xds_core_v3_Authority, Authority__Output as _xds_core_v3_Authority__Output } from '../../../../xds/core/v3/Authority'; import type { PathConfigSource as _envoy_config_core_v3_PathConfigSource, PathConfigSource__Output as _envoy_config_core_v3_PathConfigSource__Output } from '../../../../envoy/config/core/v3/PathConfigSource'; @@ -20,6 +20,7 @@ import type { PathConfigSource as _envoy_config_core_v3_PathConfigSource, PathCo export interface ConfigSource { /** * Deprecated in favor of ``path_config_source``. Use that field instead. + * @deprecated */ 'path'?: (string); /** @@ -60,7 +61,7 @@ export interface ConfigSource { * will request for resources and the resource type that the client will in * turn expect to be delivered. */ - 'resource_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); + 'resource_api_version'?: (_envoy_config_core_v3_ApiVersion); /** * Authorities that this config source may be used for. An authority specified in a xdstp:// URL * is resolved to a ``ConfigSource`` prior to configuration fetch. This field provides the @@ -87,6 +88,7 @@ export interface ConfigSource { export interface ConfigSource__Output { /** * Deprecated in favor of ``path_config_source``. Use that field instead. + * @deprecated */ 'path'?: (string); /** @@ -127,7 +129,7 @@ export interface ConfigSource__Output { * will request for resources and the resource type that the client will in * turn expect to be delivered. */ - 'resource_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); + 'resource_api_version': (_envoy_config_core_v3_ApiVersion__Output); /** * Authorities that this config source may be used for. An authority specified in a xdstp:// URL * is resolved to a ``ConfigSource`` prior to configuration fetch. This field provides the diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts index b25c15cce..dbbdb0cee 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Extension.ts @@ -24,6 +24,7 @@ export interface Extension { * [#not-implemented-hide:] Type descriptor of extension configuration proto. * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] + * @deprecated */ 'type_descriptor'?: (string); /** @@ -64,6 +65,7 @@ export interface Extension__Output { * [#not-implemented-hide:] Type descriptor of extension configuration proto. * [#comment:TODO(yanavlasov): Link to the doc with existing configuration protos.] * [#comment:TODO(yanavlasov): Add tests when PR #9391 lands.] + * @deprecated */ 'type_descriptor': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts index efb656303..e7a0a8d87 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HeaderValueOption.ts @@ -8,25 +8,55 @@ import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _goo /** * Describes the supported actions types for header append action. */ -export enum _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction { +export const _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction = { /** * This action will append the specified value to the existing values if the header * already exists. If the header doesn't exist then this will add the header with * specified key and value. */ - APPEND_IF_EXISTS_OR_ADD = 0, + APPEND_IF_EXISTS_OR_ADD: 'APPEND_IF_EXISTS_OR_ADD', /** * This action will add the header if it doesn't already exist. If the header * already exists then this will be a no-op. */ - ADD_IF_ABSENT = 1, + ADD_IF_ABSENT: 'ADD_IF_ABSENT', /** * This action will overwrite the specified value by discarding any existing values if * the header already exists. If the header doesn't exist then this will add the header * with specified key and value. */ - OVERWRITE_IF_EXISTS_OR_ADD = 2, -} + OVERWRITE_IF_EXISTS_OR_ADD: 'OVERWRITE_IF_EXISTS_OR_ADD', +} as const; + +/** + * Describes the supported actions types for header append action. + */ +export type _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction = + /** + * This action will append the specified value to the existing values if the header + * already exists. If the header doesn't exist then this will add the header with + * specified key and value. + */ + | 'APPEND_IF_EXISTS_OR_ADD' + | 0 + /** + * This action will add the header if it doesn't already exist. If the header + * already exists then this will be a no-op. + */ + | 'ADD_IF_ABSENT' + | 1 + /** + * This action will overwrite the specified value by discarding any existing values if + * the header already exists. If the header doesn't exist then this will add the header + * with specified key and value. + */ + | 'OVERWRITE_IF_EXISTS_OR_ADD' + | 2 + +/** + * Describes the supported actions types for header append action. + */ +export type _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction__Output = typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction[keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction] /** * Header name/value pair plus option to control append behavior. @@ -46,6 +76,7 @@ export interface HeaderValueOption { * The :ref:`external authorization service ` and * :ref:`external processor service ` have * default value (``false``) for this field. + * @deprecated */ 'append'?: (_google_protobuf_BoolValue | null); /** @@ -54,7 +85,7 @@ export interface HeaderValueOption { * Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD * `. */ - 'append_action'?: (_envoy_config_core_v3_HeaderValueOption_HeaderAppendAction | keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); + 'append_action'?: (_envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); /** * Is the header value allowed to be empty? If false (default), custom headers with empty values are dropped, * otherwise they are added. @@ -80,6 +111,7 @@ export interface HeaderValueOption__Output { * The :ref:`external authorization service ` and * :ref:`external processor service ` have * default value (``false``) for this field. + * @deprecated */ 'append': (_google_protobuf_BoolValue__Output | null); /** @@ -88,7 +120,7 @@ export interface HeaderValueOption__Output { * Value defaults to :ref:`APPEND_IF_EXISTS_OR_ADD * `. */ - 'append_action': (keyof typeof _envoy_config_core_v3_HeaderValueOption_HeaderAppendAction); + 'append_action': (_envoy_config_core_v3_HeaderValueOption_HeaderAppendAction__Output); /** * Is the header value allowed to be empty? If false (default), custom headers with empty values are dropped, * otherwise they are added. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts index e0638df7d..f6605412e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthCheck.ts @@ -9,11 +9,10 @@ import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; import type { HeaderValueOption as _envoy_config_core_v3_HeaderValueOption, HeaderValueOption__Output as _envoy_config_core_v3_HeaderValueOption__Output } from '../../../../envoy/config/core/v3/HeaderValueOption'; import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; -import type { CodecClientType as _envoy_type_v3_CodecClientType } from '../../../../envoy/type/v3/CodecClientType'; +import type { CodecClientType as _envoy_type_v3_CodecClientType, CodecClientType__Output as _envoy_type_v3_CodecClientType__Output } from '../../../../envoy/type/v3/CodecClientType'; import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; -import type { RequestMethod as _envoy_config_core_v3_RequestMethod } from '../../../../envoy/config/core/v3/RequestMethod'; +import type { RequestMethod as _envoy_config_core_v3_RequestMethod, RequestMethod__Output as _envoy_config_core_v3_RequestMethod__Output } from '../../../../envoy/config/core/v3/RequestMethod'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -import type { Long } from '@grpc/proto-loader'; /** * Custom health check. @@ -183,7 +182,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { /** * Use specified application protocol for health checks. */ - 'codec_client_type'?: (_envoy_type_v3_CodecClientType | keyof typeof _envoy_type_v3_CodecClientType); + 'codec_client_type'?: (_envoy_type_v3_CodecClientType); /** * An optional service name parameter which is used to validate the identity of * the health checked cluster using a :ref:`StringMatcher @@ -197,7 +196,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck { * CONNECT method is disallowed because it is not appropriate for health check request. * If a non-200 response is expected by the method, it needs to be set in :ref:`expected_statuses `. */ - 'method'?: (_envoy_config_core_v3_RequestMethod | keyof typeof _envoy_config_core_v3_RequestMethod); + 'method'?: (_envoy_config_core_v3_RequestMethod); } /** @@ -272,7 +271,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { /** * Use specified application protocol for health checks. */ - 'codec_client_type': (keyof typeof _envoy_type_v3_CodecClientType); + 'codec_client_type': (_envoy_type_v3_CodecClientType__Output); /** * An optional service name parameter which is used to validate the identity of * the health checked cluster using a :ref:`StringMatcher @@ -286,7 +285,7 @@ export interface _envoy_config_core_v3_HealthCheck_HttpHealthCheck__Output { * CONNECT method is disallowed because it is not appropriate for health check request. * If a non-200 response is expected by the method, it needs to be set in :ref:`expected_statuses `. */ - 'method': (keyof typeof _envoy_config_core_v3_RequestMethod); + 'method': (_envoy_config_core_v3_RequestMethod__Output); } /** @@ -497,6 +496,7 @@ export interface HealthCheck { * in the file sink extension. * * Specifies the path to the :ref:`health check event log `. + * @deprecated */ 'event_log_path'?: (string); /** @@ -687,6 +687,7 @@ export interface HealthCheck__Output { * in the file sink extension. * * Specifies the path to the :ref:`health check event log `. + * @deprecated */ 'event_log_path': (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts index 7d3d76569..54298f59b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatus.ts @@ -3,19 +3,19 @@ /** * Endpoint health status. */ -export enum HealthStatus { +export const HealthStatus = { /** * The health status is not known. This is interpreted by Envoy as ``HEALTHY``. */ - UNKNOWN = 0, + UNKNOWN: 'UNKNOWN', /** * Healthy. */ - HEALTHY = 1, + HEALTHY: 'HEALTHY', /** * Unhealthy. */ - UNHEALTHY = 2, + UNHEALTHY: 'UNHEALTHY', /** * Connection draining in progress. E.g., * ``_ @@ -23,14 +23,59 @@ export enum HealthStatus { * ``_. * This is interpreted by Envoy as ``UNHEALTHY``. */ - DRAINING = 3, + DRAINING: 'DRAINING', /** * Health check timed out. This is part of HDS and is interpreted by Envoy as * ``UNHEALTHY``. */ - TIMEOUT = 4, + TIMEOUT: 'TIMEOUT', /** * Degraded. */ - DEGRADED = 5, -} + DEGRADED: 'DEGRADED', +} as const; + +/** + * Endpoint health status. + */ +export type HealthStatus = + /** + * The health status is not known. This is interpreted by Envoy as ``HEALTHY``. + */ + | 'UNKNOWN' + | 0 + /** + * Healthy. + */ + | 'HEALTHY' + | 1 + /** + * Unhealthy. + */ + | 'UNHEALTHY' + | 2 + /** + * Connection draining in progress. E.g., + * ``_ + * or + * ``_. + * This is interpreted by Envoy as ``UNHEALTHY``. + */ + | 'DRAINING' + | 3 + /** + * Health check timed out. This is part of HDS and is interpreted by Envoy as + * ``UNHEALTHY``. + */ + | 'TIMEOUT' + | 4 + /** + * Degraded. + */ + | 'DEGRADED' + | 5 + +/** + * Endpoint health status. + */ +export type HealthStatus__Output = typeof HealthStatus[keyof typeof HealthStatus] diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts index c518192d7..c94bf049c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HealthStatusSet.ts @@ -1,17 +1,17 @@ // Original file: deps/envoy-api/envoy/config/core/v3/health_check.proto -import type { HealthStatus as _envoy_config_core_v3_HealthStatus } from '../../../../envoy/config/core/v3/HealthStatus'; +import type { HealthStatus as _envoy_config_core_v3_HealthStatus, HealthStatus__Output as _envoy_config_core_v3_HealthStatus__Output } from '../../../../envoy/config/core/v3/HealthStatus'; export interface HealthStatusSet { /** * An order-independent set of health status. */ - 'statuses'?: (_envoy_config_core_v3_HealthStatus | keyof typeof _envoy_config_core_v3_HealthStatus)[]; + 'statuses'?: (_envoy_config_core_v3_HealthStatus)[]; } export interface HealthStatusSet__Output { /** * An order-independent set of health status. */ - 'statuses': (keyof typeof _envoy_config_core_v3_HealthStatus)[]; + 'statuses': (_envoy_config_core_v3_HealthStatus__Output)[]; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts index cc22ce44c..9e0ae3d6e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Http2ProtocolOptions.ts @@ -159,6 +159,7 @@ export interface Http2ProtocolOptions { * ` * * See `RFC7540, sec. 8.1 `_ for details. + * @deprecated */ 'stream_error_on_invalid_http_messaging'?: (boolean); /** @@ -339,6 +340,7 @@ export interface Http2ProtocolOptions__Output { * ` * * See `RFC7540, sec. 8.1 `_ for details. + * @deprecated */ 'stream_error_on_invalid_http_messaging': (boolean); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts index a3064110f..dfa800c3b 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/HttpProtocolOptions.ts @@ -12,24 +12,61 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a * as a security measure due to systems that treat '_' and '-' as interchangeable. Envoy by default allows client request headers with underscore * characters. */ -export enum _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction { +export const _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction = { /** * Allow headers with underscores. This is the default behavior. */ - ALLOW = 0, + ALLOW: 'ALLOW', /** * Reject client request. HTTP/1 requests are rejected with the 400 status. HTTP/2 requests * end with the stream reset. The "httpN.requests_rejected_with_underscores_in_headers" counter * is incremented for each rejected request. */ - REJECT_REQUEST = 1, + REJECT_REQUEST: 'REJECT_REQUEST', /** * Drop the client header with name containing underscores. The header is dropped before the filter chain is * invoked and as such filters will not see dropped headers. The * "httpN.dropped_headers_with_underscores" is incremented for each dropped header. */ - DROP_HEADER = 2, -} + DROP_HEADER: 'DROP_HEADER', +} as const; + +/** + * Action to take when Envoy receives client request with header names containing underscore + * characters. + * Underscore character is allowed in header names by the RFC-7230 and this behavior is implemented + * as a security measure due to systems that treat '_' and '-' as interchangeable. Envoy by default allows client request headers with underscore + * characters. + */ +export type _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction = + /** + * Allow headers with underscores. This is the default behavior. + */ + | 'ALLOW' + | 0 + /** + * Reject client request. HTTP/1 requests are rejected with the 400 status. HTTP/2 requests + * end with the stream reset. The "httpN.requests_rejected_with_underscores_in_headers" counter + * is incremented for each rejected request. + */ + | 'REJECT_REQUEST' + | 1 + /** + * Drop the client header with name containing underscores. The header is dropped before the filter chain is + * invoked and as such filters will not see dropped headers. The + * "httpN.dropped_headers_with_underscores" is incremented for each dropped header. + */ + | 'DROP_HEADER' + | 2 + +/** + * Action to take when Envoy receives client request with header names containing underscore + * characters. + * Underscore character is allowed in header names by the RFC-7230 and this behavior is implemented + * as a security measure due to systems that treat '_' and '-' as interchangeable. Envoy by default allows client request headers with underscore + * characters. + */ +export type _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction__Output = typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction[keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction] /** * [#next-free-field: 7] @@ -81,7 +118,7 @@ export interface HttpProtocolOptions { * Note: this only affects client headers. It does not affect headers added * by Envoy filters and does not have any impact if added to cluster config. */ - 'headers_with_underscores_action'?: (_envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction | keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); + 'headers_with_underscores_action'?: (_envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); /** * Optional maximum requests for both upstream and downstream connections. * If not specified, there is no limit. @@ -141,7 +178,7 @@ export interface HttpProtocolOptions__Output { * Note: this only affects client headers. It does not affect headers added * by Envoy filters and does not have any impact if added to cluster config. */ - 'headers_with_underscores_action': (keyof typeof _envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction); + 'headers_with_underscores_action': (_envoy_config_core_v3_HttpProtocolOptions_HeadersWithUnderscoresAction__Output); /** * Optional maximum requests for both upstream and downstream connections. * If not specified, there is no limit. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts index 6aef94d8e..b29b68502 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/Node.ts @@ -78,6 +78,7 @@ export interface Node { * for filtering :ref:`listeners ` to be returned. For example, * if there is a listener bound to port 80, the list can optionally contain the * SocketAddress ``(0.0.0.0,80)``. The field is optional and just a hint. + * @deprecated */ 'listening_addresses'?: (_envoy_config_core_v3_Address)[]; /** @@ -162,6 +163,7 @@ export interface Node__Output { * for filtering :ref:`listeners ` to be returned. For example, * if there is a listener bound to port 80, the list can optionally contain the * SocketAddress ``(0.0.0.0,80)``. The field is optional and just a hint. + * @deprecated */ 'listening_addresses': (_envoy_config_core_v3_Address__Output)[]; /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts index 7da9d569e..34cf7475f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolConfig.ts @@ -4,22 +4,36 @@ import type { ProxyProtocolPassThroughTLVs as _envoy_config_core_v3_ProxyProtoco // Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto -export enum _envoy_config_core_v3_ProxyProtocolConfig_Version { +export const _envoy_config_core_v3_ProxyProtocolConfig_Version = { /** * PROXY protocol version 1. Human readable format. */ - V1 = 0, + V1: 'V1', /** * PROXY protocol version 2. Binary format. */ - V2 = 1, -} + V2: 'V2', +} as const; + +export type _envoy_config_core_v3_ProxyProtocolConfig_Version = + /** + * PROXY protocol version 1. Human readable format. + */ + | 'V1' + | 0 + /** + * PROXY protocol version 2. Binary format. + */ + | 'V2' + | 1 + +export type _envoy_config_core_v3_ProxyProtocolConfig_Version__Output = typeof _envoy_config_core_v3_ProxyProtocolConfig_Version[keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version] export interface ProxyProtocolConfig { /** * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details */ - 'version'?: (_envoy_config_core_v3_ProxyProtocolConfig_Version | keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); + 'version'?: (_envoy_config_core_v3_ProxyProtocolConfig_Version); /** * This config controls which TLVs can be passed to upstream if it is Proxy Protocol * V2 header. If there is no setting for this field, no TLVs will be passed through. @@ -31,7 +45,7 @@ export interface ProxyProtocolConfig__Output { /** * The PROXY protocol version to use. See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details */ - 'version': (keyof typeof _envoy_config_core_v3_ProxyProtocolConfig_Version); + 'version': (_envoy_config_core_v3_ProxyProtocolConfig_Version__Output); /** * This config controls which TLVs can be passed to upstream if it is Proxy Protocol * V2 header. If there is no setting for this field, no TLVs will be passed through. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts index 0dddbf79f..9f253ceff 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/ProxyProtocolPassThroughTLVs.ts @@ -3,23 +3,37 @@ // Original file: deps/envoy-api/envoy/config/core/v3/proxy_protocol.proto -export enum _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType { +export const _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType = { /** * Pass all TLVs. */ - INCLUDE_ALL = 0, + INCLUDE_ALL: 'INCLUDE_ALL', /** * Pass specific TLVs defined in tlv_type. */ - INCLUDE = 1, -} + INCLUDE: 'INCLUDE', +} as const; + +export type _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType = + /** + * Pass all TLVs. + */ + | 'INCLUDE_ALL' + | 0 + /** + * Pass specific TLVs defined in tlv_type. + */ + | 'INCLUDE' + | 1 + +export type _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType__Output = typeof _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType[keyof typeof _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType] export interface ProxyProtocolPassThroughTLVs { /** * The strategy to pass through TLVs. Default is INCLUDE_ALL. * If INCLUDE_ALL is set, all TLVs will be passed through no matter the tlv_type field. */ - 'match_type'?: (_envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType | keyof typeof _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType); + 'match_type'?: (_envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType); /** * The TLV types that are applied based on match_type. * TLV type is defined as uint8_t in proxy protocol. See `the spec @@ -33,7 +47,7 @@ export interface ProxyProtocolPassThroughTLVs__Output { * The strategy to pass through TLVs. Default is INCLUDE_ALL. * If INCLUDE_ALL is set, all TLVs will be passed through no matter the tlv_type field. */ - 'match_type': (keyof typeof _envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType); + 'match_type': (_envoy_config_core_v3_ProxyProtocolPassThroughTLVs_PassTLVsMatchType__Output); /** * The TLV types that are applied based on match_type. * TLV type is defined as uint8_t in proxy protocol. See `the spec diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts index 9be1aa6d1..67d40fda6 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RequestMethod.ts @@ -3,15 +3,45 @@ /** * HTTP request method. */ -export enum RequestMethod { - METHOD_UNSPECIFIED = 0, - GET = 1, - HEAD = 2, - POST = 3, - PUT = 4, - DELETE = 5, - CONNECT = 6, - OPTIONS = 7, - TRACE = 8, - PATCH = 9, -} +export const RequestMethod = { + METHOD_UNSPECIFIED: 'METHOD_UNSPECIFIED', + GET: 'GET', + HEAD: 'HEAD', + POST: 'POST', + PUT: 'PUT', + DELETE: 'DELETE', + CONNECT: 'CONNECT', + OPTIONS: 'OPTIONS', + TRACE: 'TRACE', + PATCH: 'PATCH', +} as const; + +/** + * HTTP request method. + */ +export type RequestMethod = + | 'METHOD_UNSPECIFIED' + | 0 + | 'GET' + | 1 + | 'HEAD' + | 2 + | 'POST' + | 3 + | 'PUT' + | 4 + | 'DELETE' + | 5 + | 'CONNECT' + | 6 + | 'OPTIONS' + | 7 + | 'TRACE' + | 8 + | 'PATCH' + | 9 + +/** + * HTTP request method. + */ +export type RequestMethod__Output = typeof RequestMethod[keyof typeof RequestMethod] diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts index 917d8a3df..41172d92f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/RoutingPriority.ts @@ -9,7 +9,33 @@ * upstream host. In the future Envoy will likely support true HTTP/2 priority * over a single upstream connection. */ -export enum RoutingPriority { - DEFAULT = 0, - HIGH = 1, -} +export const RoutingPriority = { + DEFAULT: 'DEFAULT', + HIGH: 'HIGH', +} as const; + +/** + * Envoy supports :ref:`upstream priority routing + * ` both at the route and the virtual + * cluster level. The current priority implementation uses different connection + * pool and circuit breaking settings for each priority level. This means that + * even for HTTP/2 requests, two physical connections will be used to an + * upstream host. In the future Envoy will likely support true HTTP/2 priority + * over a single upstream connection. + */ +export type RoutingPriority = + | 'DEFAULT' + | 0 + | 'HIGH' + | 1 + +/** + * Envoy supports :ref:`upstream priority routing + * ` both at the route and the virtual + * cluster level. The current priority implementation uses different connection + * pool and circuit breaking settings for each priority level. This means that + * even for HTTP/2 requests, two physical connections will be used to an + * upstream host. In the future Envoy will likely support true HTTP/2 priority + * over a single upstream connection. + */ +export type RoutingPriority__Output = typeof RoutingPriority[keyof typeof RoutingPriority] diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts index 3912fd1cf..939c4fd3d 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SelfConfigSource.ts @@ -1,6 +1,6 @@ // Original file: deps/envoy-api/envoy/config/core/v3/config_source.proto -import type { ApiVersion as _envoy_config_core_v3_ApiVersion } from '../../../../envoy/config/core/v3/ApiVersion'; +import type { ApiVersion as _envoy_config_core_v3_ApiVersion, ApiVersion__Output as _envoy_config_core_v3_ApiVersion__Output } from '../../../../envoy/config/core/v3/ApiVersion'; /** * [#not-implemented-hide:] @@ -13,7 +13,7 @@ export interface SelfConfigSource { * API version for xDS transport protocol. This describes the xDS gRPC/REST * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ - 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion | keyof typeof _envoy_config_core_v3_ApiVersion); + 'transport_api_version'?: (_envoy_config_core_v3_ApiVersion); } /** @@ -27,5 +27,5 @@ export interface SelfConfigSource__Output { * API version for xDS transport protocol. This describes the xDS gRPC/REST * endpoint and version of [Delta]DiscoveryRequest/Response used on the wire. */ - 'transport_api_version': (keyof typeof _envoy_config_core_v3_ApiVersion); + 'transport_api_version': (_envoy_config_core_v3_ApiVersion__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts index ae87b9edd..f939393fb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketAddress.ts @@ -3,16 +3,24 @@ // Original file: deps/envoy-api/envoy/config/core/v3/address.proto -export enum _envoy_config_core_v3_SocketAddress_Protocol { - TCP = 0, - UDP = 1, -} +export const _envoy_config_core_v3_SocketAddress_Protocol = { + TCP: 'TCP', + UDP: 'UDP', +} as const; + +export type _envoy_config_core_v3_SocketAddress_Protocol = + | 'TCP' + | 0 + | 'UDP' + | 1 + +export type _envoy_config_core_v3_SocketAddress_Protocol__Output = typeof _envoy_config_core_v3_SocketAddress_Protocol[keyof typeof _envoy_config_core_v3_SocketAddress_Protocol] /** * [#next-free-field: 7] */ export interface SocketAddress { - 'protocol'?: (_envoy_config_core_v3_SocketAddress_Protocol | keyof typeof _envoy_config_core_v3_SocketAddress_Protocol); + 'protocol'?: (_envoy_config_core_v3_SocketAddress_Protocol); /** * The address for this socket. :ref:`Listeners ` will bind * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` @@ -56,7 +64,7 @@ export interface SocketAddress { * [#next-free-field: 7] */ export interface SocketAddress__Output { - 'protocol': (keyof typeof _envoy_config_core_v3_SocketAddress_Protocol); + 'protocol': (_envoy_config_core_v3_SocketAddress_Protocol__Output); /** * The address for this socket. :ref:`Listeners ` will bind * to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts index edff50a63..9b3bc0019 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SocketOption.ts @@ -4,20 +4,39 @@ import type { Long } from '@grpc/proto-loader'; // Original file: deps/envoy-api/envoy/config/core/v3/socket_option.proto -export enum _envoy_config_core_v3_SocketOption_SocketState { +export const _envoy_config_core_v3_SocketOption_SocketState = { /** * Socket options are applied after socket creation but before binding the socket to a port */ - STATE_PREBIND = 0, + STATE_PREBIND: 'STATE_PREBIND', /** * Socket options are applied after binding the socket to a port but before calling listen() */ - STATE_BOUND = 1, + STATE_BOUND: 'STATE_BOUND', /** * Socket options are applied after calling listen() */ - STATE_LISTENING = 2, -} + STATE_LISTENING: 'STATE_LISTENING', +} as const; + +export type _envoy_config_core_v3_SocketOption_SocketState = + /** + * Socket options are applied after socket creation but before binding the socket to a port + */ + | 'STATE_PREBIND' + | 0 + /** + * Socket options are applied after binding the socket to a port but before calling listen() + */ + | 'STATE_BOUND' + | 1 + /** + * Socket options are applied after calling listen() + */ + | 'STATE_LISTENING' + | 2 + +export type _envoy_config_core_v3_SocketOption_SocketState__Output = typeof _envoy_config_core_v3_SocketOption_SocketState[keyof typeof _envoy_config_core_v3_SocketOption_SocketState] /** * Generic socket option message. This would be used to set socket options that @@ -70,7 +89,7 @@ export interface SocketOption { * The state in which the option will be applied. When used in BindConfig * STATE_PREBIND is currently the only valid value. */ - 'state'?: (_envoy_config_core_v3_SocketOption_SocketState | keyof typeof _envoy_config_core_v3_SocketOption_SocketState); + 'state'?: (_envoy_config_core_v3_SocketOption_SocketState); 'value'?: "int_value"|"buf_value"; } @@ -125,6 +144,6 @@ export interface SocketOption__Output { * The state in which the option will be applied. When used in BindConfig * STATE_PREBIND is currently the only valid value. */ - 'state': (keyof typeof _envoy_config_core_v3_SocketOption_SocketState); + 'state': (_envoy_config_core_v3_SocketOption_SocketState__Output); 'value': "int_value"|"buf_value"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts index be0237e38..01a97441c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/SubstitutionFormatString.ts @@ -28,6 +28,7 @@ export interface SubstitutionFormatString { * upstream connect error:503:path=/foo * * Deprecated in favor of :ref:`text_format_source `. To migrate text format strings, use the :ref:`inline_string ` field. + * @deprecated */ 'text_format'?: (string); /** @@ -125,6 +126,7 @@ export interface SubstitutionFormatString__Output { * upstream connect error:503:path=/foo * * Deprecated in favor of :ref:`text_format_source `. To migrate text format strings, use the :ref:`inline_string ` field. + * @deprecated */ 'text_format'?: (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts index b68323b09..e450d43cb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/TrafficDirection.ts @@ -3,17 +3,42 @@ /** * Identifies the direction of the traffic relative to the local Envoy. */ -export enum TrafficDirection { +export const TrafficDirection = { /** * Default option is unspecified. */ - UNSPECIFIED = 0, + UNSPECIFIED: 'UNSPECIFIED', /** * The transport is used for incoming traffic. */ - INBOUND = 1, + INBOUND: 'INBOUND', /** * The transport is used for outgoing traffic. */ - OUTBOUND = 2, -} + OUTBOUND: 'OUTBOUND', +} as const; + +/** + * Identifies the direction of the traffic relative to the local Envoy. + */ +export type TrafficDirection = + /** + * Default option is unspecified. + */ + | 'UNSPECIFIED' + | 0 + /** + * The transport is used for incoming traffic. + */ + | 'INBOUND' + | 1 + /** + * The transport is used for outgoing traffic. + */ + | 'OUTBOUND' + | 2 + +/** + * Identifies the direction of the traffic relative to the local Envoy. + */ +export type TrafficDirection__Output = typeof TrafficDirection[keyof typeof TrafficDirection] diff --git a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts index fe1b038db..f5e38b2b4 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/core/v3/UdpSocketConfig.ts @@ -2,7 +2,6 @@ import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { Long } from '@grpc/proto-loader'; /** * Generic UDP socket configuration. diff --git a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts index 8d184b8a0..4130eb838 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/endpoint/v3/LbEndpoint.ts @@ -1,7 +1,7 @@ // Original file: deps/envoy-api/envoy/config/endpoint/v3/endpoint_components.proto import type { Endpoint as _envoy_config_endpoint_v3_Endpoint, Endpoint__Output as _envoy_config_endpoint_v3_Endpoint__Output } from '../../../../envoy/config/endpoint/v3/Endpoint'; -import type { HealthStatus as _envoy_config_core_v3_HealthStatus } from '../../../../envoy/config/core/v3/HealthStatus'; +import type { HealthStatus as _envoy_config_core_v3_HealthStatus, HealthStatus__Output as _envoy_config_core_v3_HealthStatus__Output } from '../../../../envoy/config/core/v3/HealthStatus'; import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; @@ -14,7 +14,7 @@ export interface LbEndpoint { /** * Optional health status when known and supplied by EDS server. */ - 'health_status'?: (_envoy_config_core_v3_HealthStatus | keyof typeof _envoy_config_core_v3_HealthStatus); + 'health_status'?: (_envoy_config_core_v3_HealthStatus); /** * The endpoint metadata specifies values that may be used by the load * balancer to select endpoints in a cluster for a given request. The filter @@ -56,7 +56,7 @@ export interface LbEndpoint__Output { /** * Optional health status when known and supplied by EDS server. */ - 'health_status': (keyof typeof _envoy_config_core_v3_HealthStatus); + 'health_status': (_envoy_config_core_v3_HealthStatus__Output); /** * The endpoint metadata specifies values that may be used by the load * balancer to select endpoints in a cluster for a given request. The filter diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts index f27000d71..77b08f48e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChain.ts @@ -79,6 +79,7 @@ export interface FilterChain { * This field is deprecated. Add a * :ref:`PROXY protocol listener filter ` * explicitly instead. + * @deprecated */ 'use_proxy_proto'?: (_google_protobuf_BoolValue | null); /** @@ -149,6 +150,7 @@ export interface FilterChain__Output { * This field is deprecated. Add a * :ref:`PROXY protocol listener filter ` * explicitly instead. + * @deprecated */ 'use_proxy_proto': (_google_protobuf_BoolValue__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts index 87b1503cb..fcbfc1b3e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/FilterChainMatch.ts @@ -5,20 +5,39 @@ import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output a // Original file: deps/envoy-api/envoy/config/listener/v3/listener_components.proto -export enum _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType { +export const _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType = { /** * Any connection source matches. */ - ANY = 0, + ANY: 'ANY', /** * Match a connection originating from the same host. */ - SAME_IP_OR_LOOPBACK = 1, + SAME_IP_OR_LOOPBACK: 'SAME_IP_OR_LOOPBACK', /** * Match a connection originating from a different host. */ - EXTERNAL = 2, -} + EXTERNAL: 'EXTERNAL', +} as const; + +export type _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType = + /** + * Any connection source matches. + */ + | 'ANY' + | 0 + /** + * Match a connection originating from the same host. + */ + | 'SAME_IP_OR_LOOPBACK' + | 1 + /** + * Match a connection originating from a different host. + */ + | 'EXTERNAL' + | 2 + +export type _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType__Output = typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType[keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType] /** * Specifies the match criteria for selecting a specific filter chain for a @@ -154,7 +173,7 @@ export interface FilterChainMatch { /** * Specifies the connection source IP match type. Can be any, local or external network. */ - 'source_type'?: (_envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType | keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); + 'source_type'?: (_envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); /** * The criteria is satisfied if the directly connected source IP address of the downstream * connection is contained in at least one of the specified subnets. If the parameter is not @@ -297,7 +316,7 @@ export interface FilterChainMatch__Output { /** * Specifies the connection source IP match type. Can be any, local or external network. */ - 'source_type': (keyof typeof _envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType); + 'source_type': (_envoy_config_listener_v3_FilterChainMatch_ConnectionSourceType__Output); /** * The criteria is satisfied if the directly connected source IP address of the downstream * connection is contained in at least one of the specified subnets. If the parameter is not diff --git a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts index ca12fc6de..8897eacf5 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/listener/v3/Listener.ts @@ -8,7 +8,7 @@ import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _e import type { ListenerFilter as _envoy_config_listener_v3_ListenerFilter, ListenerFilter__Output as _envoy_config_listener_v3_ListenerFilter__Output } from '../../../../envoy/config/listener/v3/ListenerFilter'; import type { SocketOption as _envoy_config_core_v3_SocketOption, SocketOption__Output as _envoy_config_core_v3_SocketOption__Output } from '../../../../envoy/config/core/v3/SocketOption'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { TrafficDirection as _envoy_config_core_v3_TrafficDirection } from '../../../../envoy/config/core/v3/TrafficDirection'; +import type { TrafficDirection as _envoy_config_core_v3_TrafficDirection, TrafficDirection__Output as _envoy_config_core_v3_TrafficDirection__Output } from '../../../../envoy/config/core/v3/TrafficDirection'; import type { UdpListenerConfig as _envoy_config_listener_v3_UdpListenerConfig, UdpListenerConfig__Output as _envoy_config_listener_v3_UdpListenerConfig__Output } from '../../../../envoy/config/listener/v3/UdpListenerConfig'; import type { ApiListener as _envoy_config_listener_v3_ApiListener, ApiListener__Output as _envoy_config_listener_v3_ApiListener__Output } from '../../../../envoy/config/listener/v3/ApiListener'; import type { AccessLog as _envoy_config_accesslog_v3_AccessLog, AccessLog__Output as _envoy_config_accesslog_v3_AccessLog__Output } from '../../../../envoy/config/accesslog/v3/AccessLog'; @@ -82,19 +82,36 @@ export interface _envoy_config_listener_v3_Listener_DeprecatedV1__Output { // Original file: deps/envoy-api/envoy/config/listener/v3/listener.proto -export enum _envoy_config_listener_v3_Listener_DrainType { +export const _envoy_config_listener_v3_Listener_DrainType = { /** * Drain in response to calling /healthcheck/fail admin endpoint (along with the health check * filter), listener removal/modification, and hot restart. */ - DEFAULT = 0, + DEFAULT: 'DEFAULT', /** * Drain in response to listener removal/modification and hot restart. This setting does not * include /healthcheck/fail. This setting may be desirable if Envoy is hosting both ingress * and egress listeners. */ - MODIFY_ONLY = 1, -} + MODIFY_ONLY: 'MODIFY_ONLY', +} as const; + +export type _envoy_config_listener_v3_Listener_DrainType = + /** + * Drain in response to calling /healthcheck/fail admin endpoint (along with the health check + * filter), listener removal/modification, and hot restart. + */ + | 'DEFAULT' + | 0 + /** + * Drain in response to listener removal/modification and hot restart. This setting does not + * include /healthcheck/fail. This setting may be desirable if Envoy is hosting both ingress + * and egress listeners. + */ + | 'MODIFY_ONLY' + | 1 + +export type _envoy_config_listener_v3_Listener_DrainType__Output = typeof _envoy_config_listener_v3_Listener_DrainType[keyof typeof _envoy_config_listener_v3_Listener_DrainType] /** * A connection balancer implementation that does exact balancing. This means that a lock is @@ -176,12 +193,13 @@ export interface Listener { 'metadata'?: (_envoy_config_core_v3_Metadata | null); /** * [#not-implemented-hide:] + * @deprecated */ 'deprecated_v1'?: (_envoy_config_listener_v3_Listener_DeprecatedV1 | null); /** * The type of draining to perform at a listener-wide level. */ - 'drain_type'?: (_envoy_config_listener_v3_Listener_DrainType | keyof typeof _envoy_config_listener_v3_Listener_DrainType); + 'drain_type'?: (_envoy_config_listener_v3_Listener_DrainType); /** * Listener filters have the opportunity to manipulate and augment the connection metadata that * is used in connection filter chain matching, for example. These filters are run before any in @@ -256,7 +274,7 @@ export interface Listener { * This property is required on Windows for listeners using the original destination filter, * see :ref:`Original Destination `. */ - 'traffic_direction'?: (_envoy_config_core_v3_TrafficDirection | keyof typeof _envoy_config_core_v3_TrafficDirection); + 'traffic_direction'?: (_envoy_config_core_v3_TrafficDirection); /** * Whether a connection should be created when listener filters timeout. Default is false. * @@ -307,6 +325,7 @@ export interface Listener { 'connection_balance_config'?: (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig | null); /** * Deprecated. Use ``enable_reuse_port`` instead. + * @deprecated */ 'reuse_port'?: (boolean); /** @@ -466,12 +485,13 @@ export interface Listener__Output { 'metadata': (_envoy_config_core_v3_Metadata__Output | null); /** * [#not-implemented-hide:] + * @deprecated */ 'deprecated_v1': (_envoy_config_listener_v3_Listener_DeprecatedV1__Output | null); /** * The type of draining to perform at a listener-wide level. */ - 'drain_type': (keyof typeof _envoy_config_listener_v3_Listener_DrainType); + 'drain_type': (_envoy_config_listener_v3_Listener_DrainType__Output); /** * Listener filters have the opportunity to manipulate and augment the connection metadata that * is used in connection filter chain matching, for example. These filters are run before any in @@ -546,7 +566,7 @@ export interface Listener__Output { * This property is required on Windows for listeners using the original destination filter, * see :ref:`Original Destination `. */ - 'traffic_direction': (keyof typeof _envoy_config_core_v3_TrafficDirection); + 'traffic_direction': (_envoy_config_core_v3_TrafficDirection__Output); /** * Whether a connection should be created when listener filters timeout. Default is false. * @@ -597,6 +617,7 @@ export interface Listener__Output { 'connection_balance_config': (_envoy_config_listener_v3_Listener_ConnectionBalanceConfig__Output | null); /** * Deprecated. Use ``enable_reuse_port`` instead. + * @deprecated */ 'reuse_port': (boolean); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts deleted file mode 100644 index 4cf705f10..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/DogStatsdSink.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - -import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; -import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../google/protobuf/UInt64Value'; -import type { Long } from '@grpc/proto-loader'; - -/** - * Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. - * The sink emits stats with `DogStatsD `_ - * compatible tags. Tags are configurable via :ref:`StatsConfig - * `. - * [#extension: envoy.stat_sinks.dog_statsd] - */ -export interface DogStatsdSink { - /** - * The UDP address of a running DogStatsD compliant listener. If specified, - * statistics will be flushed to this address. - */ - 'address'?: (_envoy_config_core_v3_Address | null); - /** - * Optional custom metric name prefix. See :ref:`StatsdSink's prefix field - * ` for more details. - */ - 'prefix'?: (string); - /** - * Optional max datagram size to use when sending UDP messages. By default Envoy - * will emit one metric per datagram. By specifying a max-size larger than a single - * metric, Envoy will emit multiple, new-line separated metrics. The max datagram - * size should not exceed your network's MTU. - * - * Note that this value may not be respected if smaller than a single metric. - */ - 'max_bytes_per_datagram'?: (_google_protobuf_UInt64Value | null); - 'dog_statsd_specifier'?: "address"; -} - -/** - * Stats configuration proto schema for built-in *envoy.stat_sinks.dog_statsd* sink. - * The sink emits stats with `DogStatsD `_ - * compatible tags. Tags are configurable via :ref:`StatsConfig - * `. - * [#extension: envoy.stat_sinks.dog_statsd] - */ -export interface DogStatsdSink__Output { - /** - * The UDP address of a running DogStatsD compliant listener. If specified, - * statistics will be flushed to this address. - */ - 'address'?: (_envoy_config_core_v3_Address__Output | null); - /** - * Optional custom metric name prefix. See :ref:`StatsdSink's prefix field - * ` for more details. - */ - 'prefix': (string); - /** - * Optional max datagram size to use when sending UDP messages. By default Envoy - * will emit one metric per datagram. By specifying a max-size larger than a single - * metric, Envoy will emit multiple, new-line separated metrics. The max datagram - * size should not exceed your network's MTU. - * - * Note that this value may not be respected if smaller than a single metric. - */ - 'max_bytes_per_datagram': (_google_protobuf_UInt64Value__Output | null); - 'dog_statsd_specifier': "address"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts deleted file mode 100644 index 036958a49..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HistogramBucketSettings.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - -import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; - -/** - * Specifies a matcher for stats and the buckets that matching stats should use. - */ -export interface HistogramBucketSettings { - /** - * The stats that this rule applies to. The match is applied to the original stat name - * before tag-extraction, for example `cluster.exampleclustername.upstream_cx_length_ms`. - */ - 'match'?: (_envoy_type_matcher_v3_StringMatcher | null); - /** - * Each value is the upper bound of a bucket. Each bucket must be greater than 0 and unique. - * The order of the buckets does not matter. - */ - 'buckets'?: (number | string)[]; -} - -/** - * Specifies a matcher for stats and the buckets that matching stats should use. - */ -export interface HistogramBucketSettings__Output { - /** - * The stats that this rule applies to. The match is applied to the original stat name - * before tag-extraction, for example `cluster.exampleclustername.upstream_cx_length_ms`. - */ - 'match': (_envoy_type_matcher_v3_StringMatcher__Output | null); - /** - * Each value is the upper bound of a bucket. Each bucket must be greater than 0 and unique. - * The order of the buckets does not matter. - */ - 'buckets': (number)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts deleted file mode 100644 index b8fb2ed8e..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/HystrixSink.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - -import type { Long } from '@grpc/proto-loader'; - -/** - * Stats configuration proto schema for built-in *envoy.stat_sinks.hystrix* sink. - * The sink emits stats in `text/event-stream - * `_ - * formatted stream for use by `Hystrix dashboard - * `_. - * - * Note that only a single HystrixSink should be configured. - * - * Streaming is started through an admin endpoint :http:get:`/hystrix_event_stream`. - * [#extension: envoy.stat_sinks.hystrix] - */ -export interface HystrixSink { - /** - * The number of buckets the rolling statistical window is divided into. - * - * Each time the sink is flushed, all relevant Envoy statistics are sampled and - * added to the rolling window (removing the oldest samples in the window - * in the process). The sink then outputs the aggregate statistics across the - * current rolling window to the event stream(s). - * - * rolling_window(ms) = stats_flush_interval(ms) * num_of_buckets - * - * More detailed explanation can be found in `Hystrix wiki - * `_. - */ - 'num_buckets'?: (number | string | Long); -} - -/** - * Stats configuration proto schema for built-in *envoy.stat_sinks.hystrix* sink. - * The sink emits stats in `text/event-stream - * `_ - * formatted stream for use by `Hystrix dashboard - * `_. - * - * Note that only a single HystrixSink should be configured. - * - * Streaming is started through an admin endpoint :http:get:`/hystrix_event_stream`. - * [#extension: envoy.stat_sinks.hystrix] - */ -export interface HystrixSink__Output { - /** - * The number of buckets the rolling statistical window is divided into. - * - * Each time the sink is flushed, all relevant Envoy statistics are sampled and - * added to the rolling window (removing the oldest samples in the window - * in the process). The sink then outputs the aggregate statistics across the - * current rolling window to the event stream(s). - * - * rolling_window(ms) = stats_flush_interval(ms) * num_of_buckets - * - * More detailed explanation can be found in `Hystrix wiki - * `_. - */ - 'num_buckets': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts deleted file mode 100644 index df5d7c7e4..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsConfig.ts +++ /dev/null @@ -1,148 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - -import type { TagSpecifier as _envoy_config_metrics_v3_TagSpecifier, TagSpecifier__Output as _envoy_config_metrics_v3_TagSpecifier__Output } from '../../../../envoy/config/metrics/v3/TagSpecifier'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; -import type { StatsMatcher as _envoy_config_metrics_v3_StatsMatcher, StatsMatcher__Output as _envoy_config_metrics_v3_StatsMatcher__Output } from '../../../../envoy/config/metrics/v3/StatsMatcher'; -import type { HistogramBucketSettings as _envoy_config_metrics_v3_HistogramBucketSettings, HistogramBucketSettings__Output as _envoy_config_metrics_v3_HistogramBucketSettings__Output } from '../../../../envoy/config/metrics/v3/HistogramBucketSettings'; - -/** - * Statistics configuration such as tagging. - */ -export interface StatsConfig { - /** - * Each stat name is iteratively processed through these tag specifiers. - * When a tag is matched, the first capture group is removed from the name so - * later :ref:`TagSpecifiers ` cannot match that - * same portion of the match. - */ - 'stats_tags'?: (_envoy_config_metrics_v3_TagSpecifier)[]; - /** - * Use all default tag regexes specified in Envoy. These can be combined with - * custom tags specified in :ref:`stats_tags - * `. They will be processed before - * the custom tags. - * - * .. note:: - * - * If any default tags are specified twice, the config will be considered - * invalid. - * - * See :repo:`well_known_names.h ` for a list of the - * default tags in Envoy. - * - * If not provided, the value is assumed to be true. - */ - 'use_all_default_tags'?: (_google_protobuf_BoolValue | null); - /** - * Inclusion/exclusion matcher for stat name creation. If not provided, all stats are instantiated - * as normal. Preventing the instantiation of certain families of stats can improve memory - * performance for Envoys running especially large configs. - * - * .. warning:: - * Excluding stats may affect Envoy's behavior in undocumented ways. See - * `issue #8771 `_ for more information. - * If any unexpected behavior changes are observed, please open a new issue immediately. - */ - 'stats_matcher'?: (_envoy_config_metrics_v3_StatsMatcher | null); - /** - * Defines rules for setting the histogram buckets. Rules are evaluated in order, and the first - * match is applied. If no match is found (or if no rules are set), the following default buckets - * are used: - * - * .. code-block:: json - * - * [ - * 0.5, - * 1, - * 5, - * 10, - * 25, - * 50, - * 100, - * 250, - * 500, - * 1000, - * 2500, - * 5000, - * 10000, - * 30000, - * 60000, - * 300000, - * 600000, - * 1800000, - * 3600000 - * ] - */ - 'histogram_bucket_settings'?: (_envoy_config_metrics_v3_HistogramBucketSettings)[]; -} - -/** - * Statistics configuration such as tagging. - */ -export interface StatsConfig__Output { - /** - * Each stat name is iteratively processed through these tag specifiers. - * When a tag is matched, the first capture group is removed from the name so - * later :ref:`TagSpecifiers ` cannot match that - * same portion of the match. - */ - 'stats_tags': (_envoy_config_metrics_v3_TagSpecifier__Output)[]; - /** - * Use all default tag regexes specified in Envoy. These can be combined with - * custom tags specified in :ref:`stats_tags - * `. They will be processed before - * the custom tags. - * - * .. note:: - * - * If any default tags are specified twice, the config will be considered - * invalid. - * - * See :repo:`well_known_names.h ` for a list of the - * default tags in Envoy. - * - * If not provided, the value is assumed to be true. - */ - 'use_all_default_tags': (_google_protobuf_BoolValue__Output | null); - /** - * Inclusion/exclusion matcher for stat name creation. If not provided, all stats are instantiated - * as normal. Preventing the instantiation of certain families of stats can improve memory - * performance for Envoys running especially large configs. - * - * .. warning:: - * Excluding stats may affect Envoy's behavior in undocumented ways. See - * `issue #8771 `_ for more information. - * If any unexpected behavior changes are observed, please open a new issue immediately. - */ - 'stats_matcher': (_envoy_config_metrics_v3_StatsMatcher__Output | null); - /** - * Defines rules for setting the histogram buckets. Rules are evaluated in order, and the first - * match is applied. If no match is found (or if no rules are set), the following default buckets - * are used: - * - * .. code-block:: json - * - * [ - * 0.5, - * 1, - * 5, - * 10, - * 25, - * 50, - * 100, - * 250, - * 500, - * 1000, - * 2500, - * 5000, - * 10000, - * 30000, - * 60000, - * 300000, - * 600000, - * 1800000, - * 3600000 - * ] - */ - 'histogram_bucket_settings': (_envoy_config_metrics_v3_HistogramBucketSettings__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts deleted file mode 100644 index 9df9e9529..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsMatcher.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - -import type { ListStringMatcher as _envoy_type_matcher_v3_ListStringMatcher, ListStringMatcher__Output as _envoy_type_matcher_v3_ListStringMatcher__Output } from '../../../../envoy/type/matcher/v3/ListStringMatcher'; - -/** - * Configuration for disabling stat instantiation. - */ -export interface StatsMatcher { - /** - * If `reject_all` is true, then all stats are disabled. If `reject_all` is false, then all - * stats are enabled. - */ - 'reject_all'?: (boolean); - /** - * Exclusive match. All stats are enabled except for those matching one of the supplied - * StringMatcher protos. - */ - 'exclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher | null); - /** - * Inclusive match. No stats are enabled except for those matching one of the supplied - * StringMatcher protos. - */ - 'inclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher | null); - 'stats_matcher'?: "reject_all"|"exclusion_list"|"inclusion_list"; -} - -/** - * Configuration for disabling stat instantiation. - */ -export interface StatsMatcher__Output { - /** - * If `reject_all` is true, then all stats are disabled. If `reject_all` is false, then all - * stats are enabled. - */ - 'reject_all'?: (boolean); - /** - * Exclusive match. All stats are enabled except for those matching one of the supplied - * StringMatcher protos. - */ - 'exclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher__Output | null); - /** - * Inclusive match. No stats are enabled except for those matching one of the supplied - * StringMatcher protos. - */ - 'inclusion_list'?: (_envoy_type_matcher_v3_ListStringMatcher__Output | null); - 'stats_matcher': "reject_all"|"exclusion_list"|"inclusion_list"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts deleted file mode 100644 index 3eb8926fa..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsSink.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; - -/** - * Configuration for pluggable stats sinks. - */ -export interface StatsSink { - /** - * The name of the stats sink to instantiate. The name must match a supported - * stats sink. - * See the :ref:`extensions listed in typed_config below ` for the default list of available stats sink. - * Sinks optionally support tagged/multiple dimensional metrics. - */ - 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any | null); - /** - * Stats sink specific configuration which depends on the sink being instantiated. See - * :ref:`StatsdSink ` for an example. - * [#extension-category: envoy.stats_sinks] - */ - 'config_type'?: "typed_config"; -} - -/** - * Configuration for pluggable stats sinks. - */ -export interface StatsSink__Output { - /** - * The name of the stats sink to instantiate. The name must match a supported - * stats sink. - * See the :ref:`extensions listed in typed_config below ` for the default list of available stats sink. - * Sinks optionally support tagged/multiple dimensional metrics. - */ - 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output | null); - /** - * Stats sink specific configuration which depends on the sink being instantiated. See - * :ref:`StatsdSink ` for an example. - * [#extension-category: envoy.stats_sinks] - */ - 'config_type': "typed_config"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts deleted file mode 100644 index 69d978920..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/StatsdSink.ts +++ /dev/null @@ -1,103 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - -import type { Address as _envoy_config_core_v3_Address, Address__Output as _envoy_config_core_v3_Address__Output } from '../../../../envoy/config/core/v3/Address'; - -/** - * Stats configuration proto schema for built-in *envoy.stat_sinks.statsd* sink. This sink does not support - * tagged metrics. - * [#extension: envoy.stat_sinks.statsd] - */ -export interface StatsdSink { - /** - * The UDP address of a running `statsd `_ - * compliant listener. If specified, statistics will be flushed to this - * address. - */ - 'address'?: (_envoy_config_core_v3_Address | null); - /** - * The name of a cluster that is running a TCP `statsd - * `_ compliant listener. If specified, - * Envoy will connect to this cluster to flush statistics. - */ - 'tcp_cluster_name'?: (string); - /** - * Optional custom prefix for StatsdSink. If - * specified, this will override the default prefix. - * For example: - * - * .. code-block:: json - * - * { - * "prefix" : "envoy-prod" - * } - * - * will change emitted stats to - * - * .. code-block:: cpp - * - * envoy-prod.test_counter:1|c - * envoy-prod.test_timer:5|ms - * - * Note that the default prefix, "envoy", will be used if a prefix is not - * specified. - * - * Stats with default prefix: - * - * .. code-block:: cpp - * - * envoy.test_counter:1|c - * envoy.test_timer:5|ms - */ - 'prefix'?: (string); - 'statsd_specifier'?: "address"|"tcp_cluster_name"; -} - -/** - * Stats configuration proto schema for built-in *envoy.stat_sinks.statsd* sink. This sink does not support - * tagged metrics. - * [#extension: envoy.stat_sinks.statsd] - */ -export interface StatsdSink__Output { - /** - * The UDP address of a running `statsd `_ - * compliant listener. If specified, statistics will be flushed to this - * address. - */ - 'address'?: (_envoy_config_core_v3_Address__Output | null); - /** - * The name of a cluster that is running a TCP `statsd - * `_ compliant listener. If specified, - * Envoy will connect to this cluster to flush statistics. - */ - 'tcp_cluster_name'?: (string); - /** - * Optional custom prefix for StatsdSink. If - * specified, this will override the default prefix. - * For example: - * - * .. code-block:: json - * - * { - * "prefix" : "envoy-prod" - * } - * - * will change emitted stats to - * - * .. code-block:: cpp - * - * envoy-prod.test_counter:1|c - * envoy-prod.test_timer:5|ms - * - * Note that the default prefix, "envoy", will be used if a prefix is not - * specified. - * - * Stats with default prefix: - * - * .. code-block:: cpp - * - * envoy.test_counter:1|c - * envoy.test_timer:5|ms - */ - 'prefix': (string); - 'statsd_specifier': "address"|"tcp_cluster_name"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts b/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts deleted file mode 100644 index 9b0bb2467..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/metrics/v3/TagSpecifier.ts +++ /dev/null @@ -1,174 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/metrics/v3/stats.proto - - -/** - * Designates a tag name and value pair. The value may be either a fixed value - * or a regex providing the value via capture groups. The specified tag will be - * unconditionally set if a fixed value, otherwise it will only be set if one - * or more capture groups in the regex match. - */ -export interface TagSpecifier { - /** - * Attaches an identifier to the tag values to identify the tag being in the - * sink. Envoy has a set of default names and regexes to extract dynamic - * portions of existing stats, which can be found in :repo:`well_known_names.h - * ` in the Envoy repository. If a :ref:`tag_name - * ` is provided in the config and - * neither :ref:`regex ` or - * :ref:`fixed_value ` were specified, - * Envoy will attempt to find that name in its set of defaults and use the accompanying regex. - * - * .. note:: - * - * It is invalid to specify the same tag name twice in a config. - */ - 'tag_name'?: (string); - /** - * Designates a tag to strip from the tag extracted name and provide as a named - * tag value for all statistics. This will only occur if any part of the name - * matches the regex provided with one or more capture groups. - * - * The first capture group identifies the portion of the name to remove. The - * second capture group (which will normally be nested inside the first) will - * designate the value of the tag for the statistic. If no second capture - * group is provided, the first will also be used to set the value of the tag. - * All other capture groups will be ignored. - * - * Example 1. a stat name ``cluster.foo_cluster.upstream_rq_timeout`` and - * one tag specifier: - * - * .. code-block:: json - * - * { - * "tag_name": "envoy.cluster_name", - * "regex": "^cluster\\.((.+?)\\.)" - * } - * - * Note that the regex will remove ``foo_cluster.`` making the tag extracted - * name ``cluster.upstream_rq_timeout`` and the tag value for - * ``envoy.cluster_name`` will be ``foo_cluster`` (note: there will be no - * ``.`` character because of the second capture group). - * - * Example 2. a stat name - * ``http.connection_manager_1.user_agent.ios.downstream_cx_total`` and two - * tag specifiers: - * - * .. code-block:: json - * - * [ - * { - * "tag_name": "envoy.http_user_agent", - * "regex": "^http(?=\\.).*?\\.user_agent\\.((.+?)\\.)\\w+?$" - * }, - * { - * "tag_name": "envoy.http_conn_manager_prefix", - * "regex": "^http\\.((.*?)\\.)" - * } - * ] - * - * The two regexes of the specifiers will be processed in the definition order. - * - * The first regex will remove ``ios.``, leaving the tag extracted name - * ``http.connection_manager_1.user_agent.downstream_cx_total``. The tag - * ``envoy.http_user_agent`` will be added with tag value ``ios``. - * - * The second regex will remove ``connection_manager_1.`` from the tag - * extracted name produced by the first regex - * ``http.connection_manager_1.user_agent.downstream_cx_total``, leaving - * ``http.user_agent.downstream_cx_total`` as the tag extracted name. The tag - * ``envoy.http_conn_manager_prefix`` will be added with the tag value - * ``connection_manager_1``. - */ - 'regex'?: (string); - /** - * Specifies a fixed tag value for the ``tag_name``. - */ - 'fixed_value'?: (string); - 'tag_value'?: "regex"|"fixed_value"; -} - -/** - * Designates a tag name and value pair. The value may be either a fixed value - * or a regex providing the value via capture groups. The specified tag will be - * unconditionally set if a fixed value, otherwise it will only be set if one - * or more capture groups in the regex match. - */ -export interface TagSpecifier__Output { - /** - * Attaches an identifier to the tag values to identify the tag being in the - * sink. Envoy has a set of default names and regexes to extract dynamic - * portions of existing stats, which can be found in :repo:`well_known_names.h - * ` in the Envoy repository. If a :ref:`tag_name - * ` is provided in the config and - * neither :ref:`regex ` or - * :ref:`fixed_value ` were specified, - * Envoy will attempt to find that name in its set of defaults and use the accompanying regex. - * - * .. note:: - * - * It is invalid to specify the same tag name twice in a config. - */ - 'tag_name': (string); - /** - * Designates a tag to strip from the tag extracted name and provide as a named - * tag value for all statistics. This will only occur if any part of the name - * matches the regex provided with one or more capture groups. - * - * The first capture group identifies the portion of the name to remove. The - * second capture group (which will normally be nested inside the first) will - * designate the value of the tag for the statistic. If no second capture - * group is provided, the first will also be used to set the value of the tag. - * All other capture groups will be ignored. - * - * Example 1. a stat name ``cluster.foo_cluster.upstream_rq_timeout`` and - * one tag specifier: - * - * .. code-block:: json - * - * { - * "tag_name": "envoy.cluster_name", - * "regex": "^cluster\\.((.+?)\\.)" - * } - * - * Note that the regex will remove ``foo_cluster.`` making the tag extracted - * name ``cluster.upstream_rq_timeout`` and the tag value for - * ``envoy.cluster_name`` will be ``foo_cluster`` (note: there will be no - * ``.`` character because of the second capture group). - * - * Example 2. a stat name - * ``http.connection_manager_1.user_agent.ios.downstream_cx_total`` and two - * tag specifiers: - * - * .. code-block:: json - * - * [ - * { - * "tag_name": "envoy.http_user_agent", - * "regex": "^http(?=\\.).*?\\.user_agent\\.((.+?)\\.)\\w+?$" - * }, - * { - * "tag_name": "envoy.http_conn_manager_prefix", - * "regex": "^http\\.((.*?)\\.)" - * } - * ] - * - * The two regexes of the specifiers will be processed in the definition order. - * - * The first regex will remove ``ios.``, leaving the tag extracted name - * ``http.connection_manager_1.user_agent.downstream_cx_total``. The tag - * ``envoy.http_user_agent`` will be added with tag value ``ios``. - * - * The second regex will remove ``connection_manager_1.`` from the tag - * extracted name produced by the first regex - * ``http.connection_manager_1.user_agent.downstream_cx_total``, leaving - * ``http.user_agent.downstream_cx_total`` as the tag extracted name. The tag - * ``envoy.http_conn_manager_prefix`` will be added with the tag value - * ``connection_manager_1``. - */ - 'regex'?: (string); - /** - * Specifies a fixed tag value for the ``tag_name``. - */ - 'fixed_value'?: (string); - 'tag_value': "regex"|"fixed_value"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts deleted file mode 100644 index b3fbe1459..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/BufferFactoryConfig.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - - -/** - * Configuration for which accounts the WatermarkBuffer Factories should - * track. - */ -export interface BufferFactoryConfig { - /** - * The minimum power of two at which Envoy starts tracking an account. - * - * Envoy has 8 power of two buckets starting with the provided exponent below. - * Concretely the 1st bucket contains accounts for streams that use - * [2^minimum_account_to_track_power_of_two, - * 2^(minimum_account_to_track_power_of_two + 1)) bytes. - * With the 8th bucket tracking accounts - * >= 128 * 2^minimum_account_to_track_power_of_two. - * - * The maximum value is 56, since we're using uint64_t for bytes counting, - * and that's the last value that would use the 8 buckets. In practice, - * we don't expect the proxy to be holding 2^56 bytes. - * - * If omitted, Envoy should not do any tracking. - */ - 'minimum_account_to_track_power_of_two'?: (number); -} - -/** - * Configuration for which accounts the WatermarkBuffer Factories should - * track. - */ -export interface BufferFactoryConfig__Output { - /** - * The minimum power of two at which Envoy starts tracking an account. - * - * Envoy has 8 power of two buckets starting with the provided exponent below. - * Concretely the 1st bucket contains accounts for streams that use - * [2^minimum_account_to_track_power_of_two, - * 2^(minimum_account_to_track_power_of_two + 1)) bytes. - * With the 8th bucket tracking accounts - * >= 128 * 2^minimum_account_to_track_power_of_two. - * - * The maximum value is 56, since we're using uint64_t for bytes counting, - * and that's the last value that would use the 8 buckets. In practice, - * we don't expect the proxy to be holding 2^56 bytes. - * - * If omitted, Envoy should not do any tracking. - */ - 'minimum_account_to_track_power_of_two': (number); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts deleted file mode 100644 index 84f4db34b..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadAction.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - -import type { Trigger as _envoy_config_overload_v3_Trigger, Trigger__Output as _envoy_config_overload_v3_Trigger__Output } from '../../../../envoy/config/overload/v3/Trigger'; -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; - -export interface OverloadAction { - /** - * The name of the overload action. This is just a well-known string that listeners can - * use for registering callbacks. Custom overload actions should be named using reverse - * DNS to ensure uniqueness. - */ - 'name'?: (string); - /** - * A set of triggers for this action. The state of the action is the maximum - * state of all triggers, which can be scaling between 0 and 1 or saturated. Listeners - * are notified when the overload action changes state. - */ - 'triggers'?: (_envoy_config_overload_v3_Trigger)[]; - /** - * Configuration for the action being instantiated. - */ - 'typed_config'?: (_google_protobuf_Any | null); -} - -export interface OverloadAction__Output { - /** - * The name of the overload action. This is just a well-known string that listeners can - * use for registering callbacks. Custom overload actions should be named using reverse - * DNS to ensure uniqueness. - */ - 'name': (string); - /** - * A set of triggers for this action. The state of the action is the maximum - * state of all triggers, which can be scaling between 0 and 1 or saturated. Listeners - * are notified when the overload action changes state. - */ - 'triggers': (_envoy_config_overload_v3_Trigger__Output)[]; - /** - * Configuration for the action being instantiated. - */ - 'typed_config': (_google_protobuf_Any__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts deleted file mode 100644 index e7f75b8e9..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/OverloadManager.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { ResourceMonitor as _envoy_config_overload_v3_ResourceMonitor, ResourceMonitor__Output as _envoy_config_overload_v3_ResourceMonitor__Output } from '../../../../envoy/config/overload/v3/ResourceMonitor'; -import type { OverloadAction as _envoy_config_overload_v3_OverloadAction, OverloadAction__Output as _envoy_config_overload_v3_OverloadAction__Output } from '../../../../envoy/config/overload/v3/OverloadAction'; -import type { BufferFactoryConfig as _envoy_config_overload_v3_BufferFactoryConfig, BufferFactoryConfig__Output as _envoy_config_overload_v3_BufferFactoryConfig__Output } from '../../../../envoy/config/overload/v3/BufferFactoryConfig'; - -export interface OverloadManager { - /** - * The interval for refreshing resource usage. - */ - 'refresh_interval'?: (_google_protobuf_Duration | null); - /** - * The set of resources to monitor. - */ - 'resource_monitors'?: (_envoy_config_overload_v3_ResourceMonitor)[]; - /** - * The set of overload actions. - */ - 'actions'?: (_envoy_config_overload_v3_OverloadAction)[]; - /** - * Configuration for buffer factory. - */ - 'buffer_factory_config'?: (_envoy_config_overload_v3_BufferFactoryConfig | null); -} - -export interface OverloadManager__Output { - /** - * The interval for refreshing resource usage. - */ - 'refresh_interval': (_google_protobuf_Duration__Output | null); - /** - * The set of resources to monitor. - */ - 'resource_monitors': (_envoy_config_overload_v3_ResourceMonitor__Output)[]; - /** - * The set of overload actions. - */ - 'actions': (_envoy_config_overload_v3_OverloadAction__Output)[]; - /** - * Configuration for buffer factory. - */ - 'buffer_factory_config': (_envoy_config_overload_v3_BufferFactoryConfig__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts deleted file mode 100644 index 02fde2411..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ResourceMonitor.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; - -export interface ResourceMonitor { - /** - * The name of the resource monitor to instantiate. Must match a registered - * resource monitor type. - * See the :ref:`extensions listed in typed_config below ` for the default list of available resource monitor. - */ - 'name'?: (string); - 'typed_config'?: (_google_protobuf_Any | null); - /** - * Configuration for the resource monitor being instantiated. - * [#extension-category: envoy.resource_monitors] - */ - 'config_type'?: "typed_config"; -} - -export interface ResourceMonitor__Output { - /** - * The name of the resource monitor to instantiate. Must match a registered - * resource monitor type. - * See the :ref:`extensions listed in typed_config below ` for the default list of available resource monitor. - */ - 'name': (string); - 'typed_config'?: (_google_protobuf_Any__Output | null); - /** - * Configuration for the resource monitor being instantiated. - * [#extension-category: envoy.resource_monitors] - */ - 'config_type': "typed_config"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts deleted file mode 100644 index bb48fe3f6..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaleTimersOverloadActionConfig.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - -import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; -import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../envoy/type/v3/Percent'; - -export interface _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer { - /** - * The type of timer this minimum applies to. - */ - 'timer'?: (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType | keyof typeof _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType); - /** - * Sets the minimum duration as an absolute value. - */ - 'min_timeout'?: (_google_protobuf_Duration | null); - /** - * Sets the minimum duration as a percentage of the maximum value. - */ - 'min_scale'?: (_envoy_type_v3_Percent | null); - 'overload_adjust'?: "min_timeout"|"min_scale"; -} - -export interface _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer__Output { - /** - * The type of timer this minimum applies to. - */ - 'timer': (keyof typeof _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType); - /** - * Sets the minimum duration as an absolute value. - */ - 'min_timeout'?: (_google_protobuf_Duration__Output | null); - /** - * Sets the minimum duration as a percentage of the maximum value. - */ - 'min_scale'?: (_envoy_type_v3_Percent__Output | null); - 'overload_adjust': "min_timeout"|"min_scale"; -} - -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - -export enum _envoy_config_overload_v3_ScaleTimersOverloadActionConfig_TimerType { - /** - * Unsupported value; users must explicitly specify the timer they want scaled. - */ - UNSPECIFIED = 0, - /** - * Adjusts the idle timer for downstream HTTP connections that takes effect when there are no active streams. - * This affects the value of :ref:`HttpConnectionManager.common_http_protocol_options.idle_timeout - * ` - */ - HTTP_DOWNSTREAM_CONNECTION_IDLE = 1, - /** - * Adjusts the idle timer for HTTP streams initiated by downstream clients. - * This affects the value of :ref:`RouteAction.idle_timeout ` and - * :ref:`HttpConnectionManager.stream_idle_timeout - * ` - */ - HTTP_DOWNSTREAM_STREAM_IDLE = 2, - /** - * Adjusts the timer for how long downstream clients have to finish transport-level negotiations - * before the connection is closed. - * This affects the value of - * :ref:`FilterChain.transport_socket_connect_timeout `. - */ - TRANSPORT_SOCKET_CONNECT = 3, -} - -/** - * Typed configuration for the "envoy.overload_actions.reduce_timeouts" action. See - * :ref:`the docs ` for an example of how to configure - * the action with different timeouts and minimum values. - */ -export interface ScaleTimersOverloadActionConfig { - /** - * A set of timer scaling rules to be applied. - */ - 'timer_scale_factors'?: (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer)[]; -} - -/** - * Typed configuration for the "envoy.overload_actions.reduce_timeouts" action. See - * :ref:`the docs ` for an example of how to configure - * the action with different timeouts and minimum values. - */ -export interface ScaleTimersOverloadActionConfig__Output { - /** - * A set of timer scaling rules to be applied. - */ - 'timer_scale_factors': (_envoy_config_overload_v3_ScaleTimersOverloadActionConfig_ScaleTimer__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts deleted file mode 100644 index 8c6574f56..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ScaledTrigger.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - - -export interface ScaledTrigger { - /** - * If the resource pressure is greater than this value, the trigger will be in the - * :ref:`scaling ` state with value - * `(pressure - scaling_threshold) / (saturation_threshold - scaling_threshold)`. - */ - 'scaling_threshold'?: (number | string); - /** - * If the resource pressure is greater than this value, the trigger will enter saturation. - */ - 'saturation_threshold'?: (number | string); -} - -export interface ScaledTrigger__Output { - /** - * If the resource pressure is greater than this value, the trigger will be in the - * :ref:`scaling ` state with value - * `(pressure - scaling_threshold) / (saturation_threshold - scaling_threshold)`. - */ - 'scaling_threshold': (number); - /** - * If the resource pressure is greater than this value, the trigger will enter saturation. - */ - 'saturation_threshold': (number); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts deleted file mode 100644 index b02ddd47d..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/ThresholdTrigger.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - - -export interface ThresholdTrigger { - /** - * If the resource pressure is greater than or equal to this value, the trigger - * will enter saturation. - */ - 'value'?: (number | string); -} - -export interface ThresholdTrigger__Output { - /** - * If the resource pressure is greater than or equal to this value, the trigger - * will enter saturation. - */ - 'value': (number); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts b/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts deleted file mode 100644 index 38f360ee7..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/config/overload/v3/Trigger.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Original file: deps/envoy-api/envoy/config/overload/v3/overload.proto - -import type { ThresholdTrigger as _envoy_config_overload_v3_ThresholdTrigger, ThresholdTrigger__Output as _envoy_config_overload_v3_ThresholdTrigger__Output } from '../../../../envoy/config/overload/v3/ThresholdTrigger'; -import type { ScaledTrigger as _envoy_config_overload_v3_ScaledTrigger, ScaledTrigger__Output as _envoy_config_overload_v3_ScaledTrigger__Output } from '../../../../envoy/config/overload/v3/ScaledTrigger'; - -export interface Trigger { - /** - * The name of the resource this is a trigger for. - */ - 'name'?: (string); - 'threshold'?: (_envoy_config_overload_v3_ThresholdTrigger | null); - 'scaled'?: (_envoy_config_overload_v3_ScaledTrigger | null); - 'trigger_oneof'?: "threshold"|"scaled"; -} - -export interface Trigger__Output { - /** - * The name of the resource this is a trigger for. - */ - 'name': (string); - 'threshold'?: (_envoy_config_overload_v3_ThresholdTrigger__Output | null); - 'scaled'?: (_envoy_config_overload_v3_ScaledTrigger__Output | null); - 'trigger_oneof': "threshold"|"scaled"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts index e073a8f13..b5b085ae7 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/HeaderMatcher.ts @@ -3,7 +3,6 @@ import type { Int64Range as _envoy_type_v3_Int64Range, Int64Range__Output as _envoy_type_v3_Int64Range__Output } from '../../../../envoy/type/v3/Int64Range'; import type { RegexMatcher as _envoy_type_matcher_v3_RegexMatcher, RegexMatcher__Output as _envoy_type_matcher_v3_RegexMatcher__Output } from '../../../../envoy/type/matcher/v3/RegexMatcher'; import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher'; -import type { Long } from '@grpc/proto-loader'; /** * .. attention:: @@ -42,6 +41,7 @@ export interface HeaderMatcher { /** * If specified, header match will be performed based on the value of the header. * This field is deprecated. Please use :ref:`string_match `. + * @deprecated */ 'exact_match'?: (string); /** @@ -80,6 +80,7 @@ export interface HeaderMatcher { * Examples: * * * The prefix ``abcd`` matches the value ``abcdxyz``, but not for ``abcxyz``. + * @deprecated */ 'prefix_match'?: (string); /** @@ -90,6 +91,7 @@ export interface HeaderMatcher { * Examples: * * * The suffix ``abcd`` matches the value ``xyzabcd``, but not for ``xyzbcd``. + * @deprecated */ 'suffix_match'?: (string); /** @@ -97,6 +99,7 @@ export interface HeaderMatcher { * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. * This field is deprecated. Please use :ref:`string_match `. + * @deprecated */ 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher | null); /** @@ -108,6 +111,7 @@ export interface HeaderMatcher { * Examples: * * * The value ``abcd`` matches the value ``xyzabcdpqr``, but not for ``xyzbcdpqr``. + * @deprecated */ 'contains_match'?: (string); /** @@ -186,6 +190,7 @@ export interface HeaderMatcher__Output { /** * If specified, header match will be performed based on the value of the header. * This field is deprecated. Please use :ref:`string_match `. + * @deprecated */ 'exact_match'?: (string); /** @@ -224,6 +229,7 @@ export interface HeaderMatcher__Output { * Examples: * * * The prefix ``abcd`` matches the value ``abcdxyz``, but not for ``abcxyz``. + * @deprecated */ 'prefix_match'?: (string); /** @@ -234,6 +240,7 @@ export interface HeaderMatcher__Output { * Examples: * * * The suffix ``abcd`` matches the value ``xyzabcd``, but not for ``xyzbcd``. + * @deprecated */ 'suffix_match'?: (string); /** @@ -241,6 +248,7 @@ export interface HeaderMatcher__Output { * header value must match the regex. The rule will not match if only a subsequence of the * request header value matches the regex. * This field is deprecated. Please use :ref:`string_match `. + * @deprecated */ 'safe_regex_match'?: (_envoy_type_matcher_v3_RegexMatcher__Output | null); /** @@ -252,6 +260,7 @@ export interface HeaderMatcher__Output { * Examples: * * * The value ``abcd`` matches the value ``xyzabcdpqr``, but not for ``xyzbcdpqr``. + * @deprecated */ 'contains_match'?: (string); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts index cd47e471a..28d17667f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RateLimit.ts @@ -40,6 +40,7 @@ export interface _envoy_config_route_v3_RateLimit_Action { * * .. attention:: * This field has been deprecated in favor of the :ref:`metadata ` field + * @deprecated */ 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData | null); /** @@ -101,6 +102,7 @@ export interface _envoy_config_route_v3_RateLimit_Action__Output { * * .. attention:: * This field has been deprecated in favor of the :ref:`metadata ` field + * @deprecated */ 'dynamic_metadata'?: (_envoy_config_route_v3_RateLimit_Action_DynamicMetaData__Output | null); /** @@ -438,7 +440,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_MetaData { /** * Source of metadata */ - 'source'?: (_envoy_config_route_v3_RateLimit_Action_MetaData_Source | keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); + 'source'?: (_envoy_config_route_v3_RateLimit_Action_MetaData_Source); /** * If set to true, Envoy skips the descriptor while calling rate limiting service * when ``metadata_key`` is empty and ``default_value`` is not set. By default it skips calling the @@ -474,7 +476,7 @@ export interface _envoy_config_route_v3_RateLimit_Action_MetaData__Output { /** * Source of metadata */ - 'source': (keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source); + 'source': (_envoy_config_route_v3_RateLimit_Action_MetaData_Source__Output); /** * If set to true, Envoy skips the descriptor while calling rate limiting service * when ``metadata_key`` is empty and ``default_value`` is not set. By default it skips calling the @@ -643,16 +645,30 @@ export interface _envoy_config_route_v3_RateLimit_Action_RequestHeaders__Output // Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -export enum _envoy_config_route_v3_RateLimit_Action_MetaData_Source { +export const _envoy_config_route_v3_RateLimit_Action_MetaData_Source = { /** * Query :ref:`dynamic metadata ` */ - DYNAMIC = 0, + DYNAMIC: 'DYNAMIC', /** * Query :ref:`route entry metadata ` */ - ROUTE_ENTRY = 1, -} + ROUTE_ENTRY: 'ROUTE_ENTRY', +} as const; + +export type _envoy_config_route_v3_RateLimit_Action_MetaData_Source = + /** + * Query :ref:`dynamic metadata ` + */ + | 'DYNAMIC' + | 0 + /** + * Query :ref:`route entry metadata ` + */ + | 'ROUTE_ENTRY' + | 1 + +export type _envoy_config_route_v3_RateLimit_Action_MetaData_Source__Output = typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source[keyof typeof _envoy_config_route_v3_RateLimit_Action_MetaData_Source] /** * The following descriptor entry is appended to the descriptor: diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts index fd11a681b..070470af3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RedirectAction.ts @@ -4,28 +4,57 @@ import type { RegexMatchAndSubstitute as _envoy_type_matcher_v3_RegexMatchAndSub // Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -export enum _envoy_config_route_v3_RedirectAction_RedirectResponseCode { +export const _envoy_config_route_v3_RedirectAction_RedirectResponseCode = { /** * Moved Permanently HTTP Status Code - 301. */ - MOVED_PERMANENTLY = 0, + MOVED_PERMANENTLY: 'MOVED_PERMANENTLY', /** * Found HTTP Status Code - 302. */ - FOUND = 1, + FOUND: 'FOUND', /** * See Other HTTP Status Code - 303. */ - SEE_OTHER = 2, + SEE_OTHER: 'SEE_OTHER', /** * Temporary Redirect HTTP Status Code - 307. */ - TEMPORARY_REDIRECT = 3, + TEMPORARY_REDIRECT: 'TEMPORARY_REDIRECT', /** * Permanent Redirect HTTP Status Code - 308. */ - PERMANENT_REDIRECT = 4, -} + PERMANENT_REDIRECT: 'PERMANENT_REDIRECT', +} as const; + +export type _envoy_config_route_v3_RedirectAction_RedirectResponseCode = + /** + * Moved Permanently HTTP Status Code - 301. + */ + | 'MOVED_PERMANENTLY' + | 0 + /** + * Found HTTP Status Code - 302. + */ + | 'FOUND' + | 1 + /** + * See Other HTTP Status Code - 303. + */ + | 'SEE_OTHER' + | 2 + /** + * Temporary Redirect HTTP Status Code - 307. + */ + | 'TEMPORARY_REDIRECT' + | 3 + /** + * Permanent Redirect HTTP Status Code - 308. + */ + | 'PERMANENT_REDIRECT' + | 4 + +export type _envoy_config_route_v3_RedirectAction_RedirectResponseCode__Output = typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode[keyof typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode] /** * [#next-free-field: 10] @@ -58,7 +87,7 @@ export interface RedirectAction { * The HTTP status code to use in the redirect response. The default response * code is MOVED_PERMANENTLY (301). */ - 'response_code'?: (_envoy_config_route_v3_RedirectAction_RedirectResponseCode | keyof typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode); + 'response_code'?: (_envoy_config_route_v3_RedirectAction_RedirectResponseCode); /** * The scheme portion of the URL will be swapped with "https". */ @@ -155,7 +184,7 @@ export interface RedirectAction__Output { * The HTTP status code to use in the redirect response. The default response * code is MOVED_PERMANENTLY (301). */ - 'response_code': (keyof typeof _envoy_config_route_v3_RedirectAction_RedirectResponseCode); + 'response_code': (_envoy_config_route_v3_RedirectAction_RedirectResponseCode__Output); /** * The scheme portion of the URL will be swapped with "https". */ diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts index d60458728..773943b7f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RetryPolicy.ts @@ -141,7 +141,7 @@ export interface _envoy_config_route_v3_RetryPolicy_ResetHeader { /** * The format of the reset header. */ - 'format'?: (_envoy_config_route_v3_RetryPolicy_ResetHeaderFormat | keyof typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat); + 'format'?: (_envoy_config_route_v3_RetryPolicy_ResetHeaderFormat); } export interface _envoy_config_route_v3_RetryPolicy_ResetHeader__Output { @@ -156,15 +156,23 @@ export interface _envoy_config_route_v3_RetryPolicy_ResetHeader__Output { /** * The format of the reset header. */ - 'format': (keyof typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat); + 'format': (_envoy_config_route_v3_RetryPolicy_ResetHeaderFormat__Output); } // Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -export enum _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat { - SECONDS = 0, - UNIX_TIMESTAMP = 1, -} +export const _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat = { + SECONDS: 'SECONDS', + UNIX_TIMESTAMP: 'UNIX_TIMESTAMP', +} as const; + +export type _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat = + | 'SECONDS' + | 0 + | 'UNIX_TIMESTAMP' + | 1 + +export type _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat__Output = typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat[keyof typeof _envoy_config_route_v3_RetryPolicy_ResetHeaderFormat] export interface _envoy_config_route_v3_RetryPolicy_RetryBackOff { /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts index 9dd8b7c2c..fd38da92e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/RouteAction.ts @@ -5,7 +5,7 @@ import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _e import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../google/protobuf/BoolValue'; import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration'; import type { RetryPolicy as _envoy_config_route_v3_RetryPolicy, RetryPolicy__Output as _envoy_config_route_v3_RetryPolicy__Output } from '../../../../envoy/config/route/v3/RetryPolicy'; -import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority } from '../../../../envoy/config/core/v3/RoutingPriority'; +import type { RoutingPriority as _envoy_config_core_v3_RoutingPriority, RoutingPriority__Output as _envoy_config_core_v3_RoutingPriority__Output } from '../../../../envoy/config/core/v3/RoutingPriority'; import type { RateLimit as _envoy_config_route_v3_RateLimit, RateLimit__Output as _envoy_config_route_v3_RateLimit__Output } from '../../../../envoy/config/route/v3/RateLimit'; import type { CorsPolicy as _envoy_config_route_v3_CorsPolicy, CorsPolicy__Output as _envoy_config_route_v3_CorsPolicy__Output } from '../../../../envoy/config/route/v3/CorsPolicy'; import type { HedgePolicy as _envoy_config_route_v3_HedgePolicy, HedgePolicy__Output as _envoy_config_route_v3_HedgePolicy__Output } from '../../../../envoy/config/route/v3/HedgePolicy'; @@ -20,20 +20,39 @@ import type { ProxyProtocolConfig as _envoy_config_core_v3_ProxyProtocolConfig, // Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -export enum _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode { +export const _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode = { /** * HTTP status code - 503 Service Unavailable. */ - SERVICE_UNAVAILABLE = 0, + SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE', /** * HTTP status code - 404 Not Found. */ - NOT_FOUND = 1, + NOT_FOUND: 'NOT_FOUND', /** * HTTP status code - 500 Internal Server Error. */ - INTERNAL_SERVER_ERROR = 2, -} + INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR', +} as const; + +export type _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode = + /** + * HTTP status code - 503 Service Unavailable. + */ + | 'SERVICE_UNAVAILABLE' + | 0 + /** + * HTTP status code - 404 Not Found. + */ + | 'NOT_FOUND' + | 1 + /** + * HTTP status code - 500 Internal Server Error. + */ + | 'INTERNAL_SERVER_ERROR' + | 2 + +export type _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode__Output = typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode[keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode] /** * Configuration for sending data upstream as a raw data payload. This is used for @@ -302,11 +321,30 @@ export interface _envoy_config_route_v3_RouteAction_HashPolicy_Header__Output { /** * Configures :ref:`internal redirect ` behavior. * [#next-major-version: remove this definition - it's defined in the InternalRedirectPolicy message.] + * @deprecated */ -export enum _envoy_config_route_v3_RouteAction_InternalRedirectAction { - PASS_THROUGH_INTERNAL_REDIRECT = 0, - HANDLE_INTERNAL_REDIRECT = 1, -} +export const _envoy_config_route_v3_RouteAction_InternalRedirectAction = { + PASS_THROUGH_INTERNAL_REDIRECT: 'PASS_THROUGH_INTERNAL_REDIRECT', + HANDLE_INTERNAL_REDIRECT: 'HANDLE_INTERNAL_REDIRECT', +} as const; + +/** + * Configures :ref:`internal redirect ` behavior. + * [#next-major-version: remove this definition - it's defined in the InternalRedirectPolicy message.] + * @deprecated + */ +export type _envoy_config_route_v3_RouteAction_InternalRedirectAction = + | 'PASS_THROUGH_INTERNAL_REDIRECT' + | 0 + | 'HANDLE_INTERNAL_REDIRECT' + | 1 + +/** + * Configures :ref:`internal redirect ` behavior. + * [#next-major-version: remove this definition - it's defined in the InternalRedirectPolicy message.] + * @deprecated + */ +export type _envoy_config_route_v3_RouteAction_InternalRedirectAction__Output = typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction[keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction] export interface _envoy_config_route_v3_RouteAction_MaxStreamDuration { /** @@ -679,7 +717,7 @@ export interface RouteAction { /** * Optionally specifies the :ref:`routing priority `. */ - 'priority'?: (_envoy_config_core_v3_RoutingPriority | keyof typeof _envoy_config_core_v3_RoutingPriority); + 'priority'?: (_envoy_config_core_v3_RoutingPriority); /** * Specifies a set of rate limit configurations that could be applied to the * route. @@ -692,6 +730,7 @@ export interface RouteAction { * request. * * This field is deprecated. Please use :ref:`vh_rate_limits ` + * @deprecated */ 'include_vh_rate_limits'?: (_google_protobuf_BoolValue | null); /** @@ -720,13 +759,14 @@ export interface RouteAction { * :ref:`Route.typed_per_filter_config` or * :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` * to configure the CORS HTTP filter. + * @deprecated */ 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); /** * The HTTP status code to use when configured cluster is not found. * The default response code is 503 Service Unavailable. */ - 'cluster_not_found_response_code'?: (_envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode | keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); + 'cluster_not_found_response_code'?: (_envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); /** * Deprecated by :ref:`grpc_timeout_header_max ` * If present, and the request is a gRPC request, use the @@ -748,6 +788,7 @@ export interface RouteAction { * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the * :ref:`retry overview `. + * @deprecated */ 'max_grpc_timeout'?: (_google_protobuf_Duration | null); /** @@ -776,7 +817,10 @@ export interface RouteAction { */ 'idle_timeout'?: (_google_protobuf_Duration | null); 'upgrade_configs'?: (_envoy_config_route_v3_RouteAction_UpgradeConfig)[]; - 'internal_redirect_action'?: (_envoy_config_route_v3_RouteAction_InternalRedirectAction | keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); + /** + * @deprecated + */ + 'internal_redirect_action'?: (_envoy_config_route_v3_RouteAction_InternalRedirectAction); /** * Indicates that the route has a hedge policy. Note that if this is set, * it'll take precedence over the virtual host level hedge policy entirely @@ -792,6 +836,7 @@ export interface RouteAction { * The offset will only be applied if the provided grpc_timeout is greater than the offset. This * ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning * infinity). + * @deprecated */ 'grpc_timeout_offset'?: (_google_protobuf_Duration | null); /** @@ -833,6 +878,7 @@ export interface RouteAction { * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. + * @deprecated */ 'max_internal_redirects'?: (_google_protobuf_UInt32Value | null); /** @@ -1063,7 +1109,7 @@ export interface RouteAction__Output { /** * Optionally specifies the :ref:`routing priority `. */ - 'priority': (keyof typeof _envoy_config_core_v3_RoutingPriority); + 'priority': (_envoy_config_core_v3_RoutingPriority__Output); /** * Specifies a set of rate limit configurations that could be applied to the * route. @@ -1076,6 +1122,7 @@ export interface RouteAction__Output { * request. * * This field is deprecated. Please use :ref:`vh_rate_limits ` + * @deprecated */ 'include_vh_rate_limits': (_google_protobuf_BoolValue__Output | null); /** @@ -1104,13 +1151,14 @@ export interface RouteAction__Output { * :ref:`Route.typed_per_filter_config` or * :ref:`WeightedCluster.ClusterWeight.typed_per_filter_config` * to configure the CORS HTTP filter. + * @deprecated */ 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); /** * The HTTP status code to use when configured cluster is not found. * The default response code is 503 Service Unavailable. */ - 'cluster_not_found_response_code': (keyof typeof _envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode); + 'cluster_not_found_response_code': (_envoy_config_route_v3_RouteAction_ClusterNotFoundResponseCode__Output); /** * Deprecated by :ref:`grpc_timeout_header_max ` * If present, and the request is a gRPC request, use the @@ -1132,6 +1180,7 @@ export interface RouteAction__Output { * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the * :ref:`retry overview `. + * @deprecated */ 'max_grpc_timeout': (_google_protobuf_Duration__Output | null); /** @@ -1160,7 +1209,10 @@ export interface RouteAction__Output { */ 'idle_timeout': (_google_protobuf_Duration__Output | null); 'upgrade_configs': (_envoy_config_route_v3_RouteAction_UpgradeConfig__Output)[]; - 'internal_redirect_action': (keyof typeof _envoy_config_route_v3_RouteAction_InternalRedirectAction); + /** + * @deprecated + */ + 'internal_redirect_action': (_envoy_config_route_v3_RouteAction_InternalRedirectAction__Output); /** * Indicates that the route has a hedge policy. Note that if this is set, * it'll take precedence over the virtual host level hedge policy entirely @@ -1176,6 +1228,7 @@ export interface RouteAction__Output { * The offset will only be applied if the provided grpc_timeout is greater than the offset. This * ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning * infinity). + * @deprecated */ 'grpc_timeout_offset': (_google_protobuf_Duration__Output | null); /** @@ -1217,6 +1270,7 @@ export interface RouteAction__Output { * will pass the redirect back to downstream. * * If not specified, at most one redirect will be followed. + * @deprecated */ 'max_internal_redirects': (_google_protobuf_UInt32Value__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts index b2c344fff..5109be872 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/VirtualHost.ts @@ -14,22 +14,43 @@ import type { _envoy_config_route_v3_RouteAction_RequestMirrorPolicy, _envoy_con // Original file: deps/envoy-api/envoy/config/route/v3/route_components.proto -export enum _envoy_config_route_v3_VirtualHost_TlsRequirementType { +export const _envoy_config_route_v3_VirtualHost_TlsRequirementType = { /** * No TLS requirement for the virtual host. */ - NONE = 0, + NONE: 'NONE', /** * External requests must use TLS. If a request is external and it is not * using TLS, a 301 redirect will be sent telling the client to use HTTPS. */ - EXTERNAL_ONLY = 1, + EXTERNAL_ONLY: 'EXTERNAL_ONLY', /** * All requests must use TLS. If a request is not using TLS, a 301 redirect * will be sent telling the client to use HTTPS. */ - ALL = 2, -} + ALL: 'ALL', +} as const; + +export type _envoy_config_route_v3_VirtualHost_TlsRequirementType = + /** + * No TLS requirement for the virtual host. + */ + | 'NONE' + | 0 + /** + * External requests must use TLS. If a request is external and it is not + * using TLS, a 301 redirect will be sent telling the client to use HTTPS. + */ + | 'EXTERNAL_ONLY' + | 1 + /** + * All requests must use TLS. If a request is not using TLS, a 301 redirect + * will be sent telling the client to use HTTPS. + */ + | 'ALL' + | 2 + +export type _envoy_config_route_v3_VirtualHost_TlsRequirementType__Output = typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType[keyof typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType] /** * The top level element in the routing configuration is a virtual host. Each virtual host has @@ -76,7 +97,7 @@ export interface VirtualHost { * Specifies the type of TLS enforcement the virtual host expects. If this option is not * specified, there is no TLS requirement for the virtual host. */ - 'require_tls'?: (_envoy_config_route_v3_VirtualHost_TlsRequirementType | keyof typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType); + 'require_tls'?: (_envoy_config_route_v3_VirtualHost_TlsRequirementType); /** * A list of virtual clusters defined for this virtual host. Virtual clusters * are used for additional statistics gathering. @@ -106,6 +127,7 @@ export interface VirtualHost { * This option has been deprecated. Please use * :ref:`VirtualHost.typed_per_filter_config` * to configure the CORS HTTP filter. + * @deprecated */ 'cors'?: (_envoy_config_route_v3_CorsPolicy | null); /** @@ -256,7 +278,7 @@ export interface VirtualHost__Output { * Specifies the type of TLS enforcement the virtual host expects. If this option is not * specified, there is no TLS requirement for the virtual host. */ - 'require_tls': (keyof typeof _envoy_config_route_v3_VirtualHost_TlsRequirementType); + 'require_tls': (_envoy_config_route_v3_VirtualHost_TlsRequirementType__Output); /** * A list of virtual clusters defined for this virtual host. Virtual clusters * are used for additional statistics gathering. @@ -286,6 +308,7 @@ export interface VirtualHost__Output { * This option has been deprecated. Please use * :ref:`VirtualHost.typed_per_filter_config` * to configure the CORS HTTP filter. + * @deprecated */ 'cors': (_envoy_config_route_v3_CorsPolicy__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts index cc820654d..91f4c6aeb 100644 --- a/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts +++ b/packages/grpc-js-xds/src/generated/envoy/config/route/v3/WeightedCluster.ts @@ -232,6 +232,7 @@ export interface WeightedCluster { * value, if this is greater than 0. * This field is now deprecated, and the client will use the sum of all * cluster weights. It is up to the management server to supply the correct weights. + * @deprecated */ 'total_weight'?: (_google_protobuf_UInt32Value | null); /** @@ -274,6 +275,7 @@ export interface WeightedCluster__Output { * value, if this is greater than 0. * This field is now deprecated, and the client will use the sum of all * cluster weights. It is up to the management server to supply the correct weights. + * @deprecated */ 'total_weight': (_google_protobuf_UInt32Value__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts index 7679dfac1..7ca3c5b19 100644 --- a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogCommon.ts @@ -7,7 +7,7 @@ import type { Duration as _google_protobuf_Duration, Duration__Output as _google import type { ResponseFlags as _envoy_data_accesslog_v3_ResponseFlags, ResponseFlags__Output as _envoy_data_accesslog_v3_ResponseFlags__Output } from '../../../../envoy/data/accesslog/v3/ResponseFlags'; import type { Metadata as _envoy_config_core_v3_Metadata, Metadata__Output as _envoy_config_core_v3_Metadata__Output } from '../../../../envoy/config/core/v3/Metadata'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; -import type { AccessLogType as _envoy_data_accesslog_v3_AccessLogType } from '../../../../envoy/data/accesslog/v3/AccessLogType'; +import type { AccessLogType as _envoy_data_accesslog_v3_AccessLogType, AccessLogType__Output as _envoy_data_accesslog_v3_AccessLogType__Output } from '../../../../envoy/data/accesslog/v3/AccessLogType'; import type { Long } from '@grpc/proto-loader'; /** @@ -176,6 +176,7 @@ export interface AccessLogCommon { * * This field is deprecated in favor of ``access_log_type`` for better indication of the * type of the access log record. + * @deprecated */ 'intermediate_log_entry'?: (boolean); /** @@ -212,7 +213,7 @@ export interface AccessLogCommon { * For more information about how access log behaves and when it is being recorded, * please refer to :ref:`access logging `. */ - 'access_log_type'?: (_envoy_data_accesslog_v3_AccessLogType | keyof typeof _envoy_data_accesslog_v3_AccessLogType); + 'access_log_type'?: (_envoy_data_accesslog_v3_AccessLogType); } /** @@ -381,6 +382,7 @@ export interface AccessLogCommon__Output { * * This field is deprecated in favor of ``access_log_type`` for better indication of the * type of the access log record. + * @deprecated */ 'intermediate_log_entry': (boolean); /** @@ -417,5 +419,5 @@ export interface AccessLogCommon__Output { * For more information about how access log behaves and when it is being recorded, * please refer to :ref:`access logging `. */ - 'access_log_type': (keyof typeof _envoy_data_accesslog_v3_AccessLogType); + 'access_log_type': (_envoy_data_accesslog_v3_AccessLogType__Output); } diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts index a50bb42c1..29ee32f5a 100644 --- a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/AccessLogType.ts @@ -1,15 +1,41 @@ // Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto -export enum AccessLogType { - NotSet = 0, - TcpUpstreamConnected = 1, - TcpPeriodic = 2, - TcpConnectionEnd = 3, - DownstreamStart = 4, - DownstreamPeriodic = 5, - DownstreamEnd = 6, - UpstreamPoolReady = 7, - UpstreamPeriodic = 8, - UpstreamEnd = 9, - DownstreamTunnelSuccessfullyEstablished = 10, -} +export const AccessLogType = { + NotSet: 'NotSet', + TcpUpstreamConnected: 'TcpUpstreamConnected', + TcpPeriodic: 'TcpPeriodic', + TcpConnectionEnd: 'TcpConnectionEnd', + DownstreamStart: 'DownstreamStart', + DownstreamPeriodic: 'DownstreamPeriodic', + DownstreamEnd: 'DownstreamEnd', + UpstreamPoolReady: 'UpstreamPoolReady', + UpstreamPeriodic: 'UpstreamPeriodic', + UpstreamEnd: 'UpstreamEnd', + DownstreamTunnelSuccessfullyEstablished: 'DownstreamTunnelSuccessfullyEstablished', +} as const; + +export type AccessLogType = + | 'NotSet' + | 0 + | 'TcpUpstreamConnected' + | 1 + | 'TcpPeriodic' + | 2 + | 'TcpConnectionEnd' + | 3 + | 'DownstreamStart' + | 4 + | 'DownstreamPeriodic' + | 5 + | 'DownstreamEnd' + | 6 + | 'UpstreamPoolReady' + | 7 + | 'UpstreamPeriodic' + | 8 + | 'UpstreamEnd' + | 9 + | 'DownstreamTunnelSuccessfullyEstablished' + | 10 + +export type AccessLogType__Output = typeof AccessLogType[keyof typeof AccessLogType] diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts index 760954bb1..31daac364 100644 --- a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPAccessLogEntry.ts @@ -9,20 +9,40 @@ import type { HTTPResponseProperties as _envoy_data_accesslog_v3_HTTPResponsePro /** * HTTP version */ -export enum _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion { - PROTOCOL_UNSPECIFIED = 0, - HTTP10 = 1, - HTTP11 = 2, - HTTP2 = 3, - HTTP3 = 4, -} +export const _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion = { + PROTOCOL_UNSPECIFIED: 'PROTOCOL_UNSPECIFIED', + HTTP10: 'HTTP10', + HTTP11: 'HTTP11', + HTTP2: 'HTTP2', + HTTP3: 'HTTP3', +} as const; + +/** + * HTTP version + */ +export type _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion = + | 'PROTOCOL_UNSPECIFIED' + | 0 + | 'HTTP10' + | 1 + | 'HTTP11' + | 2 + | 'HTTP2' + | 3 + | 'HTTP3' + | 4 + +/** + * HTTP version + */ +export type _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion__Output = typeof _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion[keyof typeof _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion] export interface HTTPAccessLogEntry { /** * Common properties shared by all Envoy access logs. */ 'common_properties'?: (_envoy_data_accesslog_v3_AccessLogCommon | null); - 'protocol_version'?: (_envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion | keyof typeof _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion); + 'protocol_version'?: (_envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion); /** * Description of the incoming HTTP request. */ @@ -38,7 +58,7 @@ export interface HTTPAccessLogEntry__Output { * Common properties shared by all Envoy access logs. */ 'common_properties': (_envoy_data_accesslog_v3_AccessLogCommon__Output | null); - 'protocol_version': (keyof typeof _envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion); + 'protocol_version': (_envoy_data_accesslog_v3_HTTPAccessLogEntry_HTTPVersion__Output); /** * Description of the incoming HTTP request. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts index 9e145503c..f1271ba16 100644 --- a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/HTTPRequestProperties.ts @@ -1,6 +1,6 @@ // Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto -import type { RequestMethod as _envoy_config_core_v3_RequestMethod } from '../../../../envoy/config/core/v3/RequestMethod'; +import type { RequestMethod as _envoy_config_core_v3_RequestMethod, RequestMethod__Output as _envoy_config_core_v3_RequestMethod__Output } from '../../../../envoy/config/core/v3/RequestMethod'; import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../google/protobuf/UInt32Value'; import type { Long } from '@grpc/proto-loader'; @@ -11,7 +11,7 @@ export interface HTTPRequestProperties { /** * The request method (RFC 7231/2616). */ - 'request_method'?: (_envoy_config_core_v3_RequestMethod | keyof typeof _envoy_config_core_v3_RequestMethod); + 'request_method'?: (_envoy_config_core_v3_RequestMethod); /** * The scheme portion of the incoming request URI. */ @@ -90,7 +90,7 @@ export interface HTTPRequestProperties__Output { /** * The request method (RFC 7231/2616). */ - 'request_method': (keyof typeof _envoy_config_core_v3_RequestMethod); + 'request_method': (_envoy_config_core_v3_RequestMethod__Output); /** * The scheme portion of the incoming request URI. */ diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts index ec45824b3..f42e11ee3 100644 --- a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/ResponseFlags.ts @@ -6,20 +6,37 @@ /** * Reasons why the request was unauthorized */ -export enum _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason { - REASON_UNSPECIFIED = 0, +export const _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason = { + REASON_UNSPECIFIED: 'REASON_UNSPECIFIED', /** * The request was denied by the external authorization service. */ - EXTERNAL_SERVICE = 1, -} + EXTERNAL_SERVICE: 'EXTERNAL_SERVICE', +} as const; + +/** + * Reasons why the request was unauthorized + */ +export type _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason = + | 'REASON_UNSPECIFIED' + | 0 + /** + * The request was denied by the external authorization service. + */ + | 'EXTERNAL_SERVICE' + | 1 + +/** + * Reasons why the request was unauthorized + */ +export type _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason__Output = typeof _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason[keyof typeof _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason] export interface _envoy_data_accesslog_v3_ResponseFlags_Unauthorized { - 'reason'?: (_envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason | keyof typeof _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason); + 'reason'?: (_envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason); } export interface _envoy_data_accesslog_v3_ResponseFlags_Unauthorized__Output { - 'reason': (keyof typeof _envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason); + 'reason': (_envoy_data_accesslog_v3_ResponseFlags_Unauthorized_Reason__Output); } /** diff --git a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts index 106d24d09..ddeb9a1ae 100644 --- a/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts +++ b/packages/grpc-js-xds/src/generated/envoy/data/accesslog/v3/TLSProperties.ts @@ -44,13 +44,27 @@ export interface _envoy_data_accesslog_v3_TLSProperties_CertificateProperties_Su // Original file: deps/envoy-api/envoy/data/accesslog/v3/accesslog.proto -export enum _envoy_data_accesslog_v3_TLSProperties_TLSVersion { - VERSION_UNSPECIFIED = 0, - TLSv1 = 1, - TLSv1_1 = 2, - TLSv1_2 = 3, - TLSv1_3 = 4, -} +export const _envoy_data_accesslog_v3_TLSProperties_TLSVersion = { + VERSION_UNSPECIFIED: 'VERSION_UNSPECIFIED', + TLSv1: 'TLSv1', + TLSv1_1: 'TLSv1_1', + TLSv1_2: 'TLSv1_2', + TLSv1_3: 'TLSv1_3', +} as const; + +export type _envoy_data_accesslog_v3_TLSProperties_TLSVersion = + | 'VERSION_UNSPECIFIED' + | 0 + | 'TLSv1' + | 1 + | 'TLSv1_1' + | 2 + | 'TLSv1_2' + | 3 + | 'TLSv1_3' + | 4 + +export type _envoy_data_accesslog_v3_TLSProperties_TLSVersion__Output = typeof _envoy_data_accesslog_v3_TLSProperties_TLSVersion[keyof typeof _envoy_data_accesslog_v3_TLSProperties_TLSVersion] /** * Properties of a negotiated TLS connection. @@ -60,7 +74,7 @@ export interface TLSProperties { /** * Version of TLS that was negotiated. */ - 'tls_version'?: (_envoy_data_accesslog_v3_TLSProperties_TLSVersion | keyof typeof _envoy_data_accesslog_v3_TLSProperties_TLSVersion); + 'tls_version'?: (_envoy_data_accesslog_v3_TLSProperties_TLSVersion); /** * TLS cipher suite negotiated during handshake. The value is a * four-digit hex code defined by the IANA TLS Cipher Suite Registry @@ -99,7 +113,7 @@ export interface TLSProperties__Output { /** * Version of TLS that was negotiated. */ - 'tls_version': (keyof typeof _envoy_data_accesslog_v3_TLSProperties_TLSVersion); + 'tls_version': (_envoy_data_accesslog_v3_TLSProperties_TLSVersion__Output); /** * TLS cipher suite negotiated during handshake. The value is a * four-digit hex code defined by the IANA TLS Cipher Suite Registry diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts index bec0403e4..e070ae913 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/common/fault/v3/FaultDelay.ts @@ -5,12 +5,21 @@ import type { FractionalPercent as _envoy_type_v3_FractionalPercent, FractionalP // Original file: deps/envoy-api/envoy/extensions/filters/common/fault/v3/fault.proto -export enum _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType { +export const _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType = { /** * Unused and deprecated. */ - FIXED = 0, -} + FIXED: 'FIXED', +} as const; + +export type _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType = + /** + * Unused and deprecated. + */ + | 'FIXED' + | 0 + +export type _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType__Output = typeof _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType[keyof typeof _envoy_extensions_filters_common_fault_v3_FaultDelay_FaultDelayType] /** * Fault delays are controlled via an HTTP header (if applicable). See the diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts index 1b4f36fd8..1a452635c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager.ts @@ -24,7 +24,7 @@ import type { PathTransformation as _envoy_type_http_v3_PathTransformation, Path // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType { +export const _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType = { /** * For every new connection, the connection manager will determine which * codec to use. This mode supports both ALPN for TLS listeners as well as @@ -32,24 +32,56 @@ export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpCon * is preferred, otherwise protocol inference is used. In almost all cases, * this is the right option to choose for this setting. */ - AUTO = 0, + AUTO: 'AUTO', /** * The connection manager will assume that the client is speaking HTTP/1.1. */ - HTTP1 = 1, + HTTP1: 'HTTP1', /** * The connection manager will assume that the client is speaking HTTP/2 * (Envoy does not require HTTP/2 to take place over TLS or to use ALPN. * Prior knowledge is allowed). */ - HTTP2 = 2, + HTTP2: 'HTTP2', /** * [#not-implemented-hide:] QUIC implementation is not production ready yet. Use this enum with * caution to prevent accidental execution of QUIC code. I.e. `!= HTTP2` is no longer sufficient * to distinguish HTTP1 and HTTP2 traffic. */ - HTTP3 = 3, -} + HTTP3: 'HTTP3', +} as const; + +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType = + /** + * For every new connection, the connection manager will determine which + * codec to use. This mode supports both ALPN for TLS listeners as well as + * protocol inference for plaintext listeners. If ALPN data is available, it + * is preferred, otherwise protocol inference is used. In almost all cases, + * this is the right option to choose for this setting. + */ + | 'AUTO' + | 0 + /** + * The connection manager will assume that the client is speaking HTTP/1.1. + */ + | 'HTTP1' + | 1 + /** + * The connection manager will assume that the client is speaking HTTP/2 + * (Envoy does not require HTTP/2 to take place over TLS or to use ALPN. + * Prior knowledge is allowed). + */ + | 'HTTP2' + | 2 + /** + * [#not-implemented-hide:] QUIC implementation is not production ready yet. Use this enum with + * caution to prevent accidental execution of QUIC code. I.e. `!= HTTP2` is no longer sufficient + * to distinguish HTTP1 and HTTP2 traffic. + */ + | 'HTTP3' + | 3 + +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType__Output = typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType[keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType] // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -57,32 +89,73 @@ export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpCon * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. */ -export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails { +export const _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails = { /** * Do not send the XFCC header to the next hop. This is the default value. */ - SANITIZE = 0, + SANITIZE: 'SANITIZE', /** * When the client connection is mTLS (Mutual TLS), forward the XFCC header * in the request. */ - FORWARD_ONLY = 1, + FORWARD_ONLY: 'FORWARD_ONLY', /** * When the client connection is mTLS, append the client certificate * information to the request’s XFCC header and forward it. */ - APPEND_FORWARD = 2, + APPEND_FORWARD: 'APPEND_FORWARD', /** * When the client connection is mTLS, reset the XFCC header with the client * certificate information and send it to the next hop. */ - SANITIZE_SET = 3, + SANITIZE_SET: 'SANITIZE_SET', /** * Always forward the XFCC header in the request, regardless of whether the * client connection is mTLS. */ - ALWAYS_FORWARD_ONLY = 4, -} + ALWAYS_FORWARD_ONLY: 'ALWAYS_FORWARD_ONLY', +} as const; + +/** + * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP + * header. + */ +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails = + /** + * Do not send the XFCC header to the next hop. This is the default value. + */ + | 'SANITIZE' + | 0 + /** + * When the client connection is mTLS (Mutual TLS), forward the XFCC header + * in the request. + */ + | 'FORWARD_ONLY' + | 1 + /** + * When the client connection is mTLS, append the client certificate + * information to the request’s XFCC header and forward it. + */ + | 'APPEND_FORWARD' + | 2 + /** + * When the client connection is mTLS, reset the XFCC header with the client + * certificate information and send it to the next hop. + */ + | 'SANITIZE_SET' + | 3 + /** + * Always forward the XFCC header in the request, regardless of whether the + * client connection is mTLS. + */ + | 'ALWAYS_FORWARD_ONLY' + | 4 + +/** + * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP + * header. + */ +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails__Output = typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails[keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails] export interface _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_HcmAccessLogOptions { /** @@ -162,16 +235,30 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName { +export const _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName = { /** * The HTTP listener is used for ingress/incoming requests. */ - INGRESS = 0, + INGRESS: 'INGRESS', /** * The HTTP listener is used for egress/outgoing requests. */ - EGRESS = 1, -} + EGRESS: 'EGRESS', +} as const; + +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName = + /** + * The HTTP listener is used for ingress/incoming requests. + */ + | 'INGRESS' + | 0 + /** + * The HTTP listener is used for egress/outgoing requests. + */ + | 'EGRESS' + | 1 + +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName__Output = typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName[keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_Tracing_OperationName] /** * [#not-implemented-hide:] Transformations that apply to path headers. Transformations are applied @@ -253,22 +340,22 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht * Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. * This operation occurs before URL normalization and the merge slashes transformations if they were enabled. */ -export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction { +export const _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction = { /** * Default behavior specific to implementation (i.e. Envoy) of this configuration option. * Envoy, by default, takes the KEEP_UNCHANGED action. * NOTE: the implementation may change the default behavior at-will. */ - IMPLEMENTATION_SPECIFIC_DEFAULT = 0, + IMPLEMENTATION_SPECIFIC_DEFAULT: 'IMPLEMENTATION_SPECIFIC_DEFAULT', /** * Keep escaped slashes. */ - KEEP_UNCHANGED = 1, + KEEP_UNCHANGED: 'KEEP_UNCHANGED', /** * Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. * The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. */ - REJECT_REQUEST = 2, + REJECT_REQUEST: 'REJECT_REQUEST', /** * Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. * Redirect occurs after path normalization and merge slashes transformations if they were configured. @@ -278,14 +365,62 @@ export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpCon * The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each * redirected request. */ - UNESCAPE_AND_REDIRECT = 3, + UNESCAPE_AND_REDIRECT: 'UNESCAPE_AND_REDIRECT', /** * Unescape %2F and %5C sequences. * Note: this option should not be enabled if intermediaries perform path based access control as * it may lead to path confusion vulnerabilities. */ - UNESCAPE_AND_FORWARD = 4, -} + UNESCAPE_AND_FORWARD: 'UNESCAPE_AND_FORWARD', +} as const; + +/** + * Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + * This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + */ +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction = + /** + * Default behavior specific to implementation (i.e. Envoy) of this configuration option. + * Envoy, by default, takes the KEEP_UNCHANGED action. + * NOTE: the implementation may change the default behavior at-will. + */ + | 'IMPLEMENTATION_SPECIFIC_DEFAULT' + | 0 + /** + * Keep escaped slashes. + */ + | 'KEEP_UNCHANGED' + | 1 + /** + * Reject client request with the 400 status. gRPC requests will be rejected with the INTERNAL (13) error code. + * The "httpN.downstream_rq_failed_path_normalization" counter is incremented for each rejected request. + */ + | 'REJECT_REQUEST' + | 2 + /** + * Unescape %2F and %5C sequences and redirect request to the new path if these sequences were present. + * Redirect occurs after path normalization and merge slashes transformations if they were configured. + * NOTE: gRPC requests will be rejected with the INTERNAL (13) error code. + * This option minimizes possibility of path confusion exploits by forcing request with unescaped slashes to + * traverse all parties: downstream client, intermediate proxies, Envoy and upstream server. + * The "httpN.downstream_rq_redirected_with_normalized_path" counter is incremented for each + * redirected request. + */ + | 'UNESCAPE_AND_REDIRECT' + | 3 + /** + * Unescape %2F and %5C sequences. + * Note: this option should not be enabled if intermediaries perform path based access control as + * it may lead to path confusion vulnerabilities. + */ + | 'UNESCAPE_AND_FORWARD' + | 4 + +/** + * Determines the action for request that contain %2F, %2f, %5C or %5c sequences in the URI path. + * This operation occurs before URL normalization and the merge slashes transformations if they were enabled. + */ +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction__Output = typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction[keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction] /** * Configures the manner in which the Proxy-Status HTTP response header is @@ -405,22 +540,43 @@ export interface _envoy_extensions_filters_network_http_connection_manager_v3_Ht // Original file: deps/envoy-api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto -export enum _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation { +export const _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation = { /** * Overwrite any Server header with the contents of server_name. */ - OVERWRITE = 0, + OVERWRITE: 'OVERWRITE', /** * If no Server header is present, append Server server_name * If a Server header is present, pass it through. */ - APPEND_IF_ABSENT = 1, + APPEND_IF_ABSENT: 'APPEND_IF_ABSENT', /** * Pass through the value of the server header, and do not append a header * if none is present. */ - PASS_THROUGH = 2, -} + PASS_THROUGH: 'PASS_THROUGH', +} as const; + +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation = + /** + * Overwrite any Server header with the contents of server_name. + */ + | 'OVERWRITE' + | 0 + /** + * If no Server header is present, append Server server_name + * If a Server header is present, pass it through. + */ + | 'APPEND_IF_ABSENT' + | 1 + /** + * Pass through the value of the server header, and do not append a header + * if none is present. + */ + | 'PASS_THROUGH' + | 2 + +export type _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation__Output = typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation[keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation] /** * [#next-free-field: 7] @@ -693,7 +849,7 @@ export interface HttpConnectionManager { /** * Supplies the type of codec that the connection manager should use. */ - 'codec_type'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType); + 'codec_type'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType); /** * The human readable prefix to use when emitting statistics for the * connection manager. See the :ref:`statistics documentation ` for @@ -781,7 +937,7 @@ export interface HttpConnectionManager { * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. */ - 'forward_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); + 'forward_client_cert_details'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); /** * This field is valid only when :ref:`forward_client_cert_details * ` @@ -981,7 +1137,7 @@ export interface HttpConnectionManager { * By default, Envoy will overwrite the header with the value specified in * server_name. */ - 'server_header_transformation'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation); + 'server_header_transformation'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation); /** * Additional settings for HTTP requests handled by the connection manager. These will be * applicable to both HTTP1 and HTTP2 requests. @@ -1092,7 +1248,7 @@ export interface HttpConnectionManager { * :ref:`header validation configuration ` * is present.] */ - 'path_with_escaped_slashes_action'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction | keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); + 'path_with_escaped_slashes_action'?: (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); /** * The configuration for the original IP detection extensions. * @@ -1190,6 +1346,7 @@ export interface HttpConnectionManager { * Note that if both this field and :ref:`access_log_flush_interval * ` * are specified, the former (deprecated field) is ignored. + * @deprecated */ 'access_log_flush_interval'?: (_google_protobuf_Duration | null); /** @@ -1200,6 +1357,7 @@ export interface HttpConnectionManager { * Note that if both this field and :ref:`flush_access_log_on_new_request * ` * are specified, the former (deprecated field) is ignored. + * @deprecated */ 'flush_access_log_on_new_request'?: (boolean); /** @@ -1217,7 +1375,7 @@ export interface HttpConnectionManager__Output { /** * Supplies the type of codec that the connection manager should use. */ - 'codec_type': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType); + 'codec_type': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_CodecType__Output); /** * The human readable prefix to use when emitting statistics for the * connection manager. See the :ref:`statistics documentation ` for @@ -1305,7 +1463,7 @@ export interface HttpConnectionManager__Output { * How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP * header. */ - 'forward_client_cert_details': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails); + 'forward_client_cert_details': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ForwardClientCertDetails__Output); /** * This field is valid only when :ref:`forward_client_cert_details * ` @@ -1505,7 +1663,7 @@ export interface HttpConnectionManager__Output { * By default, Envoy will overwrite the header with the value specified in * server_name. */ - 'server_header_transformation': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation); + 'server_header_transformation': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_ServerHeaderTransformation__Output); /** * Additional settings for HTTP requests handled by the connection manager. These will be * applicable to both HTTP1 and HTTP2 requests. @@ -1616,7 +1774,7 @@ export interface HttpConnectionManager__Output { * :ref:`header validation configuration ` * is present.] */ - 'path_with_escaped_slashes_action': (keyof typeof _envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction); + 'path_with_escaped_slashes_action': (_envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_PathWithEscapedSlashesAction__Output); /** * The configuration for the original IP detection extensions. * @@ -1714,6 +1872,7 @@ export interface HttpConnectionManager__Output { * Note that if both this field and :ref:`access_log_flush_interval * ` * are specified, the former (deprecated field) is ignored. + * @deprecated */ 'access_log_flush_interval': (_google_protobuf_Duration__Output | null); /** @@ -1724,6 +1883,7 @@ export interface HttpConnectionManager__Output { * Note that if both this field and :ref:`flush_access_log_on_new_request * ` * are specified, the former (deprecated field) is ignored. + * @deprecated */ 'flush_access_log_on_new_request': (boolean); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts index d6fecd36b..4e3d9659e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig.ts @@ -2,7 +2,6 @@ import type { Percent as _envoy_type_v3_Percent, Percent__Output as _envoy_type_v3_Percent__Output } from '../../../../../envoy/type/v3/Percent'; import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output as _google_protobuf_UInt64Value__Output } from '../../../../../google/protobuf/UInt64Value'; -import type { Long } from '@grpc/proto-loader'; /** * Configuration for :ref:`locality weighted load balancing diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts index 4e2a73031..d8156fe0f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts +++ b/packages/grpc-js-xds/src/generated/envoy/extensions/load_balancing_policies/ring_hash/v3/RingHash.ts @@ -4,29 +4,55 @@ import type { UInt64Value as _google_protobuf_UInt64Value, UInt64Value__Output a import type { UInt32Value as _google_protobuf_UInt32Value, UInt32Value__Output as _google_protobuf_UInt32Value__Output } from '../../../../../google/protobuf/UInt32Value'; import type { ConsistentHashingLbConfig as _envoy_extensions_load_balancing_policies_common_v3_ConsistentHashingLbConfig, ConsistentHashingLbConfig__Output as _envoy_extensions_load_balancing_policies_common_v3_ConsistentHashingLbConfig__Output } from '../../../../../envoy/extensions/load_balancing_policies/common/v3/ConsistentHashingLbConfig'; import type { _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig, _envoy_extensions_load_balancing_policies_common_v3_LocalityLbConfig_LocalityWeightedLbConfig__Output } from '../../../../../envoy/extensions/load_balancing_policies/common/v3/LocalityLbConfig'; -import type { Long } from '@grpc/proto-loader'; // Original file: deps/envoy-api/envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto /** * The hash function used to hash hosts onto the ketama ring. */ -export enum _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction { +export const _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction = { /** * Currently defaults to XX_HASH. */ - DEFAULT_HASH = 0, + DEFAULT_HASH: 'DEFAULT_HASH', /** * Use `xxHash `_. */ - XX_HASH = 1, + XX_HASH: 'XX_HASH', /** * Use `MurmurHash2 `_, this is compatible with * std:hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled * on Linux and not macOS. */ - MURMUR_HASH_2 = 2, -} + MURMUR_HASH_2: 'MURMUR_HASH_2', +} as const; + +/** + * The hash function used to hash hosts onto the ketama ring. + */ +export type _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction = + /** + * Currently defaults to XX_HASH. + */ + | 'DEFAULT_HASH' + | 0 + /** + * Use `xxHash `_. + */ + | 'XX_HASH' + | 1 + /** + * Use `MurmurHash2 `_, this is compatible with + * std:hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled + * on Linux and not macOS. + */ + | 'MURMUR_HASH_2' + | 2 + +/** + * The hash function used to hash hosts onto the ketama ring. + */ +export type _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction__Output = typeof _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction[keyof typeof _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction] /** * This configuration allows the built-in RING_HASH LB policy to be configured via the LB policy @@ -39,7 +65,7 @@ export interface RingHash { * The hash function used to hash hosts onto the ketama ring. The value defaults to * :ref:`XX_HASH`. */ - 'hash_function'?: (_envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction | keyof typeof _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction); + 'hash_function'?: (_envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction); /** * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each * provided host) the better the request distribution will reflect the desired weights. Defaults @@ -60,6 +86,7 @@ export interface RingHash { * ..note:: * This is deprecated and please use :ref:`consistent_hashing_lb_config * ` instead. + * @deprecated */ 'use_hostname_for_hashing'?: (boolean); /** @@ -83,6 +110,7 @@ export interface RingHash { * ..note:: * This is deprecated and please use :ref:`consistent_hashing_lb_config * ` instead. + * @deprecated */ 'hash_balance_factor'?: (_google_protobuf_UInt32Value | null); /** @@ -106,7 +134,7 @@ export interface RingHash__Output { * The hash function used to hash hosts onto the ketama ring. The value defaults to * :ref:`XX_HASH`. */ - 'hash_function': (keyof typeof _envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction); + 'hash_function': (_envoy_extensions_load_balancing_policies_ring_hash_v3_RingHash_HashFunction__Output); /** * Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each * provided host) the better the request distribution will reflect the desired weights. Defaults @@ -127,6 +155,7 @@ export interface RingHash__Output { * ..note:: * This is deprecated and please use :ref:`consistent_hashing_lb_config * ` instead. + * @deprecated */ 'use_hostname_for_hashing': (boolean); /** @@ -150,6 +179,7 @@ export interface RingHash__Output { * ..note:: * This is deprecated and please use :ref:`consistent_hashing_lb_config * ` instead. + * @deprecated */ 'hash_balance_factor': (_google_protobuf_UInt32Value__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts deleted file mode 100644 index 3a3100f55..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - - -/** - * Indicates a certificate to be obtained from a named CertificateProvider plugin instance. - * The plugin instances are defined in the client's bootstrap file. - * The plugin allows certificates to be fetched/refreshed over the network asynchronously with - * respect to the TLS handshake. - * [#not-implemented-hide:] - */ -export interface CertificateProviderPluginInstance { - /** - * Provider instance name. If not present, defaults to "default". - * - * Instance names should generally be defined not in terms of the underlying provider - * implementation (e.g., "file_watcher") but rather in terms of the function of the - * certificates (e.g., "foo_deployment_identity"). - */ - 'instance_name'?: (string); - /** - * Opaque name used to specify certificate instances or types. For example, "ROOTCA" to specify - * a root-certificate (validation context) or "example.com" to specify a certificate for a - * particular domain. Not all provider instances will actually use this field, so the value - * defaults to the empty string. - */ - 'certificate_name'?: (string); -} - -/** - * Indicates a certificate to be obtained from a named CertificateProvider plugin instance. - * The plugin instances are defined in the client's bootstrap file. - * The plugin allows certificates to be fetched/refreshed over the network asynchronously with - * respect to the TLS handshake. - * [#not-implemented-hide:] - */ -export interface CertificateProviderPluginInstance__Output { - /** - * Provider instance name. If not present, defaults to "default". - * - * Instance names should generally be defined not in terms of the underlying provider - * implementation (e.g., "file_watcher") but rather in terms of the function of the - * certificates (e.g., "foo_deployment_identity"). - */ - 'instance_name': (string); - /** - * Opaque name used to specify certificate instances or types. For example, "ROOTCA" to specify - * a root-certificate (validation context) or "example.com" to specify a certificate for a - * particular domain. Not all provider instances will actually use this field, so the value - * defaults to the empty string. - */ - 'certificate_name': (string); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts deleted file mode 100644 index 379320086..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext.ts +++ /dev/null @@ -1,372 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - -import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; -import type { BoolValue as _google_protobuf_BoolValue, BoolValue__Output as _google_protobuf_BoolValue__Output } from '../../../../../google/protobuf/BoolValue'; -import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../../envoy/type/matcher/v3/StringMatcher'; -import type { WatchedDirectory as _envoy_config_core_v3_WatchedDirectory, WatchedDirectory__Output as _envoy_config_core_v3_WatchedDirectory__Output } from '../../../../../envoy/config/core/v3/WatchedDirectory'; -import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../../envoy/config/core/v3/TypedExtensionConfig'; -import type { CertificateProviderPluginInstance as _envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance, CertificateProviderPluginInstance__Output as _envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/CertificateProviderPluginInstance'; - -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - -/** - * Peer certificate verification mode. - */ -export enum _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification { - /** - * Perform default certificate verification (e.g., against CA / verification lists) - */ - VERIFY_TRUST_CHAIN = 0, - /** - * Connections where the certificate fails verification will be permitted. - * For HTTP connections, the result of certificate verification can be used in route matching. ( - * see :ref:`validated ` ). - */ - ACCEPT_UNTRUSTED = 1, -} - -/** - * [#next-free-field: 14] - */ -export interface CertificateValidationContext { - /** - * TLS certificate data containing certificate authority certificates to use in verifying - * a presented peer certificate (e.g. server certificate for clusters or client certificate - * for listeners). If not specified and a peer certificate is presented it will not be - * verified. By default, a client certificate is optional, unless one of the additional - * options (:ref:`require_client_certificate - * `, - * :ref:`verify_certificate_spki - * `, - * :ref:`verify_certificate_hash - * `, or - * :ref:`match_subject_alt_names - * `) is also - * specified. - * - * It can optionally contain certificate revocation lists, in which case Envoy will verify - * that the presented peer certificate has not been revoked by one of the included CRLs. Note - * that if a CRL is provided for any certificate authority in a trust chain, a CRL must be - * provided for all certificate authorities in that chain. Failure to do so will result in - * verification failure for both revoked and unrevoked certificates from that chain. - * - * See :ref:`the TLS overview ` for a list of common - * system CA locations. - * - * If *trusted_ca* is a filesystem path, a watch will be added to the parent - * directory for any file moves to support rotation. This currently only - * applies to dynamic secrets, when the *CertificateValidationContext* is - * delivered via SDS. - * - * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. - * - * [#next-major-version: This field and watched_directory below should ideally be moved into a - * separate sub-message, since there's no point in specifying the latter field without this one.] - */ - 'trusted_ca'?: (_envoy_config_core_v3_DataSource | null); - /** - * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that - * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. - * - * A hex-encoded SHA-256 of the certificate can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 - * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a - * - * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 - * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A - * - * Both of those formats are acceptable. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - */ - 'verify_certificate_hash'?: (string)[]; - /** - * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the - * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate - * matches one of the specified values. - * - * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -pubkey - * | openssl pkey -pubin -outform DER - * | openssl dgst -sha256 -binary - * | openssl enc -base64 - * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= - * - * This is the format used in HTTP Public Key Pinning. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - * - * .. attention:: - * - * This option is preferred over :ref:`verify_certificate_hash - * `, - * because SPKI is tied to a private key, so it doesn't change when the certificate - * is renewed using the same private key. - */ - 'verify_certificate_spki'?: (string)[]; - /** - * [#not-implemented-hide:] Must present signed certificate time-stamp. - */ - 'require_signed_certificate_timestamp'?: (_google_protobuf_BoolValue | null); - /** - * An optional `certificate revocation list - * `_ - * (in PEM format). If specified, Envoy will verify that the presented peer - * certificate has not been revoked by this CRL. If this DataSource contains - * multiple CRLs, all of them will be used. Note that if a CRL is provided - * for any certificate authority in a trust chain, a CRL must be provided - * for all certificate authorities in that chain. Failure to do so will - * result in verification failure for both revoked and unrevoked certificates - * from that chain. - */ - 'crl'?: (_envoy_config_core_v3_DataSource | null); - /** - * If specified, Envoy will not reject expired certificates. - */ - 'allow_expired_certificate'?: (boolean); - /** - * An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the - * Subject Alternative Name of the presented certificate matches one of the specified matchers. - * - * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - * configured with exact match type in the :ref:`string matcher `. - * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", - * it should be configured as shown below. - * - * .. code-block:: yaml - * - * match_subject_alt_names: - * exact: "api.example.com" - * - * .. attention:: - * - * Subject Alternative Names are easily spoofable and verifying only them is insecure, - * therefore this option must be used together with :ref:`trusted_ca - * `. - */ - 'match_subject_alt_names'?: (_envoy_type_matcher_v3_StringMatcher)[]; - /** - * Certificate trust chain verification mode. - */ - 'trust_chain_verification'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification | keyof typeof _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification); - /** - * If specified, updates of a file-based *trusted_ca* source will be triggered - * by this watch. This allows explicit control over the path watched, by - * default the parent directory of the filesystem path in *trusted_ca* is - * watched if this field is not specified. This only applies when a - * *CertificateValidationContext* is delivered by SDS with references to - * filesystem paths. See the :ref:`SDS key rotation ` - * documentation for further details. - */ - 'watched_directory'?: (_envoy_config_core_v3_WatchedDirectory | null); - /** - * The configuration of an extension specific certificate validator. - * If specified, all validation is done by the specified validator, - * and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). - * Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. - * [#extension-category: envoy.tls.cert_validator] - */ - 'custom_validator_config'?: (_envoy_config_core_v3_TypedExtensionConfig | null); - /** - * Certificate provider instance for fetching TLS certificates. - * - * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. - * [#not-implemented-hide:] - */ - 'ca_certificate_provider_instance'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance | null); -} - -/** - * [#next-free-field: 14] - */ -export interface CertificateValidationContext__Output { - /** - * TLS certificate data containing certificate authority certificates to use in verifying - * a presented peer certificate (e.g. server certificate for clusters or client certificate - * for listeners). If not specified and a peer certificate is presented it will not be - * verified. By default, a client certificate is optional, unless one of the additional - * options (:ref:`require_client_certificate - * `, - * :ref:`verify_certificate_spki - * `, - * :ref:`verify_certificate_hash - * `, or - * :ref:`match_subject_alt_names - * `) is also - * specified. - * - * It can optionally contain certificate revocation lists, in which case Envoy will verify - * that the presented peer certificate has not been revoked by one of the included CRLs. Note - * that if a CRL is provided for any certificate authority in a trust chain, a CRL must be - * provided for all certificate authorities in that chain. Failure to do so will result in - * verification failure for both revoked and unrevoked certificates from that chain. - * - * See :ref:`the TLS overview ` for a list of common - * system CA locations. - * - * If *trusted_ca* is a filesystem path, a watch will be added to the parent - * directory for any file moves to support rotation. This currently only - * applies to dynamic secrets, when the *CertificateValidationContext* is - * delivered via SDS. - * - * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. - * - * [#next-major-version: This field and watched_directory below should ideally be moved into a - * separate sub-message, since there's no point in specifying the latter field without this one.] - */ - 'trusted_ca': (_envoy_config_core_v3_DataSource__Output | null); - /** - * An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that - * the SHA-256 of the DER-encoded presented certificate matches one of the specified values. - * - * A hex-encoded SHA-256 of the certificate can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 - * df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a - * - * A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 - * DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A - * - * Both of those formats are acceptable. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - */ - 'verify_certificate_hash': (string)[]; - /** - * An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the - * SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate - * matches one of the specified values. - * - * A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate - * can be generated with the following command: - * - * .. code-block:: bash - * - * $ openssl x509 -in path/to/client.crt -noout -pubkey - * | openssl pkey -pubin -outform DER - * | openssl dgst -sha256 -binary - * | openssl enc -base64 - * NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= - * - * This is the format used in HTTP Public Key Pinning. - * - * When both: - * :ref:`verify_certificate_hash - * ` and - * :ref:`verify_certificate_spki - * ` are specified, - * a hash matching value from either of the lists will result in the certificate being accepted. - * - * .. attention:: - * - * This option is preferred over :ref:`verify_certificate_hash - * `, - * because SPKI is tied to a private key, so it doesn't change when the certificate - * is renewed using the same private key. - */ - 'verify_certificate_spki': (string)[]; - /** - * [#not-implemented-hide:] Must present signed certificate time-stamp. - */ - 'require_signed_certificate_timestamp': (_google_protobuf_BoolValue__Output | null); - /** - * An optional `certificate revocation list - * `_ - * (in PEM format). If specified, Envoy will verify that the presented peer - * certificate has not been revoked by this CRL. If this DataSource contains - * multiple CRLs, all of them will be used. Note that if a CRL is provided - * for any certificate authority in a trust chain, a CRL must be provided - * for all certificate authorities in that chain. Failure to do so will - * result in verification failure for both revoked and unrevoked certificates - * from that chain. - */ - 'crl': (_envoy_config_core_v3_DataSource__Output | null); - /** - * If specified, Envoy will not reject expired certificates. - */ - 'allow_expired_certificate': (boolean); - /** - * An optional list of Subject Alternative name matchers. If specified, Envoy will verify that the - * Subject Alternative Name of the presented certificate matches one of the specified matchers. - * - * When a certificate has wildcard DNS SAN entries, to match a specific client, it should be - * configured with exact match type in the :ref:`string matcher `. - * For example if the certificate has "\*.example.com" as DNS SAN entry, to allow only "api.example.com", - * it should be configured as shown below. - * - * .. code-block:: yaml - * - * match_subject_alt_names: - * exact: "api.example.com" - * - * .. attention:: - * - * Subject Alternative Names are easily spoofable and verifying only them is insecure, - * therefore this option must be used together with :ref:`trusted_ca - * `. - */ - 'match_subject_alt_names': (_envoy_type_matcher_v3_StringMatcher__Output)[]; - /** - * Certificate trust chain verification mode. - */ - 'trust_chain_verification': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_TrustChainVerification); - /** - * If specified, updates of a file-based *trusted_ca* source will be triggered - * by this watch. This allows explicit control over the path watched, by - * default the parent directory of the filesystem path in *trusted_ca* is - * watched if this field is not specified. This only applies when a - * *CertificateValidationContext* is delivered by SDS with references to - * filesystem paths. See the :ref:`SDS key rotation ` - * documentation for further details. - */ - 'watched_directory': (_envoy_config_core_v3_WatchedDirectory__Output | null); - /** - * The configuration of an extension specific certificate validator. - * If specified, all validation is done by the specified validator, - * and the behavior of all other validation settings is defined by the specified validator (and may be entirely ignored, unused, and unvalidated). - * Refer to the documentation for the specified validator. If you do not want a custom validation algorithm, do not set this field. - * [#extension-category: envoy.tls.cert_validator] - */ - 'custom_validator_config': (_envoy_config_core_v3_TypedExtensionConfig__Output | null); - /** - * Certificate provider instance for fetching TLS certificates. - * - * Only one of *trusted_ca* and *ca_certificate_provider_instance* may be specified. - * [#not-implemented-hide:] - */ - 'ca_certificate_provider_instance': (_envoy_extensions_transport_sockets_tls_v3_CertificateProviderPluginInstance__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts deleted file mode 100644 index b206fb13a..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/GenericSecret.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto - -import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; - -export interface GenericSecret { - /** - * Secret of generic type and is available to filters. - */ - 'secret'?: (_envoy_config_core_v3_DataSource | null); -} - -export interface GenericSecret__Output { - /** - * Secret of generic type and is available to filters. - */ - 'secret': (_envoy_config_core_v3_DataSource__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts deleted file mode 100644 index b4a2ad933..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - -import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../../google/protobuf/Any'; - -/** - * BoringSSL private key method configuration. The private key methods are used for external - * (potentially asynchronous) signing and decryption operations. Some use cases for private key - * methods would be TPM support and TLS acceleration. - */ -export interface PrivateKeyProvider { - /** - * Private key method provider name. The name must match a - * supported private key method provider type. - */ - 'provider_name'?: (string); - 'typed_config'?: (_google_protobuf_Any | null); - /** - * Private key method provider specific configuration. - */ - 'config_type'?: "typed_config"; -} - -/** - * BoringSSL private key method configuration. The private key methods are used for external - * (potentially asynchronous) signing and decryption operations. Some use cases for private key - * methods would be TPM support and TLS acceleration. - */ -export interface PrivateKeyProvider__Output { - /** - * Private key method provider name. The name must match a - * supported private key method provider type. - */ - 'provider_name': (string); - 'typed_config'?: (_google_protobuf_Any__Output | null); - /** - * Private key method provider specific configuration. - */ - 'config_type': "typed_config"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts deleted file mode 100644 index 38b850c50..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/SdsSecretConfig.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto - -import type { ConfigSource as _envoy_config_core_v3_ConfigSource, ConfigSource__Output as _envoy_config_core_v3_ConfigSource__Output } from '../../../../../envoy/config/core/v3/ConfigSource'; - -export interface SdsSecretConfig { - /** - * Name by which the secret can be uniquely referred to. When both name and config are specified, - * then secret can be fetched and/or reloaded via SDS. When only name is specified, then secret - * will be loaded from static resources. - */ - 'name'?: (string); - 'sds_config'?: (_envoy_config_core_v3_ConfigSource | null); -} - -export interface SdsSecretConfig__Output { - /** - * Name by which the secret can be uniquely referred to. When both name and config are specified, - * then secret can be fetched and/or reloaded via SDS. When only name is specified, then secret - * will be loaded from static resources. - */ - 'name': (string); - 'sds_config': (_envoy_config_core_v3_ConfigSource__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts deleted file mode 100644 index c86957da5..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/Secret.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/secret.proto - -import type { TlsCertificate as _envoy_extensions_transport_sockets_tls_v3_TlsCertificate, TlsCertificate__Output as _envoy_extensions_transport_sockets_tls_v3_TlsCertificate__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/TlsCertificate'; -import type { TlsSessionTicketKeys as _envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys, TlsSessionTicketKeys__Output as _envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys'; -import type { CertificateValidationContext as _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext, CertificateValidationContext__Output as _envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext'; -import type { GenericSecret as _envoy_extensions_transport_sockets_tls_v3_GenericSecret, GenericSecret__Output as _envoy_extensions_transport_sockets_tls_v3_GenericSecret__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/GenericSecret'; - -/** - * [#next-free-field: 6] - */ -export interface Secret { - /** - * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. - */ - 'name'?: (string); - 'tls_certificate'?: (_envoy_extensions_transport_sockets_tls_v3_TlsCertificate | null); - 'session_ticket_keys'?: (_envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys | null); - 'validation_context'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext | null); - 'generic_secret'?: (_envoy_extensions_transport_sockets_tls_v3_GenericSecret | null); - 'type'?: "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; -} - -/** - * [#next-free-field: 6] - */ -export interface Secret__Output { - /** - * Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. - */ - 'name': (string); - 'tls_certificate'?: (_envoy_extensions_transport_sockets_tls_v3_TlsCertificate__Output | null); - 'session_ticket_keys'?: (_envoy_extensions_transport_sockets_tls_v3_TlsSessionTicketKeys__Output | null); - 'validation_context'?: (_envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext__Output | null); - 'generic_secret'?: (_envoy_extensions_transport_sockets_tls_v3_GenericSecret__Output | null); - 'type': "tls_certificate"|"session_ticket_keys"|"validation_context"|"generic_secret"; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts deleted file mode 100644 index ce8046e95..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsCertificate.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - -import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; -import type { PrivateKeyProvider as _envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider, PrivateKeyProvider__Output as _envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider__Output } from '../../../../../envoy/extensions/transport_sockets/tls/v3/PrivateKeyProvider'; -import type { WatchedDirectory as _envoy_config_core_v3_WatchedDirectory, WatchedDirectory__Output as _envoy_config_core_v3_WatchedDirectory__Output } from '../../../../../envoy/config/core/v3/WatchedDirectory'; - -/** - * [#next-free-field: 8] - */ -export interface TlsCertificate { - /** - * The TLS certificate chain. - * - * If *certificate_chain* is a filesystem path, a watch will be added to the - * parent directory for any file moves to support rotation. This currently - * only applies to dynamic secrets, when the *TlsCertificate* is delivered via - * SDS. - */ - 'certificate_chain'?: (_envoy_config_core_v3_DataSource | null); - /** - * The TLS private key. - * - * If *private_key* is a filesystem path, a watch will be added to the parent - * directory for any file moves to support rotation. This currently only - * applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. - */ - 'private_key'?: (_envoy_config_core_v3_DataSource | null); - /** - * The password to decrypt the TLS private key. If this field is not set, it is assumed that the - * TLS private key is not password encrypted. - */ - 'password'?: (_envoy_config_core_v3_DataSource | null); - /** - * The OCSP response to be stapled with this certificate during the handshake. - * The response must be DER-encoded and may only be provided via ``filename`` or - * ``inline_bytes``. The response may pertain to only one certificate. - */ - 'ocsp_staple'?: (_envoy_config_core_v3_DataSource | null); - /** - * [#not-implemented-hide:] - */ - 'signed_certificate_timestamp'?: (_envoy_config_core_v3_DataSource)[]; - /** - * BoringSSL private key method provider. This is an alternative to :ref:`private_key - * ` field. This can't be - * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - * ` and - * :ref:`private_key_provider - * ` fields will result in an - * error. - */ - 'private_key_provider'?: (_envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider | null); - /** - * If specified, updates of file-based *certificate_chain* and *private_key* - * sources will be triggered by this watch. The certificate/key pair will be - * read together and validated for atomic read consistency (i.e. no - * intervening modification occurred between cert/key read, verified by file - * hash comparisons). This allows explicit control over the path watched, by - * default the parent directories of the filesystem paths in - * *certificate_chain* and *private_key* are watched if this field is not - * specified. This only applies when a *TlsCertificate* is delivered by SDS - * with references to filesystem paths. See the :ref:`SDS key rotation - * ` documentation for further details. - */ - 'watched_directory'?: (_envoy_config_core_v3_WatchedDirectory | null); -} - -/** - * [#next-free-field: 8] - */ -export interface TlsCertificate__Output { - /** - * The TLS certificate chain. - * - * If *certificate_chain* is a filesystem path, a watch will be added to the - * parent directory for any file moves to support rotation. This currently - * only applies to dynamic secrets, when the *TlsCertificate* is delivered via - * SDS. - */ - 'certificate_chain': (_envoy_config_core_v3_DataSource__Output | null); - /** - * The TLS private key. - * - * If *private_key* is a filesystem path, a watch will be added to the parent - * directory for any file moves to support rotation. This currently only - * applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. - */ - 'private_key': (_envoy_config_core_v3_DataSource__Output | null); - /** - * The password to decrypt the TLS private key. If this field is not set, it is assumed that the - * TLS private key is not password encrypted. - */ - 'password': (_envoy_config_core_v3_DataSource__Output | null); - /** - * The OCSP response to be stapled with this certificate during the handshake. - * The response must be DER-encoded and may only be provided via ``filename`` or - * ``inline_bytes``. The response may pertain to only one certificate. - */ - 'ocsp_staple': (_envoy_config_core_v3_DataSource__Output | null); - /** - * [#not-implemented-hide:] - */ - 'signed_certificate_timestamp': (_envoy_config_core_v3_DataSource__Output)[]; - /** - * BoringSSL private key method provider. This is an alternative to :ref:`private_key - * ` field. This can't be - * marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key - * ` and - * :ref:`private_key_provider - * ` fields will result in an - * error. - */ - 'private_key_provider': (_envoy_extensions_transport_sockets_tls_v3_PrivateKeyProvider__Output | null); - /** - * If specified, updates of file-based *certificate_chain* and *private_key* - * sources will be triggered by this watch. The certificate/key pair will be - * read together and validated for atomic read consistency (i.e. no - * intervening modification occurred between cert/key read, verified by file - * hash comparisons). This allows explicit control over the path watched, by - * default the parent directories of the filesystem paths in - * *certificate_chain* and *private_key* are watched if this field is not - * specified. This only applies when a *TlsCertificate* is delivered by SDS - * with references to filesystem paths. See the :ref:`SDS key rotation - * ` documentation for further details. - */ - 'watched_directory': (_envoy_config_core_v3_WatchedDirectory__Output | null); -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts deleted file mode 100644 index e68464c8b..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsParameters.ts +++ /dev/null @@ -1,211 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - - -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - -export enum _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol { - /** - * Envoy will choose the optimal TLS version. - */ - TLS_AUTO = 0, - /** - * TLS 1.0 - */ - TLSv1_0 = 1, - /** - * TLS 1.1 - */ - TLSv1_1 = 2, - /** - * TLS 1.2 - */ - TLSv1_2 = 3, - /** - * TLS 1.3 - */ - TLSv1_3 = 4, -} - -export interface TlsParameters { - /** - * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for - * servers. - */ - 'tls_minimum_protocol_version'?: (_envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol | keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); - /** - * Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for - * servers. - */ - 'tls_maximum_protocol_version'?: (_envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol | keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); - /** - * If specified, the TLS listener will only support the specified `cipher list - * `_ - * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). - * - * If not specified, a default list will be used. Defaults are different for server (downstream) and - * client (upstream) TLS configurations. - * - * In non-FIPS builds, the default server cipher list is: - * - * .. code-block:: none - * - * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] - * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - * - * In builds using :ref:`BoringSSL FIPS `, the default server cipher list is: - * - * .. code-block:: none - * - * ECDHE-ECDSA-AES128-GCM-SHA256 - * ECDHE-RSA-AES128-GCM-SHA256 - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - * - * In non-FIPS builds, the default client cipher list is: - * - * .. code-block:: none - * - * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] - * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * - * In builds using :ref:`BoringSSL FIPS `, the default client cipher list is: - * - * .. code-block:: none - * - * ECDHE-ECDSA-AES128-GCM-SHA256 - * ECDHE-RSA-AES128-GCM-SHA256 - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - */ - 'cipher_suites'?: (string)[]; - /** - * If specified, the TLS connection will only support the specified ECDH - * curves. If not specified, the default curves will be used. - * - * In non-FIPS builds, the default curves are: - * - * .. code-block:: none - * - * X25519 - * P-256 - * - * In builds using :ref:`BoringSSL FIPS `, the default curve is: - * - * .. code-block:: none - * - * P-256 - */ - 'ecdh_curves'?: (string)[]; -} - -export interface TlsParameters__Output { - /** - * Minimum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_0`` for - * servers. - */ - 'tls_minimum_protocol_version': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); - /** - * Maximum TLS protocol version. By default, it's ``TLSv1_2`` for clients and ``TLSv1_3`` for - * servers. - */ - 'tls_maximum_protocol_version': (keyof typeof _envoy_extensions_transport_sockets_tls_v3_TlsParameters_TlsProtocol); - /** - * If specified, the TLS listener will only support the specified `cipher list - * `_ - * when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). - * - * If not specified, a default list will be used. Defaults are different for server (downstream) and - * client (upstream) TLS configurations. - * - * In non-FIPS builds, the default server cipher list is: - * - * .. code-block:: none - * - * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] - * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - * - * In builds using :ref:`BoringSSL FIPS `, the default server cipher list is: - * - * .. code-block:: none - * - * ECDHE-ECDSA-AES128-GCM-SHA256 - * ECDHE-RSA-AES128-GCM-SHA256 - * ECDHE-ECDSA-AES128-SHA - * ECDHE-RSA-AES128-SHA - * AES128-GCM-SHA256 - * AES128-SHA - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * ECDHE-ECDSA-AES256-SHA - * ECDHE-RSA-AES256-SHA - * AES256-GCM-SHA384 - * AES256-SHA - * - * In non-FIPS builds, the default client cipher list is: - * - * .. code-block:: none - * - * [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] - * [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - * - * In builds using :ref:`BoringSSL FIPS `, the default client cipher list is: - * - * .. code-block:: none - * - * ECDHE-ECDSA-AES128-GCM-SHA256 - * ECDHE-RSA-AES128-GCM-SHA256 - * ECDHE-ECDSA-AES256-GCM-SHA384 - * ECDHE-RSA-AES256-GCM-SHA384 - */ - 'cipher_suites': (string)[]; - /** - * If specified, the TLS connection will only support the specified ECDH - * curves. If not specified, the default curves will be used. - * - * In non-FIPS builds, the default curves are: - * - * .. code-block:: none - * - * X25519 - * P-256 - * - * In builds using :ref:`BoringSSL FIPS `, the default curve is: - * - * .. code-block:: none - * - * P-256 - */ - 'ecdh_curves': (string)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts b/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts deleted file mode 100644 index 152bccac7..000000000 --- a/packages/grpc-js-xds/src/generated/envoy/extensions/transport_sockets/tls/v3/TlsSessionTicketKeys.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Original file: deps/envoy-api/envoy/extensions/transport_sockets/tls/v3/common.proto - -import type { DataSource as _envoy_config_core_v3_DataSource, DataSource__Output as _envoy_config_core_v3_DataSource__Output } from '../../../../../envoy/config/core/v3/DataSource'; - -export interface TlsSessionTicketKeys { - /** - * Keys for encrypting and decrypting TLS session tickets. The - * first key in the array contains the key to encrypt all new sessions created by this context. - * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys - * by, for example, putting the new key first, and the previous key second. - * - * If :ref:`session_ticket_keys ` - * is not specified, the TLS library will still support resuming sessions via tickets, but it will - * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts - * or on different hosts. - * - * Each key must contain exactly 80 bytes of cryptographically-secure random data. For - * example, the output of ``openssl rand 80``. - * - * .. attention:: - * - * Using this feature has serious security considerations and risks. Improper handling of keys - * may result in loss of secrecy in connections, even if ciphers supporting perfect forward - * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some - * discussion. To minimize the risk, you must: - * - * * Keep the session ticket keys at least as secure as your TLS certificate private keys - * * Rotate session ticket keys at least daily, and preferably hourly - * * Always generate keys using a cryptographically-secure random data source - */ - 'keys'?: (_envoy_config_core_v3_DataSource)[]; -} - -export interface TlsSessionTicketKeys__Output { - /** - * Keys for encrypting and decrypting TLS session tickets. The - * first key in the array contains the key to encrypt all new sessions created by this context. - * All keys are candidates for decrypting received tickets. This allows for easy rotation of keys - * by, for example, putting the new key first, and the previous key second. - * - * If :ref:`session_ticket_keys ` - * is not specified, the TLS library will still support resuming sessions via tickets, but it will - * use an internally-generated and managed key, so sessions cannot be resumed across hot restarts - * or on different hosts. - * - * Each key must contain exactly 80 bytes of cryptographically-secure random data. For - * example, the output of ``openssl rand 80``. - * - * .. attention:: - * - * Using this feature has serious security considerations and risks. Improper handling of keys - * may result in loss of secrecy in connections, even if ciphers supporting perfect forward - * secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some - * discussion. To minimize the risk, you must: - * - * * Keep the session ticket keys at least as secure as your TLS certificate private keys - * * Rotate session ticket keys at least daily, and preferably hourly - * * Always generate keys using a cryptographically-secure random data source - */ - 'keys': (_envoy_config_core_v3_DataSource__Output)[]; -} diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts index ba6b25b4c..506547d1e 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfig.ts @@ -4,8 +4,8 @@ import type { Node as _envoy_config_core_v3_Node, Node__Output as _envoy_config_ import type { PerXdsConfig as _envoy_service_status_v3_PerXdsConfig, PerXdsConfig__Output as _envoy_service_status_v3_PerXdsConfig__Output } from '../../../../envoy/service/status/v3/PerXdsConfig'; import type { Any as _google_protobuf_Any, Any__Output as _google_protobuf_Any__Output } from '../../../../google/protobuf/Any'; import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../../google/protobuf/Timestamp'; -import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus } from '../../../../envoy/service/status/v3/ConfigStatus'; -import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus } from '../../../../envoy/admin/v3/ClientResourceStatus'; +import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus, ConfigStatus__Output as _envoy_service_status_v3_ConfigStatus__Output } from '../../../../envoy/service/status/v3/ConfigStatus'; +import type { ClientResourceStatus as _envoy_admin_v3_ClientResourceStatus, ClientResourceStatus__Output as _envoy_admin_v3_ClientResourceStatus__Output } from '../../../../envoy/admin/v3/ClientResourceStatus'; import type { UpdateFailureState as _envoy_admin_v3_UpdateFailureState, UpdateFailureState__Output as _envoy_admin_v3_UpdateFailureState__Output } from '../../../../envoy/admin/v3/UpdateFailureState'; /** @@ -42,11 +42,11 @@ export interface _envoy_service_status_v3_ClientConfig_GenericXdsConfig { * Per xDS resource config status. It is generated by management servers. * It will not be present if the CSDS server is an xDS client. */ - 'config_status'?: (_envoy_service_status_v3_ConfigStatus | keyof typeof _envoy_service_status_v3_ConfigStatus); + 'config_status'?: (_envoy_service_status_v3_ConfigStatus); /** * Per xDS resource status from the view of a xDS client */ - 'client_status'?: (_envoy_admin_v3_ClientResourceStatus | keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status'?: (_envoy_admin_v3_ClientResourceStatus); /** * Set if the last update failed, cleared after the next successful * update. The *error_state* field contains the rejected version of @@ -97,11 +97,11 @@ export interface _envoy_service_status_v3_ClientConfig_GenericXdsConfig__Output * Per xDS resource config status. It is generated by management servers. * It will not be present if the CSDS server is an xDS client. */ - 'config_status': (keyof typeof _envoy_service_status_v3_ConfigStatus); + 'config_status': (_envoy_service_status_v3_ConfigStatus__Output); /** * Per xDS resource status from the view of a xDS client */ - 'client_status': (keyof typeof _envoy_admin_v3_ClientResourceStatus); + 'client_status': (_envoy_admin_v3_ClientResourceStatus__Output); /** * Set if the last update failed, cleared after the next successful * update. The *error_state* field contains the rejected version of @@ -129,6 +129,7 @@ export interface ClientConfig { /** * This field is deprecated in favor of generic_xds_configs which is * much simpler and uniform in structure. + * @deprecated */ 'xds_config'?: (_envoy_service_status_v3_PerXdsConfig)[]; /** @@ -149,6 +150,7 @@ export interface ClientConfig__Output { /** * This field is deprecated in favor of generic_xds_configs which is * much simpler and uniform in structure. + * @deprecated */ 'xds_config': (_envoy_service_status_v3_PerXdsConfig__Output)[]; /** diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts index 104445a3f..be7a7afd0 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ClientConfigStatus.ts @@ -3,24 +3,57 @@ /** * Config status from a client-side view. */ -export enum ClientConfigStatus { +export const ClientConfigStatus = { /** * Config status is not available/unknown. */ - CLIENT_UNKNOWN = 0, + CLIENT_UNKNOWN: 'CLIENT_UNKNOWN', /** * Client requested the config but hasn't received any config from management * server yet. */ - CLIENT_REQUESTED = 1, + CLIENT_REQUESTED: 'CLIENT_REQUESTED', /** * Client received the config and replied with ACK. */ - CLIENT_ACKED = 2, + CLIENT_ACKED: 'CLIENT_ACKED', /** * Client received the config and replied with NACK. Notably, the attached * config dump is not the NACKed version, but the most recent accepted one. If * no config is accepted yet, the attached config dump will be empty. */ - CLIENT_NACKED = 3, -} + CLIENT_NACKED: 'CLIENT_NACKED', +} as const; + +/** + * Config status from a client-side view. + */ +export type ClientConfigStatus = + /** + * Config status is not available/unknown. + */ + | 'CLIENT_UNKNOWN' + | 0 + /** + * Client requested the config but hasn't received any config from management + * server yet. + */ + | 'CLIENT_REQUESTED' + | 1 + /** + * Client received the config and replied with ACK. + */ + | 'CLIENT_ACKED' + | 2 + /** + * Client received the config and replied with NACK. Notably, the attached + * config dump is not the NACKed version, but the most recent accepted one. If + * no config is accepted yet, the attached config dump will be empty. + */ + | 'CLIENT_NACKED' + | 3 + +/** + * Config status from a client-side view. + */ +export type ClientConfigStatus__Output = typeof ClientConfigStatus[keyof typeof ClientConfigStatus] diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts index 71db302c3..15a8359e8 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/ConfigStatus.ts @@ -3,28 +3,66 @@ /** * Status of a config from a management server view. */ -export enum ConfigStatus { +export const ConfigStatus = { /** * Status info is not available/unknown. */ - UNKNOWN = 0, + UNKNOWN: 'UNKNOWN', /** * Management server has sent the config to client and received ACK. */ - SYNCED = 1, + SYNCED: 'SYNCED', /** * Config is not sent. */ - NOT_SENT = 2, + NOT_SENT: 'NOT_SENT', /** * Management server has sent the config to client but hasn’t received * ACK/NACK. */ - STALE = 3, + STALE: 'STALE', /** * Management server has sent the config to client but received NACK. The * attached config dump will be the latest config (the rejected one), since * it is the persisted version in the management server. */ - ERROR = 4, -} + ERROR: 'ERROR', +} as const; + +/** + * Status of a config from a management server view. + */ +export type ConfigStatus = + /** + * Status info is not available/unknown. + */ + | 'UNKNOWN' + | 0 + /** + * Management server has sent the config to client and received ACK. + */ + | 'SYNCED' + | 1 + /** + * Config is not sent. + */ + | 'NOT_SENT' + | 2 + /** + * Management server has sent the config to client but hasn’t received + * ACK/NACK. + */ + | 'STALE' + | 3 + /** + * Management server has sent the config to client but received NACK. The + * attached config dump will be the latest config (the rejected one), since + * it is the persisted version in the management server. + */ + | 'ERROR' + | 4 + +/** + * Status of a config from a management server view. + */ +export type ConfigStatus__Output = typeof ConfigStatus[keyof typeof ConfigStatus] diff --git a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts index 947f1c81a..d921f3b1c 100644 --- a/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts +++ b/packages/grpc-js-xds/src/generated/envoy/service/status/v3/PerXdsConfig.ts @@ -1,12 +1,12 @@ // Original file: deps/envoy-api/envoy/service/status/v3/csds.proto -import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus } from '../../../../envoy/service/status/v3/ConfigStatus'; +import type { ConfigStatus as _envoy_service_status_v3_ConfigStatus, ConfigStatus__Output as _envoy_service_status_v3_ConfigStatus__Output } from '../../../../envoy/service/status/v3/ConfigStatus'; import type { ListenersConfigDump as _envoy_admin_v3_ListenersConfigDump, ListenersConfigDump__Output as _envoy_admin_v3_ListenersConfigDump__Output } from '../../../../envoy/admin/v3/ListenersConfigDump'; import type { ClustersConfigDump as _envoy_admin_v3_ClustersConfigDump, ClustersConfigDump__Output as _envoy_admin_v3_ClustersConfigDump__Output } from '../../../../envoy/admin/v3/ClustersConfigDump'; import type { RoutesConfigDump as _envoy_admin_v3_RoutesConfigDump, RoutesConfigDump__Output as _envoy_admin_v3_RoutesConfigDump__Output } from '../../../../envoy/admin/v3/RoutesConfigDump'; import type { ScopedRoutesConfigDump as _envoy_admin_v3_ScopedRoutesConfigDump, ScopedRoutesConfigDump__Output as _envoy_admin_v3_ScopedRoutesConfigDump__Output } from '../../../../envoy/admin/v3/ScopedRoutesConfigDump'; import type { EndpointsConfigDump as _envoy_admin_v3_EndpointsConfigDump, EndpointsConfigDump__Output as _envoy_admin_v3_EndpointsConfigDump__Output } from '../../../../envoy/admin/v3/EndpointsConfigDump'; -import type { ClientConfigStatus as _envoy_service_status_v3_ClientConfigStatus } from '../../../../envoy/service/status/v3/ClientConfigStatus'; +import type { ClientConfigStatus as _envoy_service_status_v3_ClientConfigStatus, ClientConfigStatus__Output as _envoy_service_status_v3_ClientConfigStatus__Output } from '../../../../envoy/service/status/v3/ClientConfigStatus'; /** * Detailed config (per xDS) with status. @@ -17,7 +17,7 @@ export interface PerXdsConfig { * Config status generated by management servers. Will not be present if the * CSDS server is an xDS client. */ - 'status'?: (_envoy_service_status_v3_ConfigStatus | keyof typeof _envoy_service_status_v3_ConfigStatus); + 'status'?: (_envoy_service_status_v3_ConfigStatus); 'listener_config'?: (_envoy_admin_v3_ListenersConfigDump | null); 'cluster_config'?: (_envoy_admin_v3_ClustersConfigDump | null); 'route_config'?: (_envoy_admin_v3_RoutesConfigDump | null); @@ -32,8 +32,9 @@ export interface PerXdsConfig { * This field is deprecated. Use :ref:`ClientResourceStatus * ` for per-resource * config status instead. + * @deprecated */ - 'client_status'?: (_envoy_service_status_v3_ClientConfigStatus | keyof typeof _envoy_service_status_v3_ClientConfigStatus); + 'client_status'?: (_envoy_service_status_v3_ClientConfigStatus); 'per_xds_config'?: "listener_config"|"cluster_config"|"route_config"|"scoped_route_config"|"endpoint_config"; } @@ -46,7 +47,7 @@ export interface PerXdsConfig__Output { * Config status generated by management servers. Will not be present if the * CSDS server is an xDS client. */ - 'status': (keyof typeof _envoy_service_status_v3_ConfigStatus); + 'status': (_envoy_service_status_v3_ConfigStatus__Output); 'listener_config'?: (_envoy_admin_v3_ListenersConfigDump__Output | null); 'cluster_config'?: (_envoy_admin_v3_ClustersConfigDump__Output | null); 'route_config'?: (_envoy_admin_v3_RoutesConfigDump__Output | null); @@ -61,7 +62,8 @@ export interface PerXdsConfig__Output { * This field is deprecated. Use :ref:`ClientResourceStatus * ` for per-resource * config status instead. + * @deprecated */ - 'client_status': (keyof typeof _envoy_service_status_v3_ClientConfigStatus); + 'client_status': (_envoy_service_status_v3_ClientConfigStatus__Output); 'per_xds_config': "listener_config"|"cluster_config"|"route_config"|"scoped_route_config"|"endpoint_config"; } diff --git a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts index c83f8b473..19517678f 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/RegexMatcher.ts @@ -31,6 +31,7 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2 { * * Although this field is deprecated, the program size will still be checked against the * global ``re2.max_program_size.error_level`` runtime value. + * @deprecated */ 'max_program_size'?: (_google_protobuf_UInt32Value | null); } @@ -64,6 +65,7 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output { * * Although this field is deprecated, the program size will still be checked against the * global ``re2.max_program_size.error_level`` runtime value. + * @deprecated */ 'max_program_size': (_google_protobuf_UInt32Value__Output | null); } @@ -74,6 +76,7 @@ export interface _envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output { export interface RegexMatcher { /** * Google's RE2 regex engine. + * @deprecated */ 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2 | null); /** @@ -90,6 +93,7 @@ export interface RegexMatcher { export interface RegexMatcher__Output { /** * Google's RE2 regex engine. + * @deprecated */ 'google_re2'?: (_envoy_type_matcher_v3_RegexMatcher_GoogleRE2__Output | null); /** diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts index 308f14446..e05cdfb96 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/CodecClientType.ts @@ -1,12 +1,27 @@ // Original file: deps/envoy-api/envoy/type/v3/http.proto -export enum CodecClientType { - HTTP1 = 0, - HTTP2 = 1, +export const CodecClientType = { + HTTP1: 'HTTP1', + HTTP2: 'HTTP2', /** * [#not-implemented-hide:] QUIC implementation is not production ready yet. Use this enum with * caution to prevent accidental execution of QUIC code. I.e. `!= HTTP2` is no longer sufficient * to distinguish HTTP1 and HTTP2 traffic. */ - HTTP3 = 2, -} + HTTP3: 'HTTP3', +} as const; + +export type CodecClientType = + | 'HTTP1' + | 0 + | 'HTTP2' + | 1 + /** + * [#not-implemented-hide:] QUIC implementation is not production ready yet. Use this enum with + * caution to prevent accidental execution of QUIC code. I.e. `!= HTTP2` is no longer sufficient + * to distinguish HTTP1 and HTTP2 traffic. + */ + | 'HTTP3' + | 2 + +export type CodecClientType__Output = typeof CodecClientType[keyof typeof CodecClientType] diff --git a/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts b/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts index 564af9a0f..c45441a79 100644 --- a/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts +++ b/packages/grpc-js-xds/src/generated/envoy/type/v3/FractionalPercent.ts @@ -6,26 +6,57 @@ /** * Fraction percentages support several fixed denominator values. */ -export enum _envoy_type_v3_FractionalPercent_DenominatorType { +export const _envoy_type_v3_FractionalPercent_DenominatorType = { /** * 100. * * **Example**: 1/100 = 1%. */ - HUNDRED = 0, + HUNDRED: 'HUNDRED', /** * 10,000. * * **Example**: 1/10000 = 0.01%. */ - TEN_THOUSAND = 1, + TEN_THOUSAND: 'TEN_THOUSAND', /** * 1,000,000. * * **Example**: 1/1000000 = 0.0001%. */ - MILLION = 2, -} + MILLION: 'MILLION', +} as const; + +/** + * Fraction percentages support several fixed denominator values. + */ +export type _envoy_type_v3_FractionalPercent_DenominatorType = + /** + * 100. + * + * **Example**: 1/100 = 1%. + */ + | 'HUNDRED' + | 0 + /** + * 10,000. + * + * **Example**: 1/10000 = 0.01%. + */ + | 'TEN_THOUSAND' + | 1 + /** + * 1,000,000. + * + * **Example**: 1/1000000 = 0.0001%. + */ + | 'MILLION' + | 2 + +/** + * Fraction percentages support several fixed denominator values. + */ +export type _envoy_type_v3_FractionalPercent_DenominatorType__Output = typeof _envoy_type_v3_FractionalPercent_DenominatorType[keyof typeof _envoy_type_v3_FractionalPercent_DenominatorType] /** * A fractional percentage is used in cases in which for performance reasons performing floating @@ -44,7 +75,7 @@ export interface FractionalPercent { * Specifies the denominator. If the denominator specified is less than the numerator, the final * fractional percentage is capped at 1 (100%). */ - 'denominator'?: (_envoy_type_v3_FractionalPercent_DenominatorType | keyof typeof _envoy_type_v3_FractionalPercent_DenominatorType); + 'denominator'?: (_envoy_type_v3_FractionalPercent_DenominatorType); } /** @@ -64,5 +95,5 @@ export interface FractionalPercent__Output { * Specifies the denominator. If the denominator specified is less than the numerator, the final * fractional percentage is capped at 1 (100%). */ - 'denominator': (keyof typeof _envoy_type_v3_FractionalPercent_DenominatorType); + 'denominator': (_envoy_type_v3_FractionalPercent_DenominatorType__Output); } diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts b/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts index c511e2eff..4951919fd 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldDescriptorProto.ts @@ -4,41 +4,91 @@ import type { FieldOptions as _google_protobuf_FieldOptions, FieldOptions__Outpu // Original file: null -export enum _google_protobuf_FieldDescriptorProto_Label { - LABEL_OPTIONAL = 1, - LABEL_REQUIRED = 2, - LABEL_REPEATED = 3, -} +export const _google_protobuf_FieldDescriptorProto_Label = { + LABEL_OPTIONAL: 'LABEL_OPTIONAL', + LABEL_REQUIRED: 'LABEL_REQUIRED', + LABEL_REPEATED: 'LABEL_REPEATED', +} as const; + +export type _google_protobuf_FieldDescriptorProto_Label = + | 'LABEL_OPTIONAL' + | 1 + | 'LABEL_REQUIRED' + | 2 + | 'LABEL_REPEATED' + | 3 + +export type _google_protobuf_FieldDescriptorProto_Label__Output = typeof _google_protobuf_FieldDescriptorProto_Label[keyof typeof _google_protobuf_FieldDescriptorProto_Label] // Original file: null -export enum _google_protobuf_FieldDescriptorProto_Type { - TYPE_DOUBLE = 1, - TYPE_FLOAT = 2, - TYPE_INT64 = 3, - TYPE_UINT64 = 4, - TYPE_INT32 = 5, - TYPE_FIXED64 = 6, - TYPE_FIXED32 = 7, - TYPE_BOOL = 8, - TYPE_STRING = 9, - TYPE_GROUP = 10, - TYPE_MESSAGE = 11, - TYPE_BYTES = 12, - TYPE_UINT32 = 13, - TYPE_ENUM = 14, - TYPE_SFIXED32 = 15, - TYPE_SFIXED64 = 16, - TYPE_SINT32 = 17, - TYPE_SINT64 = 18, -} +export const _google_protobuf_FieldDescriptorProto_Type = { + TYPE_DOUBLE: 'TYPE_DOUBLE', + TYPE_FLOAT: 'TYPE_FLOAT', + TYPE_INT64: 'TYPE_INT64', + TYPE_UINT64: 'TYPE_UINT64', + TYPE_INT32: 'TYPE_INT32', + TYPE_FIXED64: 'TYPE_FIXED64', + TYPE_FIXED32: 'TYPE_FIXED32', + TYPE_BOOL: 'TYPE_BOOL', + TYPE_STRING: 'TYPE_STRING', + TYPE_GROUP: 'TYPE_GROUP', + TYPE_MESSAGE: 'TYPE_MESSAGE', + TYPE_BYTES: 'TYPE_BYTES', + TYPE_UINT32: 'TYPE_UINT32', + TYPE_ENUM: 'TYPE_ENUM', + TYPE_SFIXED32: 'TYPE_SFIXED32', + TYPE_SFIXED64: 'TYPE_SFIXED64', + TYPE_SINT32: 'TYPE_SINT32', + TYPE_SINT64: 'TYPE_SINT64', +} as const; + +export type _google_protobuf_FieldDescriptorProto_Type = + | 'TYPE_DOUBLE' + | 1 + | 'TYPE_FLOAT' + | 2 + | 'TYPE_INT64' + | 3 + | 'TYPE_UINT64' + | 4 + | 'TYPE_INT32' + | 5 + | 'TYPE_FIXED64' + | 6 + | 'TYPE_FIXED32' + | 7 + | 'TYPE_BOOL' + | 8 + | 'TYPE_STRING' + | 9 + | 'TYPE_GROUP' + | 10 + | 'TYPE_MESSAGE' + | 11 + | 'TYPE_BYTES' + | 12 + | 'TYPE_UINT32' + | 13 + | 'TYPE_ENUM' + | 14 + | 'TYPE_SFIXED32' + | 15 + | 'TYPE_SFIXED64' + | 16 + | 'TYPE_SINT32' + | 17 + | 'TYPE_SINT64' + | 18 + +export type _google_protobuf_FieldDescriptorProto_Type__Output = typeof _google_protobuf_FieldDescriptorProto_Type[keyof typeof _google_protobuf_FieldDescriptorProto_Type] export interface FieldDescriptorProto { 'name'?: (string); 'extendee'?: (string); 'number'?: (number); - 'label'?: (_google_protobuf_FieldDescriptorProto_Label | keyof typeof _google_protobuf_FieldDescriptorProto_Label); - 'type'?: (_google_protobuf_FieldDescriptorProto_Type | keyof typeof _google_protobuf_FieldDescriptorProto_Type); + 'label'?: (_google_protobuf_FieldDescriptorProto_Label); + 'type'?: (_google_protobuf_FieldDescriptorProto_Type); 'typeName'?: (string); 'defaultValue'?: (string); 'options'?: (_google_protobuf_FieldOptions | null); @@ -50,8 +100,8 @@ export interface FieldDescriptorProto__Output { 'name': (string); 'extendee': (string); 'number': (number); - 'label': (keyof typeof _google_protobuf_FieldDescriptorProto_Label); - 'type': (keyof typeof _google_protobuf_FieldDescriptorProto_Type); + 'label': (_google_protobuf_FieldDescriptorProto_Label__Output); + 'type': (_google_protobuf_FieldDescriptorProto_Type__Output); 'typeName': (string); 'defaultValue': (string); 'options': (_google_protobuf_FieldOptions__Output | null); 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 3c3b446c9..b301f2958 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FieldOptions.ts @@ -4,36 +4,56 @@ import type { UninterpretedOption as _google_protobuf_UninterpretedOption, Unint // Original file: null -export enum _google_protobuf_FieldOptions_CType { - STRING = 0, - CORD = 1, - STRING_PIECE = 2, -} +export const _google_protobuf_FieldOptions_CType = { + STRING: 'STRING', + CORD: 'CORD', + STRING_PIECE: 'STRING_PIECE', +} as const; + +export type _google_protobuf_FieldOptions_CType = + | 'STRING' + | 0 + | 'CORD' + | 1 + | 'STRING_PIECE' + | 2 + +export type _google_protobuf_FieldOptions_CType__Output = typeof _google_protobuf_FieldOptions_CType[keyof typeof _google_protobuf_FieldOptions_CType] // Original file: null -export enum _google_protobuf_FieldOptions_JSType { - JS_NORMAL = 0, - JS_STRING = 1, - JS_NUMBER = 2, -} +export const _google_protobuf_FieldOptions_JSType = { + JS_NORMAL: 'JS_NORMAL', + JS_STRING: 'JS_STRING', + JS_NUMBER: 'JS_NUMBER', +} as const; + +export type _google_protobuf_FieldOptions_JSType = + | 'JS_NORMAL' + | 0 + | 'JS_STRING' + | 1 + | 'JS_NUMBER' + | 2 + +export type _google_protobuf_FieldOptions_JSType__Output = typeof _google_protobuf_FieldOptions_JSType[keyof typeof _google_protobuf_FieldOptions_JSType] export interface FieldOptions { - 'ctype'?: (_google_protobuf_FieldOptions_CType | keyof typeof _google_protobuf_FieldOptions_CType); + 'ctype'?: (_google_protobuf_FieldOptions_CType); 'packed'?: (boolean); 'deprecated'?: (boolean); 'lazy'?: (boolean); - 'jstype'?: (_google_protobuf_FieldOptions_JSType | keyof typeof _google_protobuf_FieldOptions_JSType); + 'jstype'?: (_google_protobuf_FieldOptions_JSType); 'weak'?: (boolean); 'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[]; } export interface FieldOptions__Output { - 'ctype': (keyof typeof _google_protobuf_FieldOptions_CType); + 'ctype': (_google_protobuf_FieldOptions_CType__Output); 'packed': (boolean); 'deprecated': (boolean); 'lazy': (boolean); - 'jstype': (keyof typeof _google_protobuf_FieldOptions_JSType); + 'jstype': (_google_protobuf_FieldOptions_JSType__Output); 'weak': (boolean); 'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[]; } 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 84500fc30..6fab1a84b 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/FileOptions.ts @@ -5,21 +5,34 @@ import type { StatusAnnotation as _udpa_annotations_StatusAnnotation, StatusAnno // Original file: null -export enum _google_protobuf_FileOptions_OptimizeMode { - SPEED = 1, - CODE_SIZE = 2, - LITE_RUNTIME = 3, -} +export const _google_protobuf_FileOptions_OptimizeMode = { + SPEED: 'SPEED', + CODE_SIZE: 'CODE_SIZE', + LITE_RUNTIME: 'LITE_RUNTIME', +} as const; + +export type _google_protobuf_FileOptions_OptimizeMode = + | 'SPEED' + | 1 + | 'CODE_SIZE' + | 2 + | 'LITE_RUNTIME' + | 3 + +export type _google_protobuf_FileOptions_OptimizeMode__Output = typeof _google_protobuf_FileOptions_OptimizeMode[keyof typeof _google_protobuf_FileOptions_OptimizeMode] export interface FileOptions { 'javaPackage'?: (string); 'javaOuterClassname'?: (string); - 'optimizeFor'?: (_google_protobuf_FileOptions_OptimizeMode | keyof typeof _google_protobuf_FileOptions_OptimizeMode); + 'optimizeFor'?: (_google_protobuf_FileOptions_OptimizeMode); 'javaMultipleFiles'?: (boolean); 'goPackage'?: (string); 'ccGenericServices'?: (boolean); 'javaGenericServices'?: (boolean); 'pyGenericServices'?: (boolean); + /** + * @deprecated + */ 'javaGenerateEqualsAndHash'?: (boolean); 'deprecated'?: (boolean); 'javaStringCheckUtf8'?: (boolean); @@ -33,12 +46,15 @@ export interface FileOptions { export interface FileOptions__Output { 'javaPackage': (string); 'javaOuterClassname': (string); - 'optimizeFor': (keyof typeof _google_protobuf_FileOptions_OptimizeMode); + 'optimizeFor': (_google_protobuf_FileOptions_OptimizeMode__Output); 'javaMultipleFiles': (boolean); 'goPackage': (string); 'ccGenericServices': (boolean); 'javaGenericServices': (boolean); 'pyGenericServices': (boolean); + /** + * @deprecated + */ 'javaGenerateEqualsAndHash': (boolean); 'deprecated': (boolean); 'javaStringCheckUtf8': (boolean); diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/NullValue.ts b/packages/grpc-js-xds/src/generated/google/protobuf/NullValue.ts index 377aab885..c66dacc7b 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/NullValue.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/NullValue.ts @@ -1,5 +1,11 @@ // Original file: null -export enum NullValue { - NULL_VALUE = 0, -} +export const NullValue = { + NULL_VALUE: 'NULL_VALUE', +} as const; + +export type NullValue = + | 'NULL_VALUE' + | 0 + +export type NullValue__Output = typeof NullValue[keyof typeof NullValue] diff --git a/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts index b1a942a56..67cc03fff 100644 --- a/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts +++ b/packages/grpc-js-xds/src/generated/google/protobuf/Value.ts @@ -1,11 +1,11 @@ // Original file: null -import type { NullValue as _google_protobuf_NullValue } from '../../google/protobuf/NullValue'; +import type { NullValue as _google_protobuf_NullValue, NullValue__Output as _google_protobuf_NullValue__Output } from '../../google/protobuf/NullValue'; import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../google/protobuf/Struct'; import type { ListValue as _google_protobuf_ListValue, ListValue__Output as _google_protobuf_ListValue__Output } from '../../google/protobuf/ListValue'; export interface Value { - 'nullValue'?: (_google_protobuf_NullValue | keyof typeof _google_protobuf_NullValue); + 'nullValue'?: (_google_protobuf_NullValue); 'numberValue'?: (number | string); 'stringValue'?: (string); 'boolValue'?: (boolean); @@ -15,7 +15,7 @@ export interface Value { } export interface Value__Output { - 'nullValue'?: (keyof typeof _google_protobuf_NullValue); + 'nullValue'?: (_google_protobuf_NullValue__Output); 'numberValue'?: (number); 'stringValue'?: (string); 'boolValue'?: (boolean); diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts index d0e181aa5..4d15df739 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/PackageVersionStatus.ts @@ -1,21 +1,46 @@ // Original file: deps/xds/udpa/annotations/status.proto -export enum PackageVersionStatus { +export const PackageVersionStatus = { /** * Unknown package version status. */ - UNKNOWN = 0, + UNKNOWN: 'UNKNOWN', /** * This version of the package is frozen. */ - FROZEN = 1, + FROZEN: 'FROZEN', /** * This version of the package is the active development version. */ - ACTIVE = 2, + ACTIVE: 'ACTIVE', /** * This version of the package is the candidate for the next major version. It * is typically machine generated from the active development version. */ - NEXT_MAJOR_VERSION_CANDIDATE = 3, -} + NEXT_MAJOR_VERSION_CANDIDATE: 'NEXT_MAJOR_VERSION_CANDIDATE', +} as const; + +export type PackageVersionStatus = + /** + * Unknown package version status. + */ + | 'UNKNOWN' + | 0 + /** + * This version of the package is frozen. + */ + | 'FROZEN' + | 1 + /** + * This version of the package is the active development version. + */ + | 'ACTIVE' + | 2 + /** + * This version of the package is the candidate for the next major version. It + * is typically machine generated from the active development version. + */ + | 'NEXT_MAJOR_VERSION_CANDIDATE' + | 3 + +export type PackageVersionStatus__Output = typeof PackageVersionStatus[keyof typeof PackageVersionStatus] diff --git a/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts b/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts index f01b45063..f129c3c94 100644 --- a/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/udpa/annotations/StatusAnnotation.ts @@ -1,6 +1,6 @@ // Original file: deps/xds/udpa/annotations/status.proto -import type { PackageVersionStatus as _udpa_annotations_PackageVersionStatus } from '../../udpa/annotations/PackageVersionStatus'; +import type { PackageVersionStatus as _udpa_annotations_PackageVersionStatus, PackageVersionStatus__Output as _udpa_annotations_PackageVersionStatus__Output } from '../../udpa/annotations/PackageVersionStatus'; export interface StatusAnnotation { /** @@ -10,7 +10,7 @@ export interface StatusAnnotation { /** * The entity belongs to a package with the given version status. */ - 'package_version_status'?: (_udpa_annotations_PackageVersionStatus | keyof typeof _udpa_annotations_PackageVersionStatus); + 'package_version_status'?: (_udpa_annotations_PackageVersionStatus); } export interface StatusAnnotation__Output { @@ -21,5 +21,5 @@ export interface StatusAnnotation__Output { /** * The entity belongs to a package with the given version status. */ - 'package_version_status': (keyof typeof _udpa_annotations_PackageVersionStatus); + 'package_version_status': (_udpa_annotations_PackageVersionStatus__Output); } diff --git a/packages/grpc-js-xds/src/generated/validate/FieldRules.ts b/packages/grpc-js-xds/src/generated/validate/FieldRules.ts index 067125775..ce6f313e7 100644 --- a/packages/grpc-js-xds/src/generated/validate/FieldRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/FieldRules.ts @@ -22,7 +22,6 @@ import type { MapRules as _validate_MapRules, MapRules__Output as _validate_MapR import type { AnyRules as _validate_AnyRules, AnyRules__Output as _validate_AnyRules__Output } from '../validate/AnyRules'; import type { DurationRules as _validate_DurationRules, DurationRules__Output as _validate_DurationRules__Output } from '../validate/DurationRules'; import type { TimestampRules as _validate_TimestampRules, TimestampRules__Output as _validate_TimestampRules__Output } from '../validate/TimestampRules'; -import type { Long } from '@grpc/proto-loader'; /** * FieldRules encapsulates the rules for each type of field. Depending on the diff --git a/packages/grpc-js-xds/src/generated/validate/KnownRegex.ts b/packages/grpc-js-xds/src/generated/validate/KnownRegex.ts index 5880b5baf..8f1e20b4c 100644 --- a/packages/grpc-js-xds/src/generated/validate/KnownRegex.ts +++ b/packages/grpc-js-xds/src/generated/validate/KnownRegex.ts @@ -3,14 +3,36 @@ /** * WellKnownRegex contain some well-known patterns. */ -export enum KnownRegex { - UNKNOWN = 0, +export const KnownRegex = { + UNKNOWN: 'UNKNOWN', /** * HTTP header name as defined by RFC 7230. */ - HTTP_HEADER_NAME = 1, + HTTP_HEADER_NAME: 'HTTP_HEADER_NAME', /** * HTTP header value as defined by RFC 7230. */ - HTTP_HEADER_VALUE = 2, -} + HTTP_HEADER_VALUE: 'HTTP_HEADER_VALUE', +} as const; + +/** + * WellKnownRegex contain some well-known patterns. + */ +export type KnownRegex = + | 'UNKNOWN' + | 0 + /** + * HTTP header name as defined by RFC 7230. + */ + | 'HTTP_HEADER_NAME' + | 1 + /** + * HTTP header value as defined by RFC 7230. + */ + | 'HTTP_HEADER_VALUE' + | 2 + +/** + * WellKnownRegex contain some well-known patterns. + */ +export type KnownRegex__Output = typeof KnownRegex[keyof typeof KnownRegex] diff --git a/packages/grpc-js-xds/src/generated/validate/StringRules.ts b/packages/grpc-js-xds/src/generated/validate/StringRules.ts index b6bb1e460..8bca6dffa 100644 --- a/packages/grpc-js-xds/src/generated/validate/StringRules.ts +++ b/packages/grpc-js-xds/src/generated/validate/StringRules.ts @@ -1,6 +1,6 @@ // Original file: deps/protoc-gen-validate/validate/validate.proto -import type { KnownRegex as _validate_KnownRegex } from '../validate/KnownRegex'; +import type { KnownRegex as _validate_KnownRegex, KnownRegex__Output as _validate_KnownRegex__Output } from '../validate/KnownRegex'; import type { Long } from '@grpc/proto-loader'; /** @@ -129,7 +129,7 @@ export interface StringRules { /** * WellKnownRegex specifies a common well known pattern defined as a regex. */ - 'well_known_regex'?: (_validate_KnownRegex | keyof typeof _validate_KnownRegex); + 'well_known_regex'?: (_validate_KnownRegex); /** * This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable * strict header validation. @@ -271,7 +271,7 @@ export interface StringRules__Output { /** * WellKnownRegex specifies a common well known pattern defined as a regex. */ - 'well_known_regex'?: (keyof typeof _validate_KnownRegex); + 'well_known_regex'?: (_validate_KnownRegex__Output); /** * This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable * strict header validation. diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts index e76e2848f..e85074eae 100644 --- a/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/PackageVersionStatus.ts @@ -1,21 +1,46 @@ // Original file: deps/xds/xds/annotations/v3/status.proto -export enum PackageVersionStatus { +export const PackageVersionStatus = { /** * Unknown package version status. */ - UNKNOWN = 0, + UNKNOWN: 'UNKNOWN', /** * This version of the package is frozen. */ - FROZEN = 1, + FROZEN: 'FROZEN', /** * This version of the package is the active development version. */ - ACTIVE = 2, + ACTIVE: 'ACTIVE', /** * This version of the package is the candidate for the next major version. It * is typically machine generated from the active development version. */ - NEXT_MAJOR_VERSION_CANDIDATE = 3, -} + NEXT_MAJOR_VERSION_CANDIDATE: 'NEXT_MAJOR_VERSION_CANDIDATE', +} as const; + +export type PackageVersionStatus = + /** + * Unknown package version status. + */ + | 'UNKNOWN' + | 0 + /** + * This version of the package is frozen. + */ + | 'FROZEN' + | 1 + /** + * This version of the package is the active development version. + */ + | 'ACTIVE' + | 2 + /** + * This version of the package is the candidate for the next major version. It + * is typically machine generated from the active development version. + */ + | 'NEXT_MAJOR_VERSION_CANDIDATE' + | 3 + +export type PackageVersionStatus__Output = typeof PackageVersionStatus[keyof typeof PackageVersionStatus] diff --git a/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts b/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts index 58efbd8f7..678d6a6bf 100644 --- a/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts +++ b/packages/grpc-js-xds/src/generated/xds/annotations/v3/StatusAnnotation.ts @@ -1,6 +1,6 @@ // Original file: deps/xds/xds/annotations/v3/status.proto -import type { PackageVersionStatus as _xds_annotations_v3_PackageVersionStatus } from '../../../xds/annotations/v3/PackageVersionStatus'; +import type { PackageVersionStatus as _xds_annotations_v3_PackageVersionStatus, PackageVersionStatus__Output as _xds_annotations_v3_PackageVersionStatus__Output } from '../../../xds/annotations/v3/PackageVersionStatus'; export interface StatusAnnotation { /** @@ -10,7 +10,7 @@ export interface StatusAnnotation { /** * The entity belongs to a package with the given version status. */ - 'package_version_status'?: (_xds_annotations_v3_PackageVersionStatus | keyof typeof _xds_annotations_v3_PackageVersionStatus); + 'package_version_status'?: (_xds_annotations_v3_PackageVersionStatus); } export interface StatusAnnotation__Output { @@ -21,5 +21,5 @@ export interface StatusAnnotation__Output { /** * The entity belongs to a package with the given version status. */ - 'package_version_status': (keyof typeof _xds_annotations_v3_PackageVersionStatus); + 'package_version_status': (_xds_annotations_v3_PackageVersionStatus__Output); } diff --git a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts index bb1f822b8..28f981dd5 100644 --- a/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts +++ b/packages/grpc-js-xds/src/generated/xds/core/v3/ResourceLocator.ts @@ -97,11 +97,21 @@ export interface _xds_core_v3_ResourceLocator_Directive__Output { // Original file: deps/xds/xds/core/v3/resource_locator.proto -export enum _xds_core_v3_ResourceLocator_Scheme { - XDSTP = 0, - HTTP = 1, - FILE = 2, -} +export const _xds_core_v3_ResourceLocator_Scheme = { + XDSTP: 'XDSTP', + HTTP: 'HTTP', + FILE: 'FILE', +} as const; + +export type _xds_core_v3_ResourceLocator_Scheme = + | 'XDSTP' + | 0 + | 'HTTP' + | 1 + | 'FILE' + | 2 + +export type _xds_core_v3_ResourceLocator_Scheme__Output = typeof _xds_core_v3_ResourceLocator_Scheme[keyof typeof _xds_core_v3_ResourceLocator_Scheme] /** * xDS resource locators identify a xDS resource name and instruct the @@ -125,7 +135,7 @@ export interface ResourceLocator { /** * URI scheme. */ - 'scheme'?: (_xds_core_v3_ResourceLocator_Scheme | keyof typeof _xds_core_v3_ResourceLocator_Scheme); + 'scheme'?: (_xds_core_v3_ResourceLocator_Scheme); /** * Opaque identifier for the resource. Any '/' will not be escaped during URI * encoding and will form part of the URI path. This may end @@ -183,7 +193,7 @@ export interface ResourceLocator__Output { /** * URI scheme. */ - 'scheme': (keyof typeof _xds_core_v3_ResourceLocator_Scheme); + 'scheme': (_xds_core_v3_ResourceLocator_Scheme__Output); /** * Opaque identifier for the resource. Any '/' will not be escaped during URI * encoding and will form part of the URI path. This may end diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index 3b8ccf2c1..41594561d 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -66,7 +66,7 @@ "generate-test-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --include-dirs test/fixtures/ -O test/generated/ --grpcLib ../../src/index test_service.proto" }, "dependencies": { - "@grpc/proto-loader": "^0.7.10", + "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" }, "files": [ From d5edf49f6c59fd8f6e9ead2836bc30af8284284e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 3 May 2024 14:24:44 -0700 Subject: [PATCH 690/694] Merge pull request #2735 from murgatroid99/grpc-js_linkify-it_fix root: Update dependency on jsdoc to avoid linkify-it compilation error --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index a1fd3d59e..a5733f377 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,13 @@ "del": "^3.0.0", "execa": "^0.8.0", "gulp": "^4.0.1", - "gulp-jsdoc3": "^1.0.1", "gulp-jshint": "^2.0.4", "gulp-mocha": "^4.3.1", "gulp-sourcemaps": "^2.6.1", "gulp-tslint": "^8.1.1", "gulp-typescript": "^3.2.2", "gulp-util": "^3.0.8", - "jsdoc": "^3.3.2", + "jsdoc": "^4.0.3", "jshint": "^2.9.5", "make-dir": "^1.1.0", "merge2": "^1.1.0", From fec135a9800ce884b8dd414782f4bd0014821a0c Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Mon, 6 May 2024 15:20:11 -0700 Subject: [PATCH 691/694] Merge pull request #2729 from sergiitk/psm-interop-common-prod-tests PSM Interop: simplify Kokoro buildscripts --- .../scripts/psm-interop-build-node.sh | 38 ++++ .../scripts/psm-interop-test-node.sh | 40 ++++ packages/grpc-js-xds/scripts/xds_k8s_lb.sh | 186 ------------------ .../grpc-js-xds/scripts/xds_k8s_url_map.sh | 163 --------------- test/kokoro/xds-interop.cfg | 24 --- test/kokoro/xds_k8s_lb.cfg | 6 +- test/kokoro/xds_k8s_url_map.cfg | 6 +- 7 files changed, 88 insertions(+), 375 deletions(-) create mode 100755 packages/grpc-js-xds/scripts/psm-interop-build-node.sh create mode 100755 packages/grpc-js-xds/scripts/psm-interop-test-node.sh delete mode 100755 packages/grpc-js-xds/scripts/xds_k8s_lb.sh delete mode 100644 packages/grpc-js-xds/scripts/xds_k8s_url_map.sh delete mode 100644 test/kokoro/xds-interop.cfg diff --git a/packages/grpc-js-xds/scripts/psm-interop-build-node.sh b/packages/grpc-js-xds/scripts/psm-interop-build-node.sh new file mode 100755 index 000000000..d52206f0e --- /dev/null +++ b/packages/grpc-js-xds/scripts/psm-interop-build-node.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Copyright 2024 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. +set -eo pipefail + +####################################### +# Builds test app Docker images and pushes them to GCR. +# Called from psm_interop_kokoro_lib.sh. +# +# Globals: +# SRC_DIR: Absolute path to the source repo on Kokoro VM +# SERVER_IMAGE_NAME: Test server Docker image name +# CLIENT_IMAGE_NAME: Test client Docker image name +# GIT_COMMIT: SHA-1 of git commit being built +# DOCKER_REGISTRY: Docker registry to push to +# Outputs: +# Writes the output of docker image build stdout, stderr +####################################### +psm::lang::build_docker_images() { + local client_dockerfile="packages/grpc-js-xds/interop/Dockerfile" + + cd "${SRC_DIR}" + psm::tools::run_verbose git submodule update --init --recursive + psm::tools::run_verbose git submodule status + + psm::build::docker_images_generic "${client_dockerfile}" +} diff --git a/packages/grpc-js-xds/scripts/psm-interop-test-node.sh b/packages/grpc-js-xds/scripts/psm-interop-test-node.sh new file mode 100755 index 000000000..169cf06f2 --- /dev/null +++ b/packages/grpc-js-xds/scripts/psm-interop-test-node.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Copyright 2024 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. +set -eo pipefail + +# Input parameters to psm:: methods of the install script. +readonly GRPC_LANGUAGE="node" +readonly BUILD_SCRIPT_DIR="$(dirname "$0")" + +# Used locally. +readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" + +psm::lang::source_install_lib() { + echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" + local install_lib + # Download to a tmp file. + install_lib="$(mktemp -d)/psm_interop_kokoro_lib.sh" + curl -s --retry-connrefused --retry 5 -o "${install_lib}" "${TEST_DRIVER_INSTALL_SCRIPT_URL}" + # Checksum. + if command -v sha256sum &> /dev/null; then + echo "Install script checksum:" + sha256sum "${install_lib}" + fi + source "${install_lib}" +} + +psm::lang::source_install_lib +source "${BUILD_SCRIPT_DIR}/psm-interop-build-${GRPC_LANGUAGE}.sh" +psm::run "${PSM_TEST_SUITE}" diff --git a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh b/packages/grpc-js-xds/scripts/xds_k8s_lb.sh deleted file mode 100755 index c900a4ea5..000000000 --- a/packages/grpc-js-xds/scripts/xds_k8s_lb.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2022 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. - -set -eo pipefail - -# Constants -readonly GITHUB_REPOSITORY_NAME="grpc-node" -readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" -## xDS test client Docker images -readonly DOCKER_REGISTRY="us-docker.pkg.dev" -readonly SERVER_IMAGE_NAME="us-docker.pkg.dev/grpc-testing/psm-interop/java-server:canonical" -readonly CLIENT_IMAGE_NAME="us-docker.pkg.dev/grpc-testing/psm-interop/node-client" -readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" -readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" -readonly LANGUAGE_NAME="Node" - -####################################### -# Builds test app Docker images and pushes them to GCR -# Globals: -# BUILD_APP_PATH -# CLIENT_IMAGE_NAME: Test client Docker image name -# GIT_COMMIT: SHA-1 of git commit being built -# TESTING_VERSION: version branch under test, f.e. v1.42.x, master -# Arguments: -# None -# Outputs: -# Writes the output of `gcloud builds submit` to stdout, stderr -####################################### -build_test_app_docker_images() { - echo "Building ${LANGUAGE_NAME} xDS interop test app Docker images" - - pushd "${SRC_DIR}" - docker build \ - -f "${BUILD_APP_PATH}" \ - -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ - . - - gcloud -q auth configure-docker "${DOCKER_REGISTRY}" - docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" - if is_version_branch "${TESTING_VERSION}"; then - tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" - fi - popd -} - -####################################### -# Builds test app and its docker images unless they already exist -# Globals: -# CLIENT_IMAGE_NAME: Test client Docker image name -# GIT_COMMIT: SHA-1 of git commit being built -# FORCE_IMAGE_BUILD -# Arguments: -# None -# Outputs: -# Writes the output to stdout, stderr -####################################### -build_docker_images_if_needed() { - # Check if images already exist - client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")" - printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" - echo "${client_tags:-Client image not found}" - - # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1 - if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${client_tags}" ]]; then - build_test_app_docker_images - else - echo "Skipping ${LANGUAGE_NAME} test app build" - fi -} - -####################################### -# Executes the test case -# Globals: -# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile -# KUBE_CONTEXT: The name of kubectl context with GKE cluster access -# SECONDARY_KUBE_CONTEXT: The name of kubectl context with secondary GKE cluster access, if any -# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report -# CLIENT_IMAGE_NAME: Test client Docker image name -# GIT_COMMIT: SHA-1 of git commit being built -# Arguments: -# Test case name -# Outputs: -# Writes the output of test execution to stdout, stderr -# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml -####################################### -run_test() { - # Test driver usage: - # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage - local test_name="${1:?Usage: run_test test_name}" - local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}" - mkdir -pv "${out_dir}" - # testing_version is used by the framework to determine the supported PSM - # features. It's captured from Kokoro job name of the Node repo, which takes - # the form: - # grpc/node// - python3 -m "tests.${test_name}" \ - --flagfile="${TEST_DRIVER_FLAGFILE}" \ - --kube_context="${KUBE_CONTEXT}" \ - --secondary_kube_context="${SECONDARY_KUBE_CONTEXT}" \ - --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ - --server_image="${SERVER_IMAGE_NAME}" \ - --testing_version="${TESTING_VERSION}" \ - --force_cleanup \ - --collect_app_logs \ - --log_dir="${out_dir}" \ - --xml_output_file="${out_dir}/sponge_log.xml" \ - |& tee "${out_dir}/sponge_log.log" -} - -####################################### -# Main function: provision software necessary to execute tests, and run them -# Globals: -# KOKORO_ARTIFACTS_DIR -# GITHUB_REPOSITORY_NAME -# SRC_DIR: Populated with absolute path to the source repo -# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing -# the test driver -# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code -# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile -# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report -# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build -# GIT_COMMIT: Populated with the SHA-1 of git commit being built -# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built -# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access -# SECONDARY_KUBE_CONTEXT: Populated with name of kubectl context with secondary GKE cluster access, if any -# Arguments: -# None -# Outputs: -# Writes the output of test execution to stdout, stderr -####################################### -main() { - local script_dir - script_dir="$(dirname "$0")" - - cd "${script_dir}" - - git submodule update --init --recursive - - # Source the test driver from the master branch. - echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" - source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" - - activate_gke_cluster GKE_CLUSTER_PSM_LB - activate_secondary_gke_cluster GKE_CLUSTER_PSM_LB - - set -x - if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then - kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}" - else - local_setup_test_driver "${script_dir}" - fi - build_docker_images_if_needed - - # Run tests - cd "${TEST_DRIVER_FULL_DIR}" - local failed_tests=0 - test_suites=( - "affinity_test" - "api_listener_test" - "baseline_test" - "change_backend_service_test" - "custom_lb_test" - "failover_test" - "outlier_detection_test" - "remove_neg_test" - "round_robin_test" - ) - for test in "${test_suites[@]}"; do - run_test $test || (( ++failed_tests )) - done - echo "Failed test suites: ${failed_tests}" -} - -main "$@" diff --git a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh b/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh deleted file mode 100644 index d6e2c7ed4..000000000 --- a/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2022 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. - -set -eo pipefail - -# Constants -readonly GITHUB_REPOSITORY_NAME="grpc-node" -readonly TEST_DRIVER_INSTALL_SCRIPT_URL="https://raw.githubusercontent.com/${TEST_DRIVER_REPO_OWNER:-grpc}/psm-interop/${TEST_DRIVER_BRANCH:-main}/.kokoro/psm_interop_kokoro_lib.sh" -## xDS test client Docker images -readonly DOCKER_REGISTRY="us-docker.pkg.dev" -readonly CLIENT_IMAGE_NAME="us-docker.pkg.dev/grpc-testing/psm-interop/node-client" -readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}" -readonly BUILD_APP_PATH="packages/grpc-js-xds/interop/Dockerfile" -readonly LANGUAGE_NAME="Node" - -####################################### -# Builds test app Docker images and pushes them to GCR -# Globals: -# BUILD_APP_PATH -# CLIENT_IMAGE_NAME: Test client Docker image name -# GIT_COMMIT: SHA-1 of git commit being built -# TESTING_VERSION: version branch under test, f.e. v1.42.x, master -# Arguments: -# None -# Outputs: -# Writes the output of `gcloud builds submit` to stdout, stderr -####################################### -build_test_app_docker_images() { - echo "Building ${LANGUAGE_NAME} xDS interop test app Docker images" - - pushd "${SRC_DIR}" - docker build \ - -f "${BUILD_APP_PATH}" \ - -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ - . - - gcloud -q auth configure-docker "${DOCKER_REGISTRY}" - docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" - if is_version_branch "${TESTING_VERSION}"; then - tag_and_push_docker_image "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" "${TESTING_VERSION}" - fi - popd -} - -####################################### -# Builds test app and its docker images unless they already exist -# Globals: -# CLIENT_IMAGE_NAME: Test client Docker image name -# GIT_COMMIT: SHA-1 of git commit being built -# FORCE_IMAGE_BUILD -# Arguments: -# None -# Outputs: -# Writes the output to stdout, stderr -####################################### -build_docker_images_if_needed() { - # Check if images already exist - client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")" - printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}" - echo "${client_tags:-Client image not found}" - - # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1 - if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${client_tags}" ]]; then - build_test_app_docker_images - else - echo "Skipping ${LANGUAGE_NAME} test app build" - fi -} - -####################################### -# Executes the test case -# Globals: -# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile -# KUBE_CONTEXT: The name of kubectl context with GKE cluster access -# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report -# CLIENT_IMAGE_NAME: Test client Docker image name -# GIT_COMMIT: SHA-1 of git commit being built -# TESTING_VERSION: version branch under test: used by the framework to determine the supported PSM -# features. -# Arguments: -# Test case name -# Outputs: -# Writes the output of test execution to stdout, stderr -# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml -####################################### -run_test() { - # Test driver usage: - # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage - local test_name="${1:?Usage: run_test test_name}" - local out_dir="${TEST_XML_OUTPUT_DIR}/${test_name}" - mkdir -pv "${out_dir}" - set -x - python3 -m "tests.${test_name}" \ - --flagfile="${TEST_DRIVER_FLAGFILE}" \ - --flagfile="config/url-map.cfg" \ - --kube_context="${KUBE_CONTEXT}" \ - --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \ - --testing_version="${TESTING_VERSION}" \ - --collect_app_logs \ - --log_dir="${out_dir}" \ - --xml_output_file="${out_dir}/sponge_log.xml" \ - |& tee "${out_dir}/sponge_log.log" -} - -####################################### -# Main function: provision software necessary to execute tests, and run them -# Globals: -# KOKORO_ARTIFACTS_DIR -# GITHUB_REPOSITORY_NAME -# SRC_DIR: Populated with absolute path to the source repo -# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing -# the test driver -# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code -# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile -# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report -# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build -# GIT_COMMIT: Populated with the SHA-1 of git commit being built -# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built -# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access -# Arguments: -# None -# Outputs: -# Writes the output of test execution to stdout, stderr -####################################### -main() { - local script_dir - script_dir="$(dirname "$0")" - - cd "${script_dir}" - - git submodule update --init --recursive - - # Source the test driver from the master branch. - echo "Sourcing test driver install script from: ${TEST_DRIVER_INSTALL_SCRIPT_URL}" - source /dev/stdin <<< "$(curl -s "${TEST_DRIVER_INSTALL_SCRIPT_URL}")" - - activate_gke_cluster GKE_CLUSTER_PSM_BASIC - - set -x - if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then - kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}" - else - local_setup_test_driver "${script_dir}" - fi - build_docker_images_if_needed - # Run tests - cd "${TEST_DRIVER_FULL_DIR}" - run_test url_map || echo "Failed url_map test" -} - -main "$@" diff --git a/test/kokoro/xds-interop.cfg b/test/kokoro/xds-interop.cfg deleted file mode 100644 index 866cb4b58..000000000 --- a/test/kokoro/xds-interop.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2017 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. - -# Config file for Kokoro (in protobuf text format) - -# Location of the continuous shell script in repository. -build_file: "grpc-node/packages/grpc-js-xds/scripts/xds.sh" -timeout_mins: 360 -action { - define_artifacts { - regex: "github/grpc/reports/**" - } -} diff --git a/test/kokoro/xds_k8s_lb.cfg b/test/kokoro/xds_k8s_lb.cfg index 09aa3d17d..3efb62f29 100644 --- a/test/kokoro/xds_k8s_lb.cfg +++ b/test/kokoro/xds_k8s_lb.cfg @@ -15,7 +15,7 @@ # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. -build_file: "grpc-node/packages/grpc-js-xds/scripts/xds_k8s_lb.sh" +build_file: "grpc-node/packages/grpc-js-xds/scripts/psm-interop-test-node.sh" timeout_mins: 180 action { define_artifacts { @@ -24,3 +24,7 @@ action { strip_prefix: "artifacts" } } +env_vars { + key: "PSM_TEST_SUITE" + value: "lb" +} diff --git a/test/kokoro/xds_k8s_url_map.cfg b/test/kokoro/xds_k8s_url_map.cfg index 50d523b66..bb6e6baf1 100644 --- a/test/kokoro/xds_k8s_url_map.cfg +++ b/test/kokoro/xds_k8s_url_map.cfg @@ -15,7 +15,7 @@ # Config file for Kokoro (in protobuf text format) # Location of the continuous shell script in repository. -build_file: "grpc-node/packages/grpc-js-xds/scripts/xds_k8s_url_map.sh" +build_file: "grpc-node/packages/grpc-js-xds/scripts/psm-interop-test-node.sh" timeout_mins: 180 action { define_artifacts { @@ -24,3 +24,7 @@ action { strip_prefix: "artifacts" } } +env_vars { + key: "PSM_TEST_SUITE" + value: "url_map" +} From 87a35414021f627f01591cade9b1f9a7dcaaf5d3 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Tue, 14 May 2024 14:47:53 -0700 Subject: [PATCH 692/694] grpc-js: Fix UDS channels not reconnecting after going idle --- packages/grpc-js/package.json | 2 +- packages/grpc-js/src/resolver-uds.ts | 2 +- packages/grpc-js/test/common.ts | 51 ++++++++++++++----- packages/grpc-js/test/test-idle-timer.ts | 41 +++++++++++++++ packages/grpc-js/test/test-pick-first.ts | 2 +- .../grpc-js/test/test-server-interceptors.ts | 8 +-- 6 files changed, 86 insertions(+), 20 deletions(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index fdfab0d21..f8b070353 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.7", + "version": "1.10.8", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js", diff --git a/packages/grpc-js/src/resolver-uds.ts b/packages/grpc-js/src/resolver-uds.ts index 3a42b18c4..4d84de9d5 100644 --- a/packages/grpc-js/src/resolver-uds.ts +++ b/packages/grpc-js/src/resolver-uds.ts @@ -50,7 +50,7 @@ class UdsResolver implements Resolver { } destroy() { - // This resolver owns no resources, so we do nothing here. + this.hasReturnedResult = false; } static getDefaultAuthority(target: GrpcUri): string { diff --git a/packages/grpc-js/test/common.ts b/packages/grpc-js/test/common.ts index fcdbb4500..5efbf9808 100644 --- a/packages/grpc-js/test/common.ts +++ b/packages/grpc-js/test/common.ts @@ -19,6 +19,8 @@ import * as loader from '@grpc/proto-loader'; import * as assert2 from './assert2'; import * as path from 'path'; import * as grpc from '../src'; +import * as fsPromises from 'fs/promises'; +import * as os from 'os'; import { GrpcObject, @@ -71,54 +73,77 @@ const serviceImpl = { export class TestServer { private server: grpc.Server; - public port: number | null = null; + private target: string | null = null; constructor(public useTls: boolean, options?: grpc.ServerOptions) { this.server = new grpc.Server(options); this.server.addService(echoService.service, serviceImpl); } - start(): Promise { - let credentials: grpc.ServerCredentials; + + private getCredentials(): grpc.ServerCredentials { if (this.useTls) { - credentials = grpc.ServerCredentials.createSsl(null, [ + return grpc.ServerCredentials.createSsl(null, [ { private_key: key, cert_chain: cert }, ]); } else { - credentials = grpc.ServerCredentials.createInsecure(); + return grpc.ServerCredentials.createInsecure(); } + } + + start(): Promise { return new Promise((resolve, reject) => { - this.server.bindAsync('localhost:0', credentials, (error, port) => { + this.server.bindAsync('localhost:0', this.getCredentials(), (error, port) => { if (error) { reject(error); return; } - this.port = port; + this.target = `localhost:${port}`; resolve(); }); }); } + startUds(): Promise { + return fsPromises.mkdtemp(path.join(os.tmpdir(), 'uds')).then(dir => { + return new Promise((resolve, reject) => { + const target = `unix://${dir}/socket`; + this.server.bindAsync(target, this.getCredentials(), (error, port) => { + if (error) { + reject(error); + return; + } + this.target = target; + resolve(); + }); + }); + }); + } + shutdown() { this.server.forceShutdown(); } + + getTarget() { + if (this.target === null) { + throw new Error('Server not yet started'); + } + return this.target; + } } export class TestClient { private client: ServiceClient; - constructor(port: number, useTls: boolean, options?: grpc.ChannelOptions) { + constructor(target: string, useTls: boolean, options?: grpc.ChannelOptions) { let credentials: grpc.ChannelCredentials; if (useTls) { credentials = grpc.credentials.createSsl(ca); } else { credentials = grpc.credentials.createInsecure(); } - this.client = new echoService(`localhost:${port}`, credentials, options); + this.client = new echoService(target, credentials, options); } static createFromServer(server: TestServer, options?: grpc.ChannelOptions) { - if (server.port === null) { - throw new Error('Cannot create client, server not started'); - } - return new TestClient(server.port, server.useTls, options); + return new TestClient(server.getTarget(), server.useTls, options); } waitForReady(deadline: grpc.Deadline, callback: (error?: Error) => void) { diff --git a/packages/grpc-js/test/test-idle-timer.ts b/packages/grpc-js/test/test-idle-timer.ts index a8f457e3f..3f2a8ed20 100644 --- a/packages/grpc-js/test/test-idle-timer.ts +++ b/packages/grpc-js/test/test-idle-timer.ts @@ -129,6 +129,47 @@ describe('Channel idle timer', () => { }); }); +describe('Channel idle timer with UDS', () => { + let server: TestServer; + let client: TestClient | null = null; + before(() => { + server = new TestServer(false); + return server.startUds(); + }); + afterEach(() => { + if (client) { + client.close(); + client = null; + } + }); + after(() => { + server.shutdown(); + }); + it('Should be able to make a request after going idle', function (done) { + this.timeout(5000); + client = TestClient.createFromServer(server, { + 'grpc.client_idle_timeout_ms': 1000, + }); + client.sendRequest(error => { + assert.ifError(error); + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.READY + ); + setTimeout(() => { + assert.strictEqual( + client!.getChannelState(), + grpc.connectivityState.IDLE + ); + client!.sendRequest(error => { + assert.ifError(error); + done(); + }); + }, 1100); + }); + }); +}); + describe('Server idle timer', () => { let server: TestServer; let client: TestClient | null = null; diff --git a/packages/grpc-js/test/test-pick-first.ts b/packages/grpc-js/test/test-pick-first.ts index 4c2c319e1..9803a5853 100644 --- a/packages/grpc-js/test/test-pick-first.ts +++ b/packages/grpc-js/test/test-pick-first.ts @@ -811,7 +811,7 @@ describe('pick_first load balancing policy', () => { before(async () => { server = new TestServer(false); await server.start(); - client = new TestClient(server.port!, false, { + client = TestClient.createFromServer(server, { 'grpc.service_config': JSON.stringify(serviceConfig), }); }); diff --git a/packages/grpc-js/test/test-server-interceptors.ts b/packages/grpc-js/test/test-server-interceptors.ts index e94169721..5d4038599 100644 --- a/packages/grpc-js/test/test-server-interceptors.ts +++ b/packages/grpc-js/test/test-server-interceptors.ts @@ -153,7 +153,7 @@ describe('Server interceptors', () => { grpc.ServerCredentials.createInsecure(), (error, port) => { assert.ifError(error); - client = new TestClient(port, false); + client = new TestClient(`localhost:${port}`, false); done(); } ); @@ -195,7 +195,7 @@ describe('Server interceptors', () => { grpc.ServerCredentials.createInsecure(), (error, port) => { assert.ifError(error); - client = new TestClient(port, false); + client = new TestClient(`localhost:${port}`, false); done(); } ); @@ -246,7 +246,7 @@ describe('Server interceptors', () => { grpc.ServerCredentials.createInsecure(), (error, port) => { assert.ifError(error); - client = new TestClient(port, false); + client = new TestClient(`localhost:${port}`, false); done(); } ); @@ -292,7 +292,7 @@ describe('Server interceptors', () => { grpc.ServerCredentials.createInsecure(), (error, port) => { assert.ifError(error); - client = new TestClient(port, false); + client = new TestClient(`localhost:${port}`, false); done(); } ); From e64d816d7df6d6cde62314beb67d11f1e0a8c79e Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Wed, 5 Jun 2024 11:22:23 -0700 Subject: [PATCH 693/694] grpc-js: Avoid buffering significantly more than max_receive_message_size per received message (1.10.x) --- packages/grpc-js/src/compression-filter.ts | 67 ++++++++++---- packages/grpc-js/src/internal-channel.ts | 2 - .../grpc-js/src/max-message-size-filter.ts | 88 ------------------- packages/grpc-js/src/server-interceptors.ts | 88 ++++++++++++------- packages/grpc-js/src/stream-decoder.ts | 5 ++ packages/grpc-js/src/subchannel-call.ts | 14 ++- packages/grpc-js/src/transport.ts | 7 +- .../grpc-js/test/fixtures/test_service.proto | 1 + packages/grpc-js/test/test-server-errors.ts | 49 ++++++++++- 9 files changed, 173 insertions(+), 148 deletions(-) delete mode 100644 packages/grpc-js/src/max-message-size-filter.ts diff --git a/packages/grpc-js/src/compression-filter.ts b/packages/grpc-js/src/compression-filter.ts index 136311ad5..f1600b36d 100644 --- a/packages/grpc-js/src/compression-filter.ts +++ b/packages/grpc-js/src/compression-filter.ts @@ -21,7 +21,7 @@ import { WriteObject, WriteFlags } from './call-interface'; import { Channel } from './channel'; import { ChannelOptions } from './channel-options'; import { CompressionAlgorithms } from './compression-algorithms'; -import { LogVerbosity } from './constants'; +import { DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, LogVerbosity, Status } from './constants'; import { BaseFilter, Filter, FilterFactory } from './filter'; import * as logging from './logging'; import { Metadata, MetadataValue } from './metadata'; @@ -98,6 +98,10 @@ class IdentityHandler extends CompressionHandler { } class DeflateHandler extends CompressionHandler { + constructor(private maxRecvMessageLength: number) { + super(); + } + compressMessage(message: Buffer) { return new Promise((resolve, reject) => { zlib.deflate(message, (err, output) => { @@ -112,18 +116,34 @@ class DeflateHandler extends CompressionHandler { decompressMessage(message: Buffer) { return new Promise((resolve, reject) => { - zlib.inflate(message, (err, output) => { - if (err) { - reject(err); - } else { - resolve(output); + let totalLength = 0; + const messageParts: Buffer[] = []; + const decompresser = zlib.createInflate(); + decompresser.on('data', (chunk: Buffer) => { + messageParts.push(chunk); + totalLength += chunk.byteLength; + if (this.maxRecvMessageLength !== -1 && totalLength > this.maxRecvMessageLength) { + decompresser.destroy(); + reject({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message that decompresses to a size larger than ${this.maxRecvMessageLength}` + }); } }); + decompresser.on('end', () => { + resolve(Buffer.concat(messageParts)); + }); + decompresser.write(message); + decompresser.end(); }); } } class GzipHandler extends CompressionHandler { + constructor(private maxRecvMessageLength: number) { + super(); + } + compressMessage(message: Buffer) { return new Promise((resolve, reject) => { zlib.gzip(message, (err, output) => { @@ -138,13 +158,25 @@ class GzipHandler extends CompressionHandler { decompressMessage(message: Buffer) { return new Promise((resolve, reject) => { - zlib.unzip(message, (err, output) => { - if (err) { - reject(err); - } else { - resolve(output); + let totalLength = 0; + const messageParts: Buffer[] = []; + const decompresser = zlib.createGunzip(); + decompresser.on('data', (chunk: Buffer) => { + messageParts.push(chunk); + totalLength += chunk.byteLength; + if (this.maxRecvMessageLength !== -1 && totalLength > this.maxRecvMessageLength) { + decompresser.destroy(); + reject({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message that decompresses to a size larger than ${this.maxRecvMessageLength}` + }); } }); + decompresser.on('end', () => { + resolve(Buffer.concat(messageParts)); + }); + decompresser.write(message); + decompresser.end(); }); } } @@ -169,14 +201,14 @@ class UnknownHandler extends CompressionHandler { } } -function getCompressionHandler(compressionName: string): CompressionHandler { +function getCompressionHandler(compressionName: string, maxReceiveMessageSize: number): CompressionHandler { switch (compressionName) { case 'identity': return new IdentityHandler(); case 'deflate': - return new DeflateHandler(); + return new DeflateHandler(maxReceiveMessageSize); case 'gzip': - return new GzipHandler(); + return new GzipHandler(maxReceiveMessageSize); default: return new UnknownHandler(compressionName); } @@ -186,6 +218,7 @@ export class CompressionFilter extends BaseFilter implements Filter { private sendCompression: CompressionHandler = new IdentityHandler(); private receiveCompression: CompressionHandler = new IdentityHandler(); private currentCompressionAlgorithm: CompressionAlgorithm = 'identity'; + private maxReceiveMessageLength: number; constructor( channelOptions: ChannelOptions, @@ -195,6 +228,7 @@ export class CompressionFilter extends BaseFilter implements Filter { const compressionAlgorithmKey = channelOptions['grpc.default_compression_algorithm']; + this.maxReceiveMessageLength = channelOptions['grpc.max_receive_message_length'] ?? DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH if (compressionAlgorithmKey !== undefined) { if (isCompressionAlgorithmKey(compressionAlgorithmKey)) { const clientSelectedEncoding = CompressionAlgorithms[ @@ -215,7 +249,8 @@ export class CompressionFilter extends BaseFilter implements Filter { ) { this.currentCompressionAlgorithm = clientSelectedEncoding; this.sendCompression = getCompressionHandler( - this.currentCompressionAlgorithm + this.currentCompressionAlgorithm, + -1 ); } } else { @@ -247,7 +282,7 @@ export class CompressionFilter extends BaseFilter implements Filter { if (receiveEncoding.length > 0) { const encoding: MetadataValue = receiveEncoding[0]; if (typeof encoding === 'string') { - this.receiveCompression = getCompressionHandler(encoding); + this.receiveCompression = getCompressionHandler(encoding, this.maxReceiveMessageLength); } } metadata.remove('grpc-encoding'); diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 469ace557..e0cebd469 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -33,7 +33,6 @@ import { } from './resolver'; import { trace } from './logging'; import { SubchannelAddress } from './subchannel-address'; -import { MaxMessageSizeFilterFactory } from './max-message-size-filter'; import { mapProxyName } from './http_proxy'; import { GrpcUri, parseUri, uriToString } from './uri-parser'; import { ServerSurfaceCall } from './server-call'; @@ -402,7 +401,6 @@ export class InternalChannel { } ); this.filterStackFactory = new FilterStackFactory([ - new MaxMessageSizeFilterFactory(this.options), new CompressionFilterFactory(this, this.options), ]); this.trace( diff --git a/packages/grpc-js/src/max-message-size-filter.ts b/packages/grpc-js/src/max-message-size-filter.ts deleted file mode 100644 index b6df374b2..000000000 --- a/packages/grpc-js/src/max-message-size-filter.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020 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 { BaseFilter, Filter, FilterFactory } from './filter'; -import { WriteObject } from './call-interface'; -import { - Status, - DEFAULT_MAX_SEND_MESSAGE_LENGTH, - DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, -} from './constants'; -import { ChannelOptions } from './channel-options'; -import { Metadata } from './metadata'; - -export class MaxMessageSizeFilter extends BaseFilter implements Filter { - private maxSendMessageSize: number = DEFAULT_MAX_SEND_MESSAGE_LENGTH; - private maxReceiveMessageSize: number = DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; - constructor(options: ChannelOptions) { - super(); - if ('grpc.max_send_message_length' in options) { - this.maxSendMessageSize = options['grpc.max_send_message_length']!; - } - if ('grpc.max_receive_message_length' in options) { - this.maxReceiveMessageSize = options['grpc.max_receive_message_length']!; - } - } - - async sendMessage(message: Promise): Promise { - /* A configured size of -1 means that there is no limit, so skip the check - * entirely */ - if (this.maxSendMessageSize === -1) { - return message; - } else { - const concreteMessage = await message; - if (concreteMessage.message.length > this.maxSendMessageSize) { - throw { - code: Status.RESOURCE_EXHAUSTED, - details: `Sent message larger than max (${concreteMessage.message.length} vs. ${this.maxSendMessageSize})`, - metadata: new Metadata(), - }; - } else { - return concreteMessage; - } - } - } - - async receiveMessage(message: Promise): Promise { - /* A configured size of -1 means that there is no limit, so skip the check - * entirely */ - if (this.maxReceiveMessageSize === -1) { - return message; - } else { - const concreteMessage = await message; - if (concreteMessage.length > this.maxReceiveMessageSize) { - throw { - code: Status.RESOURCE_EXHAUSTED, - details: `Received message larger than max (${concreteMessage.length} vs. ${this.maxReceiveMessageSize})`, - metadata: new Metadata(), - }; - } else { - return concreteMessage; - } - } - } -} - -export class MaxMessageSizeFilterFactory - implements FilterFactory -{ - constructor(private readonly options: ChannelOptions) {} - - createFilter(): MaxMessageSizeFilter { - return new MaxMessageSizeFilter(this.options); - } -} diff --git a/packages/grpc-js/src/server-interceptors.ts b/packages/grpc-js/src/server-interceptors.ts index b62d55108..c2d985a6a 100644 --- a/packages/grpc-js/src/server-interceptors.ts +++ b/packages/grpc-js/src/server-interceptors.ts @@ -30,14 +30,10 @@ import { import * as http2 from 'http2'; import { getErrorMessage } from './error'; import * as zlib from 'zlib'; -import { promisify } from 'util'; import { StreamDecoder } from './stream-decoder'; import { CallEventTracker } from './transport'; import * as logging from './logging'; -const unzip = promisify(zlib.unzip); -const inflate = promisify(zlib.inflate); - const TRACER_NAME = 'server_call'; function trace(text: string) { @@ -496,7 +492,7 @@ export class BaseServerInterceptingCall private wantTrailers = false; private cancelNotified = false; private incomingEncoding = 'identity'; - private decoder = new StreamDecoder(); + private decoder: StreamDecoder; private readQueue: ReadQueueEntry[] = []; private isReadPending = false; private receivedHalfClose = false; @@ -554,6 +550,8 @@ export class BaseServerInterceptingCall this.maxReceiveMessageSize = options['grpc.max_receive_message_length']!; } + this.decoder = new StreamDecoder(this.maxReceiveMessageSize); + const metadata = Metadata.fromHttp2Headers(headers); if (logging.isTracerEnabled(TRACER_NAME)) { @@ -674,18 +672,41 @@ export class BaseServerInterceptingCall message: Buffer, encoding: string ): Buffer | Promise { - switch (encoding) { - case 'deflate': - return inflate(message.subarray(5)); - case 'gzip': - return unzip(message.subarray(5)); - case 'identity': - return message.subarray(5); - default: - return Promise.reject({ - code: Status.UNIMPLEMENTED, - details: `Received message compressed with unsupported encoding "${encoding}"`, + const messageContents = message.subarray(5); + if (encoding === 'identity') { + return messageContents; + } else if (encoding === 'deflate' || encoding === 'gzip') { + let decompresser: zlib.Gunzip | zlib.Deflate; + if (encoding === 'deflate') { + decompresser = zlib.createInflate(); + } else { + decompresser = zlib.createGunzip(); + } + return new Promise((resolve, reject) => { + let totalLength = 0 + const messageParts: Buffer[] = []; + decompresser.on('data', (chunk: Buffer) => { + messageParts.push(chunk); + totalLength += chunk.byteLength; + if (this.maxReceiveMessageSize !== -1 && totalLength > this.maxReceiveMessageSize) { + decompresser.destroy(); + reject({ + code: Status.RESOURCE_EXHAUSTED, + details: `Received message that decompresses to a size larger than ${this.maxReceiveMessageSize}` + }); + } + }); + decompresser.on('end', () => { + resolve(Buffer.concat(messageParts)); }); + decompresser.write(messageContents); + decompresser.end(); + }); + } else { + return Promise.reject({ + code: Status.UNIMPLEMENTED, + details: `Received message compressed with unsupported encoding "${encoding}"`, + }); } } @@ -698,10 +719,16 @@ export class BaseServerInterceptingCall const compressedMessageEncoding = compressed ? this.incomingEncoding : 'identity'; - const decompressedMessage = await this.decompressMessage( - queueEntry.compressedMessage!, - compressedMessageEncoding - ); + let decompressedMessage: Buffer; + try { + decompressedMessage = await this.decompressMessage( + queueEntry.compressedMessage!, + compressedMessageEncoding + ); + } catch (err) { + this.sendStatus(err as PartialStatusObject); + return; + } try { queueEntry.parsedMessage = this.handler.deserialize(decompressedMessage); } catch (err) { @@ -743,23 +770,16 @@ export class BaseServerInterceptingCall ' received data frame of size ' + data.length ); - const rawMessages = this.decoder.write(data); + let rawMessages: Buffer[]; + try { + rawMessages = this.decoder.write(data); + } catch (e) { + this.sendStatus({ code: Status.RESOURCE_EXHAUSTED, details: (e as Error).message }); + return; + } for (const messageBytes of rawMessages) { this.stream.pause(); - if ( - this.maxReceiveMessageSize !== -1 && - messageBytes.length - 5 > this.maxReceiveMessageSize - ) { - this.sendStatus({ - code: Status.RESOURCE_EXHAUSTED, - details: `Received message larger than max (${ - messageBytes.length - 5 - } vs. ${this.maxReceiveMessageSize})`, - metadata: null, - }); - return; - } const queueEntry: ReadQueueEntry = { type: 'COMPRESSED', compressedMessage: messageBytes, diff --git a/packages/grpc-js/src/stream-decoder.ts b/packages/grpc-js/src/stream-decoder.ts index 671ad41ae..ea669d14c 100644 --- a/packages/grpc-js/src/stream-decoder.ts +++ b/packages/grpc-js/src/stream-decoder.ts @@ -30,6 +30,8 @@ export class StreamDecoder { private readPartialMessage: Buffer[] = []; private readMessageRemaining = 0; + constructor(private maxReadMessageLength: number) {} + write(data: Buffer): Buffer[] { let readHead = 0; let toRead: number; @@ -60,6 +62,9 @@ export class StreamDecoder { // readSizeRemaining >=0 here if (this.readSizeRemaining === 0) { this.readMessageSize = this.readPartialSize.readUInt32BE(0); + if (this.maxReadMessageLength !== -1 && this.readMessageSize > this.maxReadMessageLength) { + throw new Error(`Received message larger than max (${this.readMessageSize} vs ${this.maxReadMessageLength})`); + } this.readMessageRemaining = this.readMessageSize; if (this.readMessageRemaining > 0) { this.readState = ReadState.READING_MESSAGE; diff --git a/packages/grpc-js/src/subchannel-call.ts b/packages/grpc-js/src/subchannel-call.ts index 0ce7d72cb..bee00119f 100644 --- a/packages/grpc-js/src/subchannel-call.ts +++ b/packages/grpc-js/src/subchannel-call.ts @@ -18,7 +18,7 @@ import * as http2 from 'http2'; import * as os from 'os'; -import { Status } from './constants'; +import { DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH, Status } from './constants'; import { Metadata } from './metadata'; import { StreamDecoder } from './stream-decoder'; import * as logging from './logging'; @@ -116,7 +116,7 @@ function mapHttpStatusCode(code: number): StatusObject { } export class Http2SubchannelCall implements SubchannelCall { - private decoder = new StreamDecoder(); + private decoder: StreamDecoder; private isReadFilterPending = false; private isPushPending = false; @@ -147,6 +147,8 @@ export class Http2SubchannelCall implements SubchannelCall { private readonly transport: Transport, private readonly callId: number ) { + const maxReceiveMessageLength = transport.getOptions()['grpc.max_receive_message_length'] ?? DEFAULT_MAX_RECEIVE_MESSAGE_LENGTH; + this.decoder = new StreamDecoder(maxReceiveMessageLength); http2Stream.on('response', (headers, flags) => { let headersString = ''; for (const header of Object.keys(headers)) { @@ -182,7 +184,13 @@ export class Http2SubchannelCall implements SubchannelCall { return; } this.trace('receive HTTP/2 data frame of length ' + data.length); - const messages = this.decoder.write(data); + let messages: Buffer[]; + try { + messages = this.decoder.write(data); + } catch (e) { + this.cancelWithStatus(Status.RESOURCE_EXHAUSTED, (e as Error).message); + return; + } for (const message of messages) { this.trace('parsed message of length ' + message.length); diff --git a/packages/grpc-js/src/transport.ts b/packages/grpc-js/src/transport.ts index 66a5d4556..934b62111 100644 --- a/packages/grpc-js/src/transport.ts +++ b/packages/grpc-js/src/transport.ts @@ -84,6 +84,7 @@ export interface TransportDisconnectListener { export interface Transport { getChannelzRef(): SocketRef; getPeerName(): string; + getOptions(): ChannelOptions; createCall( metadata: Metadata, host: string, @@ -147,7 +148,7 @@ class Http2Transport implements Transport { constructor( private session: http2.ClientHttp2Session, subchannelAddress: SubchannelAddress, - options: ChannelOptions, + private options: ChannelOptions, /** * Name of the remote server, if it is not the same as the subchannel * address, i.e. if connecting through an HTTP CONNECT proxy. @@ -617,6 +618,10 @@ class Http2Transport implements Transport { return this.subchannelAddressString; } + getOptions() { + return this.options; + } + shutdown() { this.session.close(); unregisterChannelzRef(this.channelzRef); diff --git a/packages/grpc-js/test/fixtures/test_service.proto b/packages/grpc-js/test/fixtures/test_service.proto index 64ce0d378..2a7a303f3 100644 --- a/packages/grpc-js/test/fixtures/test_service.proto +++ b/packages/grpc-js/test/fixtures/test_service.proto @@ -21,6 +21,7 @@ message Request { bool error = 1; string message = 2; int32 errorAfter = 3; + int32 responseLength = 4; } message Response { diff --git a/packages/grpc-js/test/test-server-errors.ts b/packages/grpc-js/test/test-server-errors.ts index 24ccfeef3..243e10918 100644 --- a/packages/grpc-js/test/test-server-errors.ts +++ b/packages/grpc-js/test/test-server-errors.ts @@ -33,6 +33,7 @@ import { } from '../src/server-call'; import { loadProtoFile } from './common'; +import { CompressionAlgorithms } from '../src/compression-algorithms'; const protoFile = join(__dirname, 'fixtures', 'test_service.proto'); const testServiceDef = loadProtoFile(protoFile); @@ -310,7 +311,7 @@ describe('Other conditions', () => { trailerMetadata ); } else { - cb(null, { count: 1 }, trailerMetadata); + cb(null, { count: 1, message: 'a'.repeat(req.responseLength) }, trailerMetadata); } }, @@ -320,6 +321,7 @@ describe('Other conditions', () => { ) { let count = 0; let errored = false; + let responseLength = 0; stream.on('data', (data: any) => { if (data.error) { @@ -327,13 +329,14 @@ describe('Other conditions', () => { errored = true; cb(new Error(message) as ServiceError, null, trailerMetadata); } else { + responseLength += data.responseLength; count++; } }); stream.on('end', () => { if (!errored) { - cb(null, { count }, trailerMetadata); + cb(null, { count, message: 'a'.repeat(responseLength) }, trailerMetadata); } }); }, @@ -349,7 +352,7 @@ describe('Other conditions', () => { }); } else { for (let i = 1; i <= 5; i++) { - stream.write({ count: i }); + stream.write({ count: i, message: 'a'.repeat(req.responseLength) }); if (req.errorAfter && req.errorAfter === i) { stream.emit('error', { code: grpc.status.UNKNOWN, @@ -376,7 +379,7 @@ describe('Other conditions', () => { err.metadata.add('count', '' + count); stream.emit('error', err); } else { - stream.write({ count }); + stream.write({ count, message: 'a'.repeat(data.responseLength) }); count++; } }); @@ -740,6 +743,44 @@ describe('Other conditions', () => { }); }); }); + + describe('Max message size', () => { + const largeMessage = 'a'.repeat(10_000_000); + it('Should be enforced on the server', done => { + client.unary({ message: largeMessage }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + }); + it('Should be enforced on the client', done => { + client.unary({ responseLength: 10_000_000 }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + done(); + }); + }); + describe('Compressed messages', () => { + it('Should be enforced with gzip', done => { + const compressingClient = new testServiceClient(`localhost:${port}`, clientInsecureCreds, {'grpc.default_compression_algorithm': CompressionAlgorithms.gzip}); + compressingClient.unary({ message: largeMessage }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + assert.match(error.details, /Received message that decompresses to a size larger/); + done(); + }); + }); + it('Should be enforced with deflate', done => { + const compressingClient = new testServiceClient(`localhost:${port}`, clientInsecureCreds, {'grpc.default_compression_algorithm': CompressionAlgorithms.deflate}); + compressingClient.unary({ message: largeMessage }, (error?: ServiceError) => { + assert(error); + assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED); + assert.match(error.details, /Received message that decompresses to a size larger/); + done(); + }); + }); + }); + }); }); function identity(arg: any): any { From 7ecaa2d2dcaaa49467d41143169212caf55a40cd Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Fri, 7 Jun 2024 10:52:50 -0700 Subject: [PATCH 694/694] grpc-js: Bump to 1.10.9 --- packages/grpc-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index f8b070353..73b63bf7b 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -1,6 +1,6 @@ { "name": "@grpc/grpc-js", - "version": "1.10.8", + "version": "1.10.9", "description": "gRPC Library for Node - pure JS implementation", "homepage": "https://grpc.io/", "repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",